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:
119
infogenie-backend-go/internal/cache/redis.go
vendored
Normal file
119
infogenie-backend-go/internal/cache/redis.go
vendored
Normal 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;未启用或未命中返回 false(err 仅表示 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()
|
||||
}
|
||||
Reference in New Issue
Block a user