Files
huangguo_server/common/cachev2/base.go
T
rootandClaude Opus 5 8679200f41 Initial commit
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 13:57:10 +08:00

446 lines
9.8 KiB
Go

package cachev2
import (
"crypto/md5"
"fmt"
"math"
"math/rand"
"runtime"
"strings"
"time"
"go.uber.org/zap"
)
const (
// 默认的缓存时间
defaultCacheTime = 1800
// 空数据的缓存时间
emptyDataCacheTime = 300
// 数据类型
dataTypeList = "list"
dataTypeInfo = "info"
)
// BaseCache 缓存类定义
// 集成到类黑料框架文件:
//
// copy common/cachev2/*
// common/redis/redis.go +ScanKeys方法
// web/main.go | app/main.go (+初始化缓存操作中间件)
//
// Usage:
//
// cache object: cachev2.Classes().CacheTime(30*time.Minute).Key("cache-key").ResBind(&object).Cache(user_func, params...)
// cache base type: cachev2.Classes().CacheTime(2*time.Hour).Key("cache-key").Cache(user_func, params...)
// auto cache key(list): cachev2.Classes().CacheTime(600*time.Second).AutoListKey("table-name").Cache(user_func, params...)
// auto cache key(info): cachev2.Classes().CacheTime(600*time.Second).AutoInfoKey("table-name", "id-string").Cache(user_func, params...)
//
// 清理缓存:
//
// clear cache key: cachev2.Classes().FussyClear("key-prefix*")
// clear auto cache(just list cache): cachev2.Classes().Table("table-name").AutoClear(true, nil)
// clear auto cache(just info cache): cachev2.Classes().Table("table-name").AutoClear(false, &idString)
// clear auto cache(both of list and info): cachev2.Classes().Table("table-name").AutoClear(true, &idString)
type BaseCache struct {
// 缓存驱动器
driver Driver
// 缓存时间 s
ttl int64
// 是否随机时间
random bool
// 缓存随机时间 s
randomTTL int
// 是否需要刷新缓存
refresh bool
// 缓存key
key string
// 自动生成缓存key
autoKey bool
// 表名
table string
// 数据类型
dataType string
// 数据id
dataID string
// 缓存对象
object interface{}
// 日志驱动
logger *zap.Logger
}
// basic 缓存基础
var basic = &BaseCache{}
// Init 初始化
func Init(d Driver, l *zap.Logger) {
basic.driver = d
basic.logger = l
}
// Classes 获取操作类
func Classes() *BaseCache {
c := &BaseCache{
driver: basic.driver,
ttl: defaultCacheTime,
random: true,
logger: basic.logger,
}
return c
}
// New 新建缓存操作类
func New(d Driver) *BaseCache {
rand.Seed(time.Now().UnixNano())
rnd := rand.Intn(30)
c := &BaseCache{
driver: d,
ttl: defaultCacheTime,
random: true,
randomTTL: rnd,
logger: basic.logger,
}
return c
}
// Key 设置缓存键
func (c *BaseCache) Key(k string) *BaseCache {
c.key = k
return c
}
// AutoListKey 自动生成列表存储键
func (c *BaseCache) AutoListKey(table string) *BaseCache {
c.autoKey = true
c.table = table
c.dataType = dataTypeList
return c
}
// AutoInfoKey 自动生成单条数据键
func (c *BaseCache) AutoInfoKey(table, id string) *BaseCache {
c.autoKey = true
c.table = table
c.dataID = id
c.dataType = dataTypeInfo
return c
}
// ResBind 设置缓存对象
func (c *BaseCache) ResBind(o interface{}) *BaseCache {
c.object = o
return c
}
// CacheTime 设定缓存时间
func (c *BaseCache) CacheTime(t time.Duration) *BaseCache {
c.ttl = int64(t.Seconds())
c.random = true
return c
}
// CacheStair 阶梯式缓存
func (c *BaseCache) CacheStair(td time.Duration) *BaseCache {
second := int64(td.Seconds())
t := time.Now()
timeSecond := int64(math.Min(86400, math.Max(60, float64(second))))
zeroTime := time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location()).Unix()
over := t.Unix() - zeroTime
c.ttl = timeSecond - over%timeSecond
c.random = false
return c
}
// Refresh 刷新缓存
func (c *BaseCache) Refresh() *BaseCache {
c.refresh = true
return c
}
// Cache 缓存并返回[对象]数据
func (c *BaseCache) Cache(fn interface{}, p ...interface{}) (data interface{}, err error) {
// 操作完成后需要重置缓存对象
defer c.reset()
// 自动缓存key
if c.autoKey {
c.key, err = c.generalKey(false, p...)
if err != nil {
return
}
}
if c.key == "" {
return nil, fmt.Errorf("缓存键未设置")
}
var e error
if c.refresh {
_, _ = c.Delete(c.key)
}
// 获取缓存
ok, d := c.getCache(c.key)
// 存在缓在则进行反序列编码
if ok {
data, e = unSerialJson(d, c.object)
if e == nil {
//fmt.Println("data from cache")
return
}
c.LogError("unSerialJson error occur:", zap.Any("data", d), zap.Error(err))
}
// 不存在缓存则获取锁
lockKey := c.key + "_processing"
ok, e = c.driver.SetNX(lockKey, 1, 5*time.Second)
if !ok || e != nil {
//fmt.Println("未获取到锁,等待中:", lockKey)
i := 0
expire := false
var d string
for expire == false {
time.Sleep(200 * time.Millisecond)
// 重新尝试获取缓存
ok, d = c.getCache(c.key)
i++
expire = i >= 25 || ok
//fmt.Printf("第 %d 次尝试获取缓存,key: %s \n", i, lockKey)
}
if ok {
data, e = unSerialJson(d, c.object)
if e == nil {
//fmt.Println("data from cache2")
return
}
}
}
//fmt.Println("已获取到cache锁:", lockKey)
// 释放数据请求锁
defer func(driver Driver, key string) {
_, _ = driver.Del(key)
}(c.driver, lockKey)
// 请求原始数据
data, err = funcInvoke(fn, p...)
if err != nil && err.Error() != "record not found" {
c.LogError("acquire source data error occur:", zap.Error(err))
return nil, fmt.Errorf("获取原数据错误:%s", err.Error())
}
// 缓存原始数据后释放锁
d, e = serialJson(data)
if e == nil {
//fmt.Println("data form origin")
// object 设置
if c.object != nil {
_, e = unSerialJson(d, c.object)
if e != nil {
c.LogError("unmarshal data to object error occur:", zap.Error(e))
return data, fmt.Errorf("数据映射失败:%s", e.Error())
}
}
e = c.setCache(c.key, d)
if e != nil && c.logger != nil {
c.LogError("set cache data error occur:", zap.Any("data", data), zap.Error(e))
}
} else {
c.LogError("serialJson data error occur:", zap.Any("data", data), zap.Error(e))
}
return
}
// generalKey 自动生成缓存键
func (c *BaseCache) generalKey(forFuzzyClear bool, p ...interface{}) (string, error) {
var id, key string
keyStr, e := serialJson(p)
if e != nil {
return "", fmt.Errorf("此类参数不支持自动生成key")
}
switch c.dataType {
case dataTypeList:
if forFuzzyClear {
id = "*"
} else {
id = fmt.Sprintf("%x", md5.Sum([]byte(keyStr)))
}
key = fmt.Sprintf("table-%s:list-%s", c.table, id)
case dataTypeInfo:
if forFuzzyClear {
id = c.dataID + ":*"
} else {
id = fmt.Sprintf("%s:%x", c.dataID, md5.Sum([]byte(keyStr)))
}
key = fmt.Sprintf("table-%s:info-%s", c.table, id)
}
return key, nil
}
// getCache 获取缓存
func (c *BaseCache) getCache(key string) (ok bool, data string) {
if !c.driver.IsExist(key) {
return
}
var err error
res, err := c.driver.Get(key)
if err != nil {
c.LogError("get cache error:", zap.Any("cache key", key), zap.Error(err))
return
}
if res == nil {
return
}
return true, *res
}
// setCache 设置缓存
func (c *BaseCache) setCache(key string, data string) error {
if data == "" || data == "null" {
c.ttl = emptyDataCacheTime
} else if c.random {
if c.randomTTL == 0 {
rand.Seed(time.Now().UnixNano())
rnd := rand.Intn(30)
c.ttl += int64(rnd)
} else {
c.ttl += int64(c.randomTTL)
}
}
return c.driver.Set(key, data, time.Duration(c.ttl)*time.Second)
}
// Delete 清除指定缓存
func (c *BaseCache) Delete(key ...string) (int64, error) {
c.refresh = false
count, err := c.driver.Del(key...)
if err != nil {
c.LogError("delete cache key error", zap.Any("key", key), zap.Error(err))
return count, err
}
return count, nil
}
// DeleteCurrent 清除当前缓存
func (c *BaseCache) DeleteCurrent(p ...interface{}) (int64, error) {
var err error
if c.key == "" {
c.key, err = c.generalKey(false, p...)
if err != nil {
return 0, err
}
}
return c.Delete(c.key)
}
// FussyClear 模糊匹配删除
func (c *BaseCache) FussyClear(match string) (int64, error) {
go func() {
var err error
var keys []string
keys, err = c.driver.ScanKeys(match)
if err != nil {
c.LogError("scan cache key error", zap.Any("match", match), zap.Error(err))
return
}
// 分批删除
var batch = 10
for i := 0; i < len(keys); i += batch {
if i+batch >= len(keys) {
_, err = c.Delete(keys[i:]...)
} else {
_, err = c.Delete(keys[i : i+batch]...)
}
if err != nil {
return
}
}
}()
return 1, nil
}
// Table 设置表名
func (c *BaseCache) Table(table string) *BaseCache {
c.table = table
return c
}
// AutoClear 清理自动生成的列表/详情缓存
func (c *BaseCache) AutoClear(clearList bool, id *string) (count int64, err error) {
// 判断是否设置表
//if c.table == "" {
// return 0, fmt.Errorf("清理表缓存失败,未设置表名")
//}
//
//var tc int64
//// 先清理列表缓存
//if clearList {
// c.dataType = dataTypeList
// key, _ := c.generalKey(true)
// tc, err = c.FussyClear(key)
// count += tc
// if err != nil {
// return
// }
//}
//// 清理详情数据
//if id != nil {
// c.dataType = dataTypeInfo
// c.dataID = *id
// key, _ := c.generalKey(true)
// tc, err = c.FussyClear(key)
// count += tc
// if err != nil {
// return
// }
//}
return
}
// LogError 日志错误记录
func (c *BaseCache) LogError(msg string, fs ...zap.Field) {
if c.logger != nil {
dep := 0
t := make([]string, 0, 10)
for i := 1; i < 10; i++ {
_, file, line, ok := runtime.Caller(i)
if !ok {
break
}
if strings.Contains(file, "/runtime/") || strings.Contains(file, "/reflect/") {
continue
}
t = append(t, fmt.Sprintf("%s∟%s:%d", strings.Repeat(" ", dep), file, line))
dep++
}
exception := fmt.Sprintf("[MSG]%s\n[Stack]\n%s", msg, strings.Join(t, "\n"))
c.logger.Error(exception, fs...)
}
}
// reset 重置缓存
func (c *BaseCache) reset() {
c.ttl = defaultCacheTime
c.random = true
c.randomTTL = 0
c.refresh = false
c.key = ""
c.autoKey = false
c.table = ""
c.dataType = ""
c.dataID = ""
c.object = nil
}