应避免直接用c.defaultquery做类型转换,需先取值再校验转换;推荐用结构体绑定query参数并结合binding规则;分页优先游标分页,sql筛选用sqlx.in或builder动态构建。

Query 参数解析时别直接用 c.DefaultQuery 做类型转换
很多人一上来就写 c.DefaultQuery("page", "1") 然后 strconv.Atoi,结果遇到空字符串或非数字时 panic。Gin 的 DefaultQuery 和 Query 只返回 string,不校验格式,也不处理缺失/非法值。
正确做法是先取值,再统一校验和转换:
- 用
c.Query("page")获取原始值(返回空字符串表示参数未传) - 对空字符串、负数、超大数值做防御性检查,比如分页页码必须 ≥ 1
- 用
strconv.ParseInt(..., 10, 32)并检查 error,失败时给默认值或返回 400 - 避免在 handler 里反复调用
c.Query,建议一次性解析到结构体中
用结构体绑定 Query 参数比手写解析更安全也更易维护
Gin 支持用 c.ShouldBindQuery(&v) 把所有 Query 参数自动映射到 Go 结构体字段,还能结合 struct tag 控制默认值、验证规则和字段名映射。
例如这个结构体:
type ListOptions struct {
Page int `form:"page" binding:"required,min=1,default=1"`
PageSize int `form:"page_size" binding:"required,min=1,max=100,default=20"`
Status string `form:"status" binding:"omitempty,oneof=active inactive pending"`
Keyword string `form:"keyword" binding:"max=50"`
}
关键点:
-
formtag 指定 URL 参数名,和 JSON 的jsontag 分开管理 -
binding中的default是 gin-validator 的扩展行为,仅当参数完全未传时生效;若传了空字符串,omitempty才跳过校验 -
oneof能防止前端乱传 status 值,比 if-else 判断更清晰 - 注意:struct 字段必须是 exported(首字母大写),否则绑定失败且无提示
分页逻辑别在 SQL 层硬拼 offset,优先考虑游标分页或优化 limit+offset
用 LIMIT ? OFFSET ? 做分页在数据量大时会越来越慢,尤其当 OFFSET 达到几十万行后,MySQL 仍要扫描前面所有行。
简单项目可先用 offset,但要注意:
- 把
Page和PageSize转成offset = (page-1) * page_size,别漏减 1 - 加
MAX_PAGE_SIZE限制(如 100),防恶意请求拖垮数据库 - 查询总数时慎用
COUNT(*),可考虑缓存总条数,或改用估算(如EXPLAIN行数)
更优解是游标分页:用上一页最后一条记录的排序字段(如 created_at + id)作为下一页起点,避免偏移扫描。
条件筛选组合多时,别用 if-else 拼 SQL,用 sqlx.In 或表达式构建器
当支持 status、category、date_range 等多个可选条件时,手写 WHERE 拼接容易出 SQL 注入或语法错误(比如多加 or、少加括号)。
推荐方式:
- 用
sqlx.In处理IN (?)场景,它会自动展开切片并占位,例如:"WHERE status IN (?) AND created_at >= ?"+sqlx.In(statuses, createdAt) - 用
strings.Builder动态拼 WHERE 子句,每加一个条件前判断值是否有效(如非空字符串、非零时间),并统一管理AND连接 - 避免把用户输入直接塞进 SQL,所有参数都走问号占位符,哪怕只是字符串比较
复杂筛选最终生成的 SQL 应该干净可读,而不是靠注释才能看懂哪段对应哪个参数。
游标分页的 where 条件容易被忽略:它必须和排序字段严格一致,且不能混用 offset 分页的逻辑。一旦引入游标,page 参数就该废弃,否则两个分页策略打架。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











