Files
SingBox-Gopanel/internal/database/cache.go
CN-JS-HuiBai 97f0672729
Some checks failed
build / build (api, amd64, linux) (push) Failing after -50s
build / build (api, arm64, linux) (push) Failing after -51s
build / build (api.exe, amd64, windows) (push) Failing after -51s
修复订阅无法正常获取的错误
2026-04-17 23:07:47 +08:00

152 lines
2.9 KiB
Go

package database
import (
"context"
"encoding/json"
"log"
"net"
"sync"
"time"
"xboard-go/internal/config"
"github.com/redis/go-redis/v9"
)
var Redis *redis.Client
type memoryEntry struct {
Value []byte
ExpiresAt time.Time
}
type memoryCache struct {
mu sync.RWMutex
items map[string]memoryEntry
}
var fallbackCache = &memoryCache{
items: make(map[string]memoryEntry),
}
func InitCache() {
if config.AppConfig.RedisHost == "" {
if config.IsLogLevelEnabled(config.AppConfig.LogLevel, "warn") {
log.Printf("Redis host not configured, using in-memory cache")
}
return
}
addr := net.JoinHostPort(config.AppConfig.RedisHost, config.AppConfig.RedisPort)
client := redis.NewClient(&redis.Options{
Addr: addr,
Password: config.AppConfig.RedisPass,
DB: config.AppConfig.RedisDB,
})
// Fast timeout for initial connectivity check to avoid blocking startup
ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
defer cancel()
if err := client.Ping(ctx).Err(); err != nil {
if config.IsLogLevelEnabled(config.AppConfig.LogLevel, "warn") {
log.Printf("Redis unavailable at %s, falling back to in-memory: %v", addr, err)
}
return
}
Redis = client
if config.IsLogLevelEnabled(config.AppConfig.LogLevel, "info") {
log.Printf("Redis connection established at %s", addr)
}
}
func CacheSet(key string, value any, ttl time.Duration) error {
payload, err := json.Marshal(value)
if err != nil {
return err
}
if Redis != nil {
if err := Redis.Set(context.Background(), key, payload, ttl).Err(); err == nil {
return nil
}
}
fallbackCache.Set(key, payload, ttl)
return nil
}
func CacheDelete(key string) error {
if Redis != nil {
if err := Redis.Del(context.Background(), key).Err(); err == nil {
fallbackCache.Delete(key)
return nil
}
}
fallbackCache.Delete(key)
return nil
}
func CacheGetJSON[T any](key string) (T, bool) {
var zero T
var payload []byte
if Redis != nil {
value, err := Redis.Get(context.Background(), key).Bytes()
if err == nil {
payload = value
}
}
if len(payload) == 0 {
value, ok := fallbackCache.Get(key)
if !ok {
return zero, false
}
payload = value
}
var result T
if err := json.Unmarshal(payload, &result); err != nil {
return zero, false
}
return result, true
}
func (m *memoryCache) Set(key string, value []byte, ttl time.Duration) {
m.mu.Lock()
defer m.mu.Unlock()
entry := memoryEntry{
Value: value,
}
if ttl > 0 {
entry.ExpiresAt = time.Now().Add(ttl)
}
m.items[key] = entry
}
func (m *memoryCache) Get(key string) ([]byte, bool) {
m.mu.RLock()
entry, ok := m.items[key]
m.mu.RUnlock()
if !ok {
return nil, false
}
if !entry.ExpiresAt.IsZero() && time.Now().After(entry.ExpiresAt) {
m.Delete(key)
return nil, false
}
return entry.Value, true
}
func (m *memoryCache) Delete(key string) {
m.mu.Lock()
defer m.mu.Unlock()
delete(m.items, key)
}