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
This commit is contained in:
2026-04-03 16:10:12 +08:00
parent 284b5a5260
commit 6b3fcc1791
25 changed files with 1078 additions and 972 deletions

View File

@@ -0,0 +1,119 @@
package cache
import (
"context"
"encoding/json"
"fmt"
"log"
"time"
"github.com/redis/go-redis/v9"
"infogenie-backend/config"
)
// Key suffixes完整 key = KeyPrefix + suffix
const (
KeySite60sDisabled = "site:60s-disabled"
KeySite60sSource = "site:60s-source"
KeySiteAIModelDisabled = "site:ai-model-disabled"
)
func KeySiteFeatureClicks(section string) string {
return "site:feature-clicks:" + section
}
var (
rdb *redis.Client
keyPrefix string
siteCacheTTL time.Duration
redisOn bool
)
// Init 在 REDIS_ENABLED 时连接并 Ping未启用时为空操作
func Init(rc config.RedisConfig) error {
if !rc.Enabled {
redisOn = false
rdb = nil
return nil
}
redisOn = true
keyPrefix = rc.KeyPrefix
siteCacheTTL = rc.SiteTTL
if siteCacheTTL <= 0 {
siteCacheTTL = 60 * time.Second
}
rdb = redis.NewClient(&redis.Options{
Addr: rc.Addr,
Password: rc.Password,
DB: rc.DB,
})
if err := rdb.Ping(context.Background()).Err(); err != nil {
return fmt.Errorf("redis: %w", err)
}
return nil
}
// Enabled 表示已初始化且客户端可用
func Enabled() bool {
return redisOn && rdb != nil
}
func fullKey(suffix string) string {
return keyPrefix + suffix
}
// GetJSON 命中返回 true未启用或未命中返回 falseerr 仅表示 Redis/JSON 异常)
func GetJSON(ctx context.Context, suffix string, dest interface{}) (bool, error) {
if !Enabled() {
return false, nil
}
val, err := rdb.Get(ctx, fullKey(suffix)).Bytes()
if err == redis.Nil {
return false, nil
}
if err != nil {
return false, err
}
if err := json.Unmarshal(val, dest); err != nil {
return false, err
}
return true, nil
}
// SetJSON ttl<=0 时使用配置的 SiteTTL
func SetJSON(ctx context.Context, suffix string, v interface{}, ttl time.Duration) error {
if !Enabled() {
return nil
}
b, err := json.Marshal(v)
if err != nil {
return err
}
if ttl <= 0 {
ttl = siteCacheTTL
}
return rdb.Set(ctx, fullKey(suffix), b, ttl).Err()
}
// Delete 删除若干后缀对应的 key失败仅打日志
func Delete(ctx context.Context, suffixes ...string) {
if !Enabled() || len(suffixes) == 0 {
return
}
keys := make([]string, 0, len(suffixes))
for _, s := range suffixes {
keys = append(keys, fullKey(s))
}
if err := rdb.Del(ctx, keys...).Err(); err != nil {
log.Printf("redis DEL 失败: %v keys=%v", err, keys)
}
}
// Ping 未启用时返回 nil
func Ping(ctx context.Context) error {
if !Enabled() {
return nil
}
return rdb.Ping(ctx).Err()
}