主题
15 · 参数校验 Validator
目标:用结构体 tag 声明约束,拒绝脏数据进入业务层。
前置:12 · Gin · 14 · Zap
文档:go-playground/validator · Gin 已集成binding
对照:Joi / Zod / class-validator
1. 为何在框架层校验
| 只靠前端 | 危险 |
|---|---|
| 小程序可改请求 | 必须服务端再验 |
手工 if content == "" | 易漏、难统一错误格式 |
Gin:binding:"required,min=1,max=2000" + ShouldBindJSON。
2. 最小可跑
go
type CreateNoteReq struct {
Content string `json:"content" binding:"required,min=1,max=2000"`
Tags []string `json:"tags" binding:"omitempty,max=20,dive,min=1,max=32"`
}
func createNote(c *gin.Context) {
var req CreateNoteReq
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": "validation_failed",
"details": err.Error(), // 生产可转成字段级中文
})
return
}
// 进入 service
c.JSON(http.StatusCreated, gin.H{"content": req.Content})
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
常用 tag:required、email、gte/lte、oneof=a b、uuid、url。
Query / URI:
go
type ListQuery struct {
Page int `form:"page" binding:"omitempty,gte=1"`
Size int `form:"size" binding:"omitempty,gte=1,lte=50"`
}
// c.ShouldBindQuery(&q)1
2
3
4
5
2
3
4
5
3. 统一错误体(建议)
json
{
"error": "validation_failed",
"fields": [{"field": "content", "msg": "required"}]
}1
2
3
4
2
3
4
入门可用 err.Error();串烧项目(18)再做成字段映射。
4. 动手清单
- [ ] Create/Update DTO 全部带 binding
- [ ] 超长 content 返回 400,而不是写入 DB
- [ ] 列表 page/size 设上限,防一次拉爆
- [ ] 与 Zap:校验失败打 Warn,不打 Error 刷屏
5. 项目驱动
| 场景 | 规则 |
|---|---|
| 注册 | email + 密码长度 |
| 手帐正文 | max 与产品一致 |
| 标签 | dive 限制单标签长度与个数 |
6. 常见坑 + AI 审查
| 坑 | 说明 |
|---|---|
binding 与 validate 两套混用不懂 | Gin 默认走 validator.v10 |
校验通过仍信客户端 userId | 身份以 JWT 为准(16) |
| AI 发明不存在的 tag | 查 validator 文档 tag 列表 |
