|
|
|
@@ -0,0 +1,445 @@
|
|
|
|
|
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
|
|
|
|
|
}
|