Skip to content

Commit 3409b29

Browse files
committed
extend service func
1 parent e6ef160 commit 3409b29

File tree

2 files changed

+244
-17
lines changed

2 files changed

+244
-17
lines changed

pkg/response/resp.go

+28-13
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,20 @@ import (
88

99
// http请求响应封装
1010
type Resp struct {
11-
Code int `json:"code"` // 错误代码代码
11+
Code int `json:"code"` // 错误代码
1212
Data interface{} `json:"data"` // 数据内容
1313
Msg string `json:"msg"` // 消息提示
1414
}
1515

1616
// 分页封装
1717
type PageInfo struct {
18-
PageNum uint `json:"pageNum" form:"pageNum"` // 当前页码
19-
PageSize uint `json:"pageSize" form:"pageSize"` // 每页显示条数
20-
Total int64 `json:"total"` // 数据总条数(gorm v2 Count方法参数从interface改为int64, 这里也需要相应改变)
21-
NoPagination bool `json:"noPagination" form:"noPagination"` // 不使用分页
18+
PageNum uint `json:"pageNum" form:"pageNum"` // 当前页码
19+
PageSize uint `json:"pageSize" form:"pageSize"` // 每页显示条数
20+
Total int64 `json:"total"` // 数据总条数(gorm v2 Count方法参数从interface改为int64, 这里也需要相应改变)
21+
NoPagination bool `json:"noPagination" form:"noPagination"` // 不使用分页
22+
CountCache *bool `json:"countCache" form:"countCache"` // 缓存总条数
23+
SkipCount bool `json:"skipCount" form:"skipCount"` // 跳过条数查询
24+
LimitPrimary string `json:"-"` // 当数据量很大时, limit通过指定字段(该字段一般是自增id或有索引)来优化, 可提高查询效率(如果不传则不优化)
2225
}
2326

2427
// 带分页数据封装
@@ -53,11 +56,6 @@ func (s *PageInfo) GetLimit() (int, int) {
5356
pageNum = int64(s.PageNum)
5457
}
5558

56-
// 如果偏移量比总条数还多
57-
if total > 0 && pageNum > total {
58-
pageNum = total
59-
}
60-
6159
// 计算最大页码
6260
maxPageNum := total/pageSize + 1
6361
if total%pageSize == 0 {
@@ -67,14 +65,23 @@ func (s *PageInfo) GetLimit() (int, int) {
6765
if maxPageNum < 1 {
6866
maxPageNum = 1
6967
}
70-
71-
// 超出最后一页
72-
if pageNum > maxPageNum {
68+
// 如果偏移量比总条数还多
69+
if total > 0 && pageNum > total {
7370
pageNum = maxPageNum
7471
}
7572

7673
limit := pageSize
7774
offset := limit * (pageNum - 1)
75+
// 页码小于1设置为第1页数据
76+
if s.PageNum < 1 {
77+
offset = 0
78+
}
79+
80+
// 超出最后一页设置为空数据
81+
if int64(s.PageNum) > maxPageNum {
82+
pageNum = maxPageNum + 1
83+
offset = limit * maxPageNum
84+
}
7885

7986
s.PageNum = uint(pageNum)
8087
s.PageSize = uint(pageSize)
@@ -135,6 +142,14 @@ func FailWithCode(code int) {
135142
Result(code, msg, map[string]interface{}{})
136143
}
137144

145+
func FailWithCodeAndMsg(code int, msg string) {
146+
// 查找给定的错误码存在对应的错误信息, 默认使用NotOk
147+
if msg == "" {
148+
msg = CustomError[NotOk]
149+
}
150+
Result(code, msg, map[string]interface{}{})
151+
}
152+
138153
func GetFailWithCode(code int) Resp {
139154
// 查找给定的错误码存在对应的错误信息, 默认使用NotOk
140155
msg := CustomError[NotOk]

pkg/service/service.go

+216-4
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@ import (
77
"gin-web/pkg/response"
88
"gin-web/pkg/utils"
99
"github.com/gin-gonic/gin"
10+
"github.com/patrickmn/go-cache"
1011
"gorm.io/gorm"
1112
"reflect"
13+
"strings"
14+
"time"
1215
)
1316

1417
type MysqlService struct {
@@ -26,6 +29,165 @@ func New(c *gin.Context) MysqlService {
2629
}
2730
}
2831

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+
29191
// 查询, model需使用指针, 否则可能无法绑定数据
30192
func (s *MysqlService) Find(query *gorm.DB, page *response.PageInfo, model interface{}) (err error) {
31193
// 获取model值
@@ -34,13 +196,63 @@ func (s *MysqlService) Find(query *gorm.DB, page *response.PageInfo, model inter
34196
return fmt.Errorf("model必须是非空指针数组类型")
35197
}
36198

199+
countCache := false
200+
if page.CountCache != nil {
201+
countCache = *page.CountCache
202+
}
37203
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 {
41228
// 获取分页参数
42229
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+
}
44256
}
45257
} else {
46258
// 不使用分页

0 commit comments

Comments
 (0)