first commit
This commit is contained in:
139
internal/database/cache.go
Normal file
139
internal/database/cache.go
Normal file
@@ -0,0 +1,139 @@
|
||||
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() {
|
||||
addr := net.JoinHostPort(config.AppConfig.RedisHost, config.AppConfig.RedisPort)
|
||||
client := redis.NewClient(&redis.Options{
|
||||
Addr: addr,
|
||||
Password: config.AppConfig.RedisPass,
|
||||
DB: config.AppConfig.RedisDB,
|
||||
})
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := client.Ping(ctx).Err(); err != nil {
|
||||
log.Printf("Redis/Valkey unavailable, falling back to in-memory cache: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
Redis = client
|
||||
log.Printf("Redis/Valkey 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)
|
||||
}
|
||||
Reference in New Issue
Block a user