列表接口一般都含有一些查询参数queryString
,用于过滤数据。且这些查询可能传可能不传,如果每个接口手动去处理的话,比较麻烦,样板代码比较多,可以考虑使用反射,获取Query结构体的tag做自动的数据库查询过滤…
gin.Context::Query()
列表接口一般都含有一些查询参数queryString
,用于过滤数据。且这些查询可能传可能不传,如果每个接口手动去处理的话,比较麻烦,
样板代码比较多, 例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
func Index(c *gin.Context) {
var query ListQuery
c.ShouldBindQuery(&query)
// ....................
}
// 然后在model中,
func List(query ListQuery) YourStruct {
if query.Username != "" {
db.Where("username like ?", query.Username + "%")
}
// if ....
}
|
可以考虑使用反射,获取Query结构体的tag做自动的数据库查询过滤。以gin+gorm为例。
Query实体
1
2
3
4
5
6
7
8
9
10
11
12
13
|
// xxxQuery.go
type ListQuery struct {
Id uint `form:"id" table:"coin_user"`
Mobile string `form:"mobile" like:"" table:"coin_user"`
Username string `form:"username" table:"coin_user" like:""`
TrueName string `form:"trueName" table:"coin_user" like:""`
Email string `form:"email" table:"coin_user" like:""`
AuthStatus *AuthStatus `form:"authStatus" table:"coin_user" field:"isauth"`
Status *bool `form:"status" table:"coin_user"`
IsAgent *bool `form:"isAgent" table:"coin_lottery_user"`
VipLv *int8 `form:"vipLv" table:"coin_lottery_user"`
IgnoreIt string `form:"ignoreIt" ignore:""`
}
|
以上为查询参数的结构体,tag中form
是gin的tag,用于绑定queryString到结构体上。
like
,table
,field
, ignore
是自己加的tag, like
用于指定此字段用于模糊查询。
table
用于指定表名,用于连表查询的时候需要指定字段所属的表名.
field
用于指定数据库字段名,因为结构体的字段名可能与数据库的字段名不一致。
ignore
用于忽略这个字段的查询
Controller
1
2
3
4
5
6
7
8
|
// xxxController.go
func Index(c *gin.Context) {
var query ListQuery
if err := c.ShouldBindQuery(&query); err != nil {
// 错误处理...
}
// ...
}
|
在控制器中绑定值到结构体上
QueryBuilder
通过反射获取结构体的tag,解析之后,添加到数据库的查询条件中去
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
|
/// @param query 各种Query结构体的实例
/// @param db gorm的数据库连接
func GetQueryBuilder(query interface{}, db *gorm.DB) *gorm.DB {
v := reflect.ValueOf(query)
t := reflect.TypeOf(query)
fieldCount := v.NumField()
for i := 0; i < fieldCount; i++ { // 循环结构体的每一个字段
f := v.Field(i)
tf := t.Field(i)
name := tf.Name
// 分页参数和where查询条件无关,过滤掉
if name == "PageStruct" || name == "Page" || name == "PageSize" {
continue
}
// 如果有ignore tag过滤
if _, ok := tf.Tag.Lookup("ignore"); ok {
continue
}
// 如果有指定的字段名,就使用指定的字段名,否则使用结构体的字段名的蛇形形式
if tv, ok := tf.Tag.Lookup("field"); ok {
name = tv
} else {
name = Camel2Snake(name)
}
// 如果此字段是可以导出的,且不为Value零值
if f.CanInterface() && !f.IsZero() {
var value interface{} // 用于接收字段的值
// 搜寻tag, 如果有table这个tag,key加上table名字
if tv, ok := tf.Tag.Lookup("table"); ok {
name = fmt.Sprintf("%s.%s", tv, name)
}
// 如果有like这个tag, 执行模糊查询
if _, ok := tf.Tag.Lookup("like"); ok {
var tv string;
if f.Kind() == reflect.Ptr {
if f.Elem().IsZero() {
// like查询应该都是字符串,字符串要零值过滤掉
continue
}
tv = f.Elem().String()
} else {
tv = f.String()
}
db = db.Where(fmt.Sprintf("%s like ?", name), tv + "%")
continue
}
// 这儿可以判断 f.Kind() == reflect.Struct,是否是嵌套结构体, 然后做递归处理
value = f.Interface()
db = db.Where(fmt.Sprintf("%s = ?", name), value)
}
}
return db
}
func Camel2Snake(name string) string {
buffer := new(bytes.Buffer)
for i, r := range name {
if unicode.IsUpper(r) {
if i != 0 {
buffer.WriteRune('_')
}
buffer.WriteRune(unicode.ToLower(r))
} else {
buffer.WriteRune(r)
}
}
return buffer.String()
}
|
Last
在Model
中或者叫Service
中,调用这个函数GetQueryBuilder
获取到含有查询条件的gorm.DB指针,查询….
由于暂时没遇到查询结构体是嵌套结构体的情况,不搞这个,后面遇到了再加上去。
也可以加上自己定义的其他tag做更多的查询条件添加, 例如between等,多写几行就完事。
最后还可以考虑反射的缓存,不过暂时不需要,先这样用着