-
Notifications
You must be signed in to change notification settings - Fork 12
/
cache.go
200 lines (158 loc) · 3.64 KB
/
cache.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
package cache
import (
"context"
"hash/fnv"
"os"
"strconv"
"time"
"gorm.io/gorm"
"gorm.io/gorm/callbacks"
)
type Config struct {
Store Store
Prefix string
Serializer Serializer
}
type (
Serializer interface {
Serialize(v any) ([]byte, error)
Deserialize(data []byte, v any) error
}
Store interface {
// Set 写入缓存数据
Set(ctx context.Context, key string, value any, ttl time.Duration) error
// Get 获取缓存数据
Get(ctx context.Context, key string) ([]byte, error)
// SaveTagKey 将缓存key写入tag
SaveTagKey(ctx context.Context, tag, key string) error
// RemoveFromTag 根据缓存tag删除缓存
RemoveFromTag(ctx context.Context, tag string) error
}
)
type Cache struct {
store Store
// Serializer 序列化
Serializer Serializer
// prefix 缓存前缀
prefix string
}
// New
// @param conf
// @date 2022-07-02 08:09:52
func New(conf *Config) *Cache {
if conf.Store == nil {
os.Exit(1)
}
if conf.Serializer == nil {
conf.Serializer = &DefaultJSONSerializer{}
}
return &Cache{
store: conf.Store,
prefix: conf.Prefix,
Serializer: conf.Serializer,
}
}
// Name
// @date 2022-07-02 08:09:48
func (p *Cache) Name() string {
return "gorm:cache"
}
// Initialize
// @param tx
// @date 2022-07-02 08:09:47
func (p *Cache) Initialize(tx *gorm.DB) error {
return tx.Callback().Query().Replace("gorm:query", p.Query)
}
// generateKey
// @param key
// @date 2022-07-02 08:09:46
func generateKey(key string) string {
hash := fnv.New64a()
_, _ = hash.Write([]byte(key))
return strconv.FormatUint(hash.Sum64(), 36)
}
// Query
// @param tx
// @date 2022-07-02 08:09:38
func (p *Cache) Query(tx *gorm.DB) {
ctx := tx.Statement.Context
var ttl time.Duration
var hasTTL bool
if ttl, hasTTL = FromExpiration(ctx); !hasTTL {
callbacks.Query(tx)
return
}
var (
key string
hasKey bool
)
// 调用 Gorm的方法生产SQL
callbacks.BuildQuerySQL(tx)
// 是否有自定义key
if key, hasKey = FromKey(ctx); !hasKey {
key = p.prefix + generateKey(tx.Statement.SQL.String())
}
// 查询缓存数据
if err := p.QueryCache(ctx, key, tx.Statement.Dest); err == nil {
return
}
// 查询数据库
p.QueryDB(tx)
if tx.Error != nil {
return
}
// 写入缓存
if err := p.SaveCache(ctx, key, tx.Statement.Dest, ttl); err != nil {
tx.Logger.Error(ctx, err.Error())
return
}
if tag, hasTag := FromTag(ctx); hasTag {
_ = p.store.SaveTagKey(ctx, tag, key)
}
}
// QueryDB 查询数据库数据
// 这里重写Query方法 是不想执行 callbacks.BuildQuerySQL 两遍
func (p *Cache) QueryDB(tx *gorm.DB) {
if tx.Error != nil || tx.DryRun {
return
}
rows, err := tx.Statement.ConnPool.QueryContext(tx.Statement.Context, tx.Statement.SQL.String(), tx.Statement.Vars...)
if err != nil {
_ = tx.AddError(err)
return
}
defer func() {
_ = tx.AddError(rows.Close())
}()
gorm.Scan(rows, tx, 0)
}
// QueryCache 查询缓存数据
// @param ctx
// @param key
// @param dest
func (p *Cache) QueryCache(ctx context.Context, key string, dest any) error {
values, err := p.store.Get(ctx, key)
if err != nil {
return err
}
switch dest.(type) {
case *int64:
dest = 0
}
return p.Serializer.Deserialize(values, dest)
}
// SaveCache 写入缓存数据
func (p *Cache) SaveCache(ctx context.Context, key string, dest any, ttl time.Duration) error {
values, err := p.Serializer.Serialize(dest)
if err != nil {
return err
}
return p.store.Set(ctx, key, values, ttl)
}
// RemoveFromTag 根据tag删除缓存数据
// @param ctx
// @param tag
// @date 2022-07-02 08:08:59
func (p *Cache) RemoveFromTag(ctx context.Context, tag string) error {
return p.store.RemoveFromTag(ctx, tag)
}