shanshanzhong c582087c0f
Some checks failed
Build docker and publish / build (20.15.1) (push) Failing after 6m27s
refactor: 更新项目引用路径从perfect-panel/ppanel-server到perfect-panel/server
feat: 添加版本和构建时间变量
fix: 修正短信队列类型注释错误
style: 清理未使用的代码和测试文件
docs: 更新安装文档中的下载链接
chore: 迁移数据库脚本添加日志和订阅配置
2025-10-13 01:33:03 -07:00

130 lines
3.6 KiB
Go

package cache
import (
"context"
"database/sql"
"encoding/json"
"errors"
"time"
"github.com/redis/go-redis/v9"
"gorm.io/gorm"
)
var (
// ErrNotFound is the error when cache not found.
ErrNotFound = redis.Nil
)
type (
// ExecCtxFn defines the sql exec method.
ExecCtxFn func(conn *gorm.DB) error
// IndexQueryCtxFn defines the query method that based on unique indexes.
IndexQueryCtxFn func(conn *gorm.DB, v interface{}) (interface{}, error)
// PrimaryQueryCtxFn defines the query method that based on primary keys.
PrimaryQueryCtxFn func(conn *gorm.DB, v, primary interface{}) error
// QueryCtxFn defines the query method.
QueryCtxFn func(conn *gorm.DB, v interface{}) error
CachedConn struct {
db *gorm.DB
cache *redis.Client
expiry time.Duration
notFoundExpiry time.Duration
}
)
// NewConn returns a CachedConn with a redis cluster cache.
func NewConn(db *gorm.DB, c *redis.Client, opts ...Option) CachedConn {
o := newOptions(opts...)
return CachedConn{
db: db,
cache: c,
expiry: o.Expiry,
notFoundExpiry: o.NotFoundExpiry,
}
}
// DelCache deletes cache with keys.
func (cc CachedConn) DelCache(keys ...string) error {
return cc.cache.Del(context.Background(), keys...).Err()
}
// DelCacheCtx deletes cache with keys.
func (cc CachedConn) DelCacheCtx(ctx context.Context, keys ...string) error {
return cc.cache.Del(ctx, keys...).Err()
}
// GetCache unmarshals cache with given key into v.
func (cc CachedConn) GetCache(key string, v interface{}) error {
// query redis key
val, err := cc.cache.Get(context.Background(), key).Result()
if err != nil {
return err
}
// unmarshal value
return json.Unmarshal([]byte(val), v)
}
// SetCache sets cache with key and v.
func (cc CachedConn) SetCache(key string, v interface{}) error {
// marshal value
val, err := json.Marshal(v)
if err != nil {
return err
}
// set redis key
return cc.cache.Set(context.Background(), key, val, cc.expiry).Err()
}
// ExecCtx runs given exec on given keys, and returns execution result.
func (cc CachedConn) ExecCtx(ctx context.Context, execCtx ExecCtxFn, keys ...string) error {
err := execCtx(cc.db.WithContext(ctx))
if err != nil {
return err
}
if err := cc.DelCacheCtx(ctx, keys...); err != nil {
return err
}
return nil
}
// ExecNoCache runs exec with given sql statement, without affecting cache.
func (cc CachedConn) ExecNoCache(exec ExecCtxFn) error {
return cc.ExecNoCacheCtx(context.Background(), exec)
}
// ExecNoCacheCtx runs exec with given sql statement, without affecting cache.
func (cc CachedConn) ExecNoCacheCtx(ctx context.Context, execCtx ExecCtxFn) (err error) {
return execCtx(cc.db.WithContext(ctx))
}
func (cc CachedConn) QueryCtx(ctx context.Context, v interface{}, key string, query QueryCtxFn) (err error) {
err = cc.GetCache(key, v)
if err != nil {
if errors.Is(err, ErrNotFound) {
err = query(cc.db.WithContext(ctx), v)
if err != nil {
return err
}
return cc.SetCache(key, v)
}
}
return
}
// QueryNoCacheCtx runs query with given sql statement, without affecting cache.
func (cc CachedConn) QueryNoCacheCtx(ctx context.Context, v interface{}, query QueryCtxFn) (err error) {
return query(cc.db.WithContext(ctx), v)
}
// TransactCtx runs given fn in transaction mode.
func (cc CachedConn) TransactCtx(ctx context.Context, fn func(db *gorm.DB) error, opts ...*sql.TxOptions) error {
return cc.db.WithContext(ctx).Transaction(fn, opts...)
}
// Transact runs given fn in transaction mode.
func (cc CachedConn) Transact(fn func(db *gorm.DB) error, opts ...*sql.TxOptions) error {
return cc.TransactCtx(context.Background(), fn, opts...)
}