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
120 lines
2.5 KiB
Go
120 lines
2.5 KiB
Go
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()
|
||
}
|