Featured image of post gin过滤参数查询

gin过滤参数查询

列表接口一般都含有一些查询参数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等,多写几行就完事。
最后还可以考虑反射的缓存,不过暂时不需要,先这样用着

Licensed under CC BY-NC-SA 4.0
comments powered by Disqus