@@ -7,8 +7,11 @@ import (
7
7
"gin-web/pkg/response"
8
8
"gin-web/pkg/utils"
9
9
"github.com/gin-gonic/gin"
10
+ "github.com/patrickmn/go-cache"
10
11
"gorm.io/gorm"
11
12
"reflect"
13
+ "strings"
14
+ "time"
12
15
)
13
16
14
17
type MysqlService struct {
@@ -26,6 +29,165 @@ func New(c *gin.Context) MysqlService {
26
29
}
27
30
}
28
31
32
+ var (
33
+ findByIdsCache = cache .New (24 * time .Hour , 48 * time .Hour )
34
+ findCountCache = cache .New (5 * time .Minute , 48 * time .Hour )
35
+ )
36
+
37
+ // 查询指定id, model需使用指针, 否则可能无法绑定数据
38
+ func (s * MysqlService ) FindById (id uint , model interface {}, setCache bool ) (err error ) {
39
+ return s .FindByKeys ("id" , id , model , setCache )
40
+ }
41
+
42
+ // 查询指定id列表, model需使用指针, 否则可能无法绑定数据
43
+ func (s * MysqlService ) FindByIds (ids []uint , model interface {}, setCache bool ) (err error ) {
44
+ return s .FindByKeys ("id" , ids , model , setCache )
45
+ }
46
+
47
+ // 查询指定key列表, model需使用指针, 否则可能无法绑定数据(如不使用cache可设置为false)
48
+ func (s * MysqlService ) FindByKeys (key string , ids interface {}, model interface {}, setCache bool ) (err error ) {
49
+ return s .FindByKeysWithPreload (key , nil , ids , model , setCache )
50
+ }
51
+
52
+ // 查询指定key列表, 并且preload其他表, model需使用指针, 否则可能无法绑定数据(如不使用cache可设置为false)
53
+ func (s * MysqlService ) FindByKeysWithPreload (key string , preloads []string , ids interface {}, model interface {}, setCache bool ) (err error ) {
54
+ var newIds interface {}
55
+ var firstId interface {}
56
+ // 判断ids是否数组
57
+ idsRv := reflect .ValueOf (ids )
58
+ idsRt := reflect .TypeOf (ids )
59
+ newIdsRv := reflect .ValueOf (newIds )
60
+ newIdsIsArr := false
61
+ if idsRv .Kind () == reflect .Ptr {
62
+ return fmt .Errorf ("ids不能为指针类型" )
63
+ }
64
+ // 获取model值
65
+ rv := reflect .ValueOf (model )
66
+ if rv .Kind () != reflect .Ptr || rv .IsNil () {
67
+ return fmt .Errorf ("model必须是非空指针类型" )
68
+ }
69
+ if key == "" {
70
+ key = "id"
71
+ }
72
+ // 处理参数是否与值类似匹配
73
+ if idsRv .Kind () == reflect .Slice {
74
+ if idsRv .Len () == 0 {
75
+ return
76
+ }
77
+ // 获取第一个元素
78
+ firstId = idsRv .Index (0 ).Convert (idsRt .Elem ()).Interface ()
79
+ if idsRv .Len () > 1 {
80
+ // 参数是数组, 值不是数组
81
+ if reflect .ValueOf (model ).Elem ().Kind () != reflect .Slice {
82
+ newIds = firstId
83
+ } else {
84
+ // 创建新数组, 避免数组引用传递在外层被修改
85
+ newArr := reflect .MakeSlice (reflect .TypeOf (ids ), idsRv .Len (), idsRv .Len ())
86
+ // 通过golang提供的拷贝方法
87
+ reflect .Copy (newArr , idsRv )
88
+ newIds = newArr .Interface ()
89
+ newIdsIsArr = true
90
+ }
91
+ } else {
92
+ // len=0, 将值重写
93
+ newIds = firstId
94
+ }
95
+ } else {
96
+ firstId = ids
97
+ newIds = ids
98
+ }
99
+ // 刷新反射值
100
+ newIdsRv = reflect .ValueOf (newIds )
101
+
102
+ // 可能一个条件有多个查询结果
103
+ if key != "id" && ! newIdsIsArr && newIdsRv .Kind () != reflect .Slice && rv .Elem ().Kind () == reflect .Slice {
104
+ newIdsIsArr = true
105
+ }
106
+ // ids是数组, 但model却不是数组
107
+ if newIdsIsArr && rv .Elem ().Kind () != reflect .Slice {
108
+ // ids取第一个值
109
+ newIds = firstId
110
+ }
111
+ cacheKey := ""
112
+ // 需要设置缓存
113
+ if setCache {
114
+ structName := ""
115
+ if rv .Elem ().Kind () == reflect .Slice {
116
+ structName = strings .ToLower (rv .Elem ().Type ().Elem ().String ())
117
+ } else {
118
+ structName = strings .ToLower (rv .Elem ().Type ().String ())
119
+ }
120
+ preload := "preload_nothing"
121
+ if len (preloads ) > 0 {
122
+ preload = "preload_" + strings .ToLower (strings .Join (preloads , "_" ))
123
+ }
124
+ // 缓存key组成: table+preloads+key+ids+modelIsArr
125
+ cacheKey = fmt .Sprintf ("%s_%s_%s_%s_find" , structName , preload , key , utils .Struct2Json (newIds ))
126
+ if rv .Elem ().Kind () != reflect .Slice {
127
+ cacheKey = fmt .Sprintf ("%s_%s_%s_%s_first" , structName , preload , key , utils .Struct2Json (newIds ))
128
+ }
129
+ oldCache , ok := findByIdsCache .Get (cacheKey )
130
+ if ok {
131
+ // 通过反射回写数据, 而不是直接赋值
132
+ // model = oldCache
133
+ crv := reflect .ValueOf (oldCache )
134
+ if rv .Elem ().Kind () == reflect .Struct && crv .Kind () == reflect .Slice {
135
+ rv .Elem ().Set (crv .Index (0 ))
136
+ } else if rv .Elem ().Kind () == reflect .Slice && crv .Kind () == reflect .Struct {
137
+ // 结构体写入数组第一个元素
138
+ newArr1 := reflect .MakeSlice (rv .Elem ().Type (), 1 , 1 )
139
+ v := newArr1 .Index (0 )
140
+ v .Set (crv )
141
+ // 创建新数组, 避免数组引用传递在外层被修改
142
+ newArr2 := reflect .MakeSlice (rv .Elem ().Type (), 1 , 1 )
143
+ reflect .Copy (newArr2 , newArr1 )
144
+ rv .Elem ().Set (newArr2 )
145
+ } else if rv .Elem ().Kind () == reflect .Slice && crv .Kind () == reflect .Slice {
146
+ // 创建新数组, 避免数组引用传递在外层被修改
147
+ newArr := reflect .MakeSlice (rv .Elem ().Type (), crv .Len (), crv .Len ())
148
+ // 通过golang提供的拷贝方法
149
+ reflect .Copy (newArr , crv )
150
+ rv .Elem ().Set (newArr )
151
+ } else {
152
+ rv .Elem ().Set (crv )
153
+ }
154
+ return
155
+ }
156
+ }
157
+ query := s .tx
158
+ for _ , preload := range preloads {
159
+ query = query .Preload (preload )
160
+ }
161
+ if ! newIdsIsArr {
162
+ err = query .
163
+ Where (fmt .Sprintf ("`%s` = ?" , key ), newIds ).
164
+ First (model ).Error
165
+ } else {
166
+ if newIdsIsArr && newIdsRv .Kind () != reflect .Slice {
167
+ // 可能一个条件有多个查询结果
168
+ err = query .
169
+ Where (fmt .Sprintf ("`%s` = ?" , key ), firstId ).
170
+ Find (model ).Error
171
+ } else {
172
+ err = query .
173
+ Where (fmt .Sprintf ("`%s` IN (?)" , key ), newIds ).
174
+ Find (model ).Error
175
+ }
176
+ }
177
+ if setCache {
178
+ if rv .Elem ().Kind () == reflect .Slice {
179
+ // 如果model是数组, 需创建新数组, 避免数组引用传递在外层被修改
180
+ newArr := reflect .MakeSlice (rv .Elem ().Type (), rv .Elem ().Len (), rv .Elem ().Len ())
181
+ reflect .Copy (newArr , rv .Elem ())
182
+ findByIdsCache .Set (cacheKey , newArr .Interface (), cache .DefaultExpiration )
183
+ } else {
184
+ // 写入缓存
185
+ findByIdsCache .Set (cacheKey , rv .Elem ().Interface (), cache .DefaultExpiration )
186
+ }
187
+ }
188
+ return
189
+ }
190
+
29
191
// 查询, model需使用指针, 否则可能无法绑定数据
30
192
func (s * MysqlService ) Find (query * gorm.DB , page * response.PageInfo , model interface {}) (err error ) {
31
193
// 获取model值
@@ -34,13 +196,63 @@ func (s *MysqlService) Find(query *gorm.DB, page *response.PageInfo, model inter
34
196
return fmt .Errorf ("model必须是非空指针数组类型" )
35
197
}
36
198
199
+ countCache := false
200
+ if page .CountCache != nil {
201
+ countCache = * page .CountCache
202
+ }
37
203
if ! page .NoPagination {
38
- // 查询条数
39
- err = query .Count (& page .Total ).Error
40
- if err == nil && page .Total > 0 {
204
+ if ! page .SkipCount {
205
+ // 查询条数
206
+ fromCache := false
207
+ // 以sql语句作为缓存键
208
+ stmt := query .Session (& gorm.Session {DryRun : true }).Count (& page .Total ).Statement
209
+ cacheKey := s .tx .Dialector .Explain (stmt .SQL .String (), stmt .Vars ... )
210
+ if countCache {
211
+ countCache , ok := findCountCache .Get (cacheKey )
212
+ if ok {
213
+ total , _ := countCache .(int64 )
214
+ page .Total = total
215
+ fromCache = true
216
+ }
217
+ }
218
+ if ! fromCache {
219
+ err = query .Count (& page .Total ).Error
220
+ if err == nil {
221
+ findCountCache .Set (cacheKey , page .Total , cache .DefaultExpiration )
222
+ }
223
+ } else {
224
+ global .Log .Debug (fmt .Sprintf ("条数缓存命中: %s, total: %d" , cacheKey , page .Total ))
225
+ }
226
+ }
227
+ if page .Total > 0 || page .SkipCount {
41
228
// 获取分页参数
42
229
limit , offset := page .GetLimit ()
43
- err = query .Limit (limit ).Offset (offset ).Find (model ).Error
230
+ if page .LimitPrimary == "" {
231
+ err = query .Limit (limit ).Offset (offset ).Find (model ).Error
232
+ } else {
233
+ // 解析model
234
+ if query .Statement .Model != nil {
235
+ err = query .Statement .Parse (query .Statement .Model )
236
+ if err != nil {
237
+ return
238
+ }
239
+ }
240
+ err = query .Joins (
241
+ // 通过索引先分页再获取join其他字段, 以提高查询效率
242
+ fmt .Sprintf (
243
+ "JOIN (?) AS `OFFSET_T` ON `%s`.`id` = `OFFSET_T`.`%s`" ,
244
+ query .Statement .Table ,
245
+ page .LimitPrimary ,
246
+ ),
247
+ query .
248
+ Session (& gorm.Session {}).
249
+ Select (
250
+ fmt .Sprintf ("`%s`.`%s`" , query .Statement .Table , page .LimitPrimary ),
251
+ ).
252
+ Limit (limit ).
253
+ Offset (offset ),
254
+ ).Find (model ).Error
255
+ }
44
256
}
45
257
} else {
46
258
// 不使用分页
0 commit comments