146 lines
2.7 KiB
Go
146 lines
2.7 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 == "" {
|
|
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 {
|
|
log.Printf("Redis unavailable at %s, falling back to in-memory: %v", addr, err)
|
|
return
|
|
}
|
|
|
|
Redis = client
|
|
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)
|
|
}
|