多平台账号批量注册系统,支持 OpenAI / Grok / Kiro / Gemini 平台的全自动注册。 Features: - 用户名/密码注册登录(首个用户自动管理员) - JWT 认证 + bcrypt 密码加密 - 积分系统与多用户管理 - HuggingFace Space 弹性 Worker 节点管理 - Cloudflare Worker 负载均衡 - 实时日志推送(SSE/WebSocket) - 管理后台(用户管理、系统设置、代理池、卡池) Tech Stack: Go 1.25 / Gin / GORM / PostgreSQL / Vue 3 / Vite / TailwindCSS
133 lines
3.2 KiB
Go
133 lines
3.2 KiB
Go
package cache
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"time"
|
||
|
||
"github.com/redis/go-redis/v9"
|
||
"github.com/rs/zerolog/log"
|
||
)
|
||
|
||
// RedisCache Redis 缓存层(L2)
|
||
// 设计为可选组件:REDIS_URL 为空时所有方法静默返回 miss,不影响业务
|
||
type RedisCache struct {
|
||
client *redis.Client
|
||
prefix string // key 前缀,如 "regp:"
|
||
}
|
||
|
||
// NewRedisCache 创建 Redis 缓存
|
||
// redisURL 为空则返回空壳实例(所有操作静默降级)
|
||
func NewRedisCache(redisURL, prefix string) *RedisCache {
|
||
rc := &RedisCache{prefix: prefix}
|
||
if redisURL == "" {
|
||
log.Info().Msg("REDIS_URL 未配置,L2 缓存已禁用(纯内存模式)")
|
||
return rc
|
||
}
|
||
|
||
opts, err := redis.ParseURL(redisURL)
|
||
if err != nil {
|
||
log.Error().Err(err).Str("url", redisURL).Msg("Redis URL 解析失败,L2 缓存已禁用")
|
||
return rc
|
||
}
|
||
|
||
client := redis.NewClient(opts)
|
||
|
||
// 连接测试(3 秒超时)
|
||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
||
defer cancel()
|
||
if err := client.Ping(ctx).Err(); err != nil {
|
||
log.Error().Err(err).Msg("Redis 连接失败,L2 缓存已禁用")
|
||
return rc
|
||
}
|
||
|
||
rc.client = client
|
||
log.Info().Str("addr", opts.Addr).Msg("Redis L2 缓存已连接")
|
||
return rc
|
||
}
|
||
|
||
// Available 检查 Redis 是否可用
|
||
func (c *RedisCache) Available() bool {
|
||
return c.client != nil
|
||
}
|
||
|
||
// key 拼接完整 key
|
||
func (c *RedisCache) key(k string) string {
|
||
return c.prefix + k
|
||
}
|
||
|
||
// GetString 获取字符串值
|
||
func (c *RedisCache) GetString(ctx context.Context, key string) (string, bool) {
|
||
if c.client == nil {
|
||
return "", false
|
||
}
|
||
val, err := c.client.Get(ctx, c.key(key)).Result()
|
||
if err != nil {
|
||
return "", false
|
||
}
|
||
return val, true
|
||
}
|
||
|
||
// SetString 设置字符串值
|
||
func (c *RedisCache) SetString(ctx context.Context, key, value string, ttl time.Duration) {
|
||
if c.client == nil {
|
||
return
|
||
}
|
||
if err := c.client.Set(ctx, c.key(key), value, ttl).Err(); err != nil {
|
||
log.Warn().Err(err).Str("key", key).Msg("Redis SET 失败")
|
||
}
|
||
}
|
||
|
||
// GetJSON 获取 JSON 序列化的值
|
||
func (c *RedisCache) GetJSON(ctx context.Context, key string, dest interface{}) bool {
|
||
if c.client == nil {
|
||
return false
|
||
}
|
||
val, err := c.client.Get(ctx, c.key(key)).Bytes()
|
||
if err != nil {
|
||
return false
|
||
}
|
||
if err := json.Unmarshal(val, dest); err != nil {
|
||
log.Warn().Err(err).Str("key", key).Msg("Redis JSON 反序列化失败")
|
||
return false
|
||
}
|
||
return true
|
||
}
|
||
|
||
// SetJSON 设置 JSON 序列化的值
|
||
func (c *RedisCache) SetJSON(ctx context.Context, key string, value interface{}, ttl time.Duration) {
|
||
if c.client == nil {
|
||
return
|
||
}
|
||
data, err := json.Marshal(value)
|
||
if err != nil {
|
||
log.Warn().Err(err).Str("key", key).Msg("Redis JSON 序列化失败")
|
||
return
|
||
}
|
||
if err := c.client.Set(ctx, c.key(key), data, ttl).Err(); err != nil {
|
||
log.Warn().Err(err).Str("key", key).Msg("Redis SET 失败")
|
||
}
|
||
}
|
||
|
||
// Del 删除 key
|
||
func (c *RedisCache) Del(ctx context.Context, keys ...string) {
|
||
if c.client == nil {
|
||
return
|
||
}
|
||
fullKeys := make([]string, len(keys))
|
||
for i, k := range keys {
|
||
fullKeys[i] = c.key(k)
|
||
}
|
||
if err := c.client.Del(ctx, fullKeys...).Err(); err != nil {
|
||
log.Warn().Err(err).Msg("Redis DEL 失败")
|
||
}
|
||
}
|
||
|
||
// Close 关闭连接
|
||
func (c *RedisCache) Close() error {
|
||
if c.client == nil {
|
||
return nil
|
||
}
|
||
return c.client.Close()
|
||
}
|