mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-01-13 01:02:46 +00:00
123 lines
3.3 KiB
Go
123 lines
3.3 KiB
Go
// Package cache provides caching utilities with JSON serialization support.
|
|
package cache
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/mhsanaei/3x-ui/v2/logger"
|
|
)
|
|
|
|
const (
|
|
// Default TTL values
|
|
TTLInbounds = 30 * time.Second
|
|
TTLClients = 30 * time.Second
|
|
TTLSettings = 5 * time.Minute
|
|
TTLSetting = 10 * time.Minute // Increased from 5 to 10 minutes for better cache hit rate
|
|
)
|
|
|
|
// Cache keys
|
|
const (
|
|
KeyInboundsPrefix = "inbounds:user:"
|
|
KeyClientsPrefix = "clients:user:"
|
|
KeySettingsAll = "settings:all"
|
|
KeySettingPrefix = "setting:"
|
|
)
|
|
|
|
// GetJSON retrieves a value from cache and unmarshals it as JSON.
|
|
func GetJSON(key string, dest interface{}) error {
|
|
val, err := Get(key)
|
|
if err != nil {
|
|
// Check if it's a "key not found" error (redis.Nil)
|
|
// This is expected and not a real error
|
|
if err.Error() == "redis: nil" {
|
|
return fmt.Errorf("key not found: %s", key)
|
|
}
|
|
return err
|
|
}
|
|
if val == "" {
|
|
return fmt.Errorf("empty value for key: %s", key)
|
|
}
|
|
return json.Unmarshal([]byte(val), dest)
|
|
}
|
|
|
|
// SetJSON marshals a value as JSON and stores it in cache.
|
|
func SetJSON(key string, value interface{}, expiration time.Duration) error {
|
|
data, err := json.Marshal(value)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal value: %w", err)
|
|
}
|
|
return Set(key, string(data), expiration)
|
|
}
|
|
|
|
// GetOrSet retrieves a value from cache, or computes it using fn if not found.
|
|
func GetOrSet(key string, dest interface{}, expiration time.Duration, fn func() (interface{}, error)) error {
|
|
// Try to get from cache
|
|
err := GetJSON(key, dest)
|
|
if err == nil {
|
|
logger.Debugf("Cache hit for key: %s", key)
|
|
return nil
|
|
}
|
|
|
|
// Cache miss, compute value
|
|
logger.Debugf("Cache miss for key: %s", key)
|
|
value, err := fn()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Store in cache
|
|
if err := SetJSON(key, value, expiration); err != nil {
|
|
logger.Warningf("Failed to set cache for key %s: %v", key, err)
|
|
}
|
|
|
|
// Copy value to dest
|
|
data, err := json.Marshal(value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return json.Unmarshal(data, dest)
|
|
}
|
|
|
|
// InvalidateInbounds invalidates all inbounds cache for a user.
|
|
func InvalidateInbounds(userId int) error {
|
|
pattern := fmt.Sprintf("%s%d", KeyInboundsPrefix, userId)
|
|
return DeletePattern(pattern)
|
|
}
|
|
|
|
// InvalidateAllInbounds invalidates all inbounds cache.
|
|
func InvalidateAllInbounds() error {
|
|
pattern := KeyInboundsPrefix + "*"
|
|
return DeletePattern(pattern)
|
|
}
|
|
|
|
// InvalidateClients invalidates all clients cache for a user.
|
|
func InvalidateClients(userId int) error {
|
|
pattern := fmt.Sprintf("%s%d", KeyClientsPrefix, userId)
|
|
return DeletePattern(pattern)
|
|
}
|
|
|
|
// InvalidateAllClients invalidates all clients cache.
|
|
func InvalidateAllClients() error {
|
|
pattern := KeyClientsPrefix + "*"
|
|
return DeletePattern(pattern)
|
|
}
|
|
|
|
// InvalidateSetting invalidates a specific setting cache.
|
|
// Note: We don't invalidate KeySettingsAll here to avoid unnecessary cache misses.
|
|
// KeySettingsAll will be invalidated only when settings are actually changed.
|
|
func InvalidateSetting(key string) error {
|
|
settingKey := KeySettingPrefix + key
|
|
return Delete(settingKey)
|
|
}
|
|
|
|
// InvalidateAllSettings invalidates all settings cache.
|
|
func InvalidateAllSettings() error {
|
|
if err := Delete(KeySettingsAll); err != nil {
|
|
return err
|
|
}
|
|
// Also invalidate all individual settings
|
|
pattern := KeySettingPrefix + "*"
|
|
return DeletePattern(pattern)
|
|
}
|