列表接口一般都含有一些查询参数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 ....
}
Copy 可以考虑使用反射,获取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:""`
}
Copy
以上为查询参数的结构体,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 {
// 错误处理...
}
// ...
}
Copy 在控制器中绑定值到结构体上
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 ()
}
Copy Last
在Model
中或者叫Service
中,调用这个函数GetQueryBuilder
获取到含有查询条件的gorm.DB指针,查询….
由于暂时没遇到查询结构体是嵌套结构体的情况,不搞这个,后面遇到了再加上去。
也可以加上自己定义的其他tag做更多的查询条件添加, 例如between等,多写几行就完事。
最后还可以考虑反射的缓存,不过暂时不需要,先这样用着