diff --git a/main.go b/main.go index a40bbd9..992bf9d 100644 --- a/main.go +++ b/main.go @@ -32,9 +32,11 @@ func main() { if err != nil { panic("failed to connect database:" + err.Error()) } - err = worker.InitRedis() - if err != nil { - panic("failed to connect redis:" + err.Error()) + if proto.Config.MEMORY_CACHE == false { //不开启内存缓存,才使用redis + err = worker.InitRedis() + if err != nil { + panic("failed to connect redis:" + err.Error()) + } } r.Use(handler.CrosHandler()) r.Use(JWTAuthMiddleware()) // 使用 JWT 认证中间件 @@ -233,7 +235,11 @@ func myTask() { service.ShellWillRunFromServer() service.SyncTokenSecretFromUserCenter() service.DelDBMMap() //定时处理DBMMap中的数据 - + if proto.Config.MEMORY_CACHE { + worker.DeleteMemoryCacheCron() + // 清理后持久化 + worker.WriteMemoryCacheToFile() + } } func ReadConfigToSetSystem() { diff --git a/proto/conf.go b/proto/conf.go index f821ed0..2253348 100644 --- a/proto/conf.go +++ b/proto/conf.go @@ -88,6 +88,7 @@ type ConfigStruct struct { TOKEN_USE_REDIS bool `json:"token_use_redis"` REDIS_User_PW bool `json:"redis_user_pw"` // 是否使用密码 REDIS_PASSWORD string `json:"redis_password"` + MEMORY_CACHE bool `json:"memory_cache"` //使用程序内缓存,开启这个redis将不生效 REDIS_DB int `json:"redis_db"` TOKEN_SECRET string `json:"token_secret"` CID_BASE_DIR string `json:"cid_base_dir"` diff --git a/worker/memorycache.go b/worker/memorycache.go new file mode 100644 index 0000000..29ca86e --- /dev/null +++ b/worker/memorycache.go @@ -0,0 +1,186 @@ +package worker + +import ( + "encoding/json" + "log" + "math" + "os" + "sync" + "time" +) + +// MemoryCacheValue 缓存值结构,包含值和过期时间 +type MemoryCacheValue struct { + Value string `json:"value"` + ExpireAt int64 `json:"expireAt"` // 过期时间戳,秒级 +} + +var ( + memoryCacheData = NewMemoryCache() +) + +// MemoryCache 线程安全的内存缓存 +type MemoryCache struct { + data map[string]MemoryCacheValue + mu sync.RWMutex +} + +// NewMemoryCache 创建新的内存缓存实例 +func NewMemoryCache() *MemoryCache { + return &MemoryCache{ + data: make(map[string]MemoryCacheValue), + } +} + +// InitStaticMemoryCache 初始化全局缓存实例 +func InitStaticMemoryCache() { + // 先尝试从文件加载 + ReadMemoryCacheFromJsonFile() + +} + +// SetWithExp 设置带过期时间的键值对 +func (mc *MemoryCache) SetWithExp(key string, value string, expireAt int64) { + mc.mu.Lock() + defer mc.mu.Unlock() + mc.data[key] = MemoryCacheValue{value, expireAt} +} + +// Get 获取键值,如果已过期则返回空并删除 +func (mc *MemoryCache) Get(key string) string { + // 先加读锁检查 + mc.mu.RLock() + value, ok := mc.data[key] + mc.mu.RUnlock() + + if !ok { + return "" + } + + // 检查是否过期 + now := time.Now().Unix() + if value.ExpireAt > now { + return value.Value + } + + // 已过期,删除该键 + mc.mu.Lock() + // 二次检查,防止并发情况下已被删除 + if v, exists := mc.data[key]; exists && v.ExpireAt <= now { + delete(mc.data, key) + } + mc.mu.Unlock() + + return "" +} + +// Set 设置永不过期的键值对 +func (mc *MemoryCache) Set(key string, value string) { + mc.SetWithExp(key, value, math.MaxInt64) +} + +// Del 删除指定键 +func (mc *MemoryCache) Del(key string) { + mc.mu.Lock() + defer mc.mu.Unlock() + delete(mc.data, key) +} + +// Clear 清空所有缓存 +func (mc *MemoryCache) Clear() { + mc.mu.Lock() + defer mc.mu.Unlock() + mc.data = make(map[string]MemoryCacheValue) +} + +const CRON_MAX_DEL_NUMBER = 100 // 每次清理的最大数量 + +// DeleteMemoryCacheCron 定时清理过期键 +func DeleteMemoryCacheCron() { + memoryCacheData.mu.Lock() + defer memoryCacheData.mu.Unlock() + + now := time.Now().Unix() + i := 0 + for key, value := range memoryCacheData.data { + if i >= CRON_MAX_DEL_NUMBER { + break + } + if value.ExpireAt < now { + delete(memoryCacheData.data, key) + i++ + } + } +} + +// GetMemoryCacheFilePath 获取缓存持久化文件路径 +func GetMemoryCacheFilePath() string { + if os.Getenv("OS") == "Windows_NT" { + return "C:/Users/Administrator/vp_mc.json" + } + return "/etc/vp_mc.json" +} + +// WriteMemoryCacheToFile 将缓存写入文件持久化 +func WriteMemoryCacheToFile() { + memoryCacheData.mu.RLock() + defer memoryCacheData.mu.RUnlock() + + path := GetMemoryCacheFilePath() + data, err := json.MarshalIndent(memoryCacheData.data, "", " ") + if err != nil { + log.Println("mc write file json err:", err) + return + } + + // 先写入临时文件,再原子替换,防止文件损坏 + tempPath := path + ".tmp" + if err := os.WriteFile(tempPath, data, 0644); err != nil { + log.Println("mc write temp file err:", err) + return + } + + if err := os.Rename(tempPath, path); err != nil { + log.Println("mc rename file err:", err) + os.Remove(tempPath) // 清理临时文件 + } +} + +// ReadMemoryCacheFromJsonFile 从文件加载缓存 +func ReadMemoryCacheFromJsonFile() { + path := GetMemoryCacheFilePath() + _, err := os.Stat(path) + if err != nil { + log.Println("mc file not exists:", err) + return + } + + file, err := os.Open(path) + if err != nil { + log.Println("mc open file err:", err) + return + } + defer file.Close() + + var data map[string]MemoryCacheValue + decoder := json.NewDecoder(file) + if err := decoder.Decode(&data); err != nil { + log.Println("mc decode file err:", err) + return + } + + // 过滤已过期的数据 + now := time.Now().Unix() + memoryCacheData.mu.Lock() + for k, v := range data { + if v.ExpireAt > now || v.ExpireAt == math.MaxInt64 { + memoryCacheData.data[k] = v + } + } + memoryCacheData.mu.Unlock() +} + +// 提供全局缓存的访问方法 +func GetGlobalCache() *MemoryCache { + return memoryCacheData +}