Files
InfoGenie/infogenie-backend-go/internal/handler/feature_card_clicks.go
树萌芽 6b3fcc1791 security: stop tracking env files; admin gate via VITE_SITE_ADMIN_GATE; Redis/cache/docs
Remove InfoGenie-frontend and Go .env from version control; add .env.example templates; ignore .claude local settings. Admin UI reads site gate from env only. Note: rotate secrets if repo history was ever public.

Made-with: Cursor
2026-04-03 16:10:12 +08:00

116 lines
3.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package handler
import (
"log"
"net/http"
"strings"
"github.com/gin-gonic/gin"
"infogenie-backend/internal/cache"
"infogenie-backend/internal/database"
"infogenie-backend/internal/model"
)
var featureCardSections = map[string]struct{}{
"60sapi": {},
"smallgame": {},
"toolbox": {},
"aimodel": {},
}
func normalizeFeatureSection(s string) (string, bool) {
key := strings.ToLower(strings.TrimSpace(s))
if key == "" {
return "", false
}
if _, ok := featureCardSections[key]; !ok {
return "", false
}
return key, true
}
func sanitizeFeatureItemID(raw string) (string, bool) {
id := strings.TrimSpace(raw)
if id == "" || len(id) > 128 {
return "", false
}
return id, true
}
// GetFeatureCardClicks 公开:按板块返回各功能 id 的点击次数(未出现过的 id 前端视为 0
func (h *SiteConfigHandler) GetFeatureCardClicks(c *gin.Context) {
section, ok := normalizeFeatureSection(c.Query("section"))
if !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid_section"})
return
}
ctx := c.Request.Context()
key := cache.KeySiteFeatureClicks(section)
var cached struct {
Section string `json:"section"`
Counts map[string]uint64 `json:"counts"`
}
if hit, err := cache.GetJSON(ctx, key, &cached); err != nil {
log.Printf("redis GET %s: %v", key, err)
} else if hit {
c.JSON(http.StatusOK, gin.H{"section": cached.Section, "counts": cached.Counts})
return
}
var rows []model.SiteFeatureCardClick
if err := database.DB.Where("section = ?", section).Find(&rows).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "db_error"})
return
}
counts := make(map[string]uint64, len(rows))
for _, r := range rows {
counts[r.ItemID] = r.ClickCount
}
payload := gin.H{"section": section, "counts": counts}
if err := cache.SetJSON(ctx, key, payload, 0); err != nil {
log.Printf("redis SET %s: %v", key, err)
}
c.JSON(http.StatusOK, payload)
}
type postFeatureCardClickBody struct {
Section string `json:"section"`
ItemID string `json:"item_id"`
}
// PostFeatureCardClickIncrement 公开:记录一次卡片点击并返回最新计数
func (h *SiteConfigHandler) PostFeatureCardClickIncrement(c *gin.Context) {
var body postFeatureCardClickBody
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid_json"})
return
}
section, ok := normalizeFeatureSection(body.Section)
if !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid_section"})
return
}
itemID, ok := sanitizeFeatureItemID(body.ItemID)
if !ok {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid_item_id"})
return
}
const q = `INSERT INTO site_feature_card_clicks (section, item_id, click_count, updated_at)
VALUES (?, ?, 1, NOW())
ON DUPLICATE KEY UPDATE click_count = click_count + 1, updated_at = NOW()`
if err := database.DB.Exec(q, section, itemID).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "db_error"})
return
}
var row model.SiteFeatureCardClick
if err := database.DB.Where("section = ? AND item_id = ?", section, itemID).First(&row).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "db_error"})
return
}
cache.Delete(c.Request.Context(), cache.KeySiteFeatureClicks(section))
c.JSON(http.StatusOK, gin.H{"section": section, "item_id": itemID, "count": row.ClickCount})
}