4.3 KiB
4.3 KiB
3.8 参数校验与错误上抛
:::tip 概述 校验在契约层用标签声明,错误在业务层用哨兵错误上抛——两头都不写手工 if。本页说明绑定优先级、校验标签用法与错误传播路径。 :::
绑定优先级
请求参数按固定顺序写入,后写覆盖:
query(GET 参数) → body(JSON 体) → path(路径参数)
最终优先级表现为 path > body > query:同名字段以路径参数为准。每个字段同时带 json(body)与 form(query)标签即可参与两种来源绑定。
绑定引擎的入口在 router.Bind 包装函数内,handler 拿到的 req 已完成解析与校验。
binding / msg 标签
校验读标准 binding 标签,错误消息取 msg 标签:
// internal/model/dto/example.go
type ExampleDetailReq struct {
Id int64 `json:"id" binding:"required" msg:"缺少ID" dc:"记录ID"`
}
type ExampleBatchDeleteReq struct {
Ids []int64 `json:"ids" binding:"min=1" msg:"请选择要删除的记录" dc:"ID列表"`
}
// internal/model/dto/user.go(多规则 + 多消息示例)
type UserAddReq struct {
Username string `json:"username" binding:"required,min=2,max=30" msg:"请输入用户名;用户名长度需在2~30位"`
Password string `json:"password" binding:"required,min=6,max=32" msg:"请输入密码;密码长度需在6~32位"`
Mobile string `json:"mobile" binding:"omitempty,phone" msg:"手机号格式不正确"`
Email string `json:"email" binding:"omitempty,email" msg:"邮箱格式不正确"`
}
常用规则:
| 标签 | 含义 | 示例 |
|---|---|---|
| required | 必填(零值也算缺失) | binding:"required" |
| min / max | 数值/长度边界 | binding:"min=1" msg:"请选择记录" |
| oneof | 枚举取值 | binding:"oneof=1 2" |
| gte / lte | 大于等于 / 小于等于 | binding:"gte=1,lte=500" |
| omitempty | 空值放行(配合格式规则) | binding:"omitempty,email" |
| phone / email | 内置格式规则(本项目 validator 扩展) | binding:"omitempty,phone" |
多消息映射(utility/validator):msg 以 ; 分段与 binding 多规则一一对应(如 required,min=2,max=30 ↔ 请输入用户名;用户名长度需在2~30位);只写一段时适用于全部规则;分段缺失按规则默认提示兜底。校验快速失败:返回第一条错误,消息含字段提示语。内嵌结构体(PageReq)由引擎递归校验。
校验失败时框架直接以统一信封返回,msg 即上述文案——不需要 handler 做任何判断。
UnmarshalBody 逃生舱
文件上传等非 JSON 载荷,结构体实现 UnmarshalBody(c *gin.Context) error 即可接管解析:
// internal/model/dto/example.go
func (r *ExampleImportReq) UnmarshalBody(c *gin.Context) error {
f, err := c.FormFile("file")
if err != nil {
return errors.New("请选择文件")
}
r.File = f
return nil
}
返回 error 即校验失败,同样走统一错误信封。
业务错误上抛
service / 钩子内业务失败一律返回 apperr 错误或哨兵错误,原样上抛,不做包装:
// internal/service/auth.go(实际代码)
return nil, consts.CodeCaptchaError
return nil, consts.CodeUserDisabled
// internal/service/category.go(实际代码,格式化消息)
return apperr.Newf(consts.CodeParamError.Code(), "上级不能选择自己")
response 中间件捕获 error 后:
apperr 错误 → 按其 code/msg 直接输出
普通 error → 映射为通用失败码,msg 取错误文本
panic → recovery 捕获,输出 500 信封并打错误栈日志
错误处理禁则
| 禁则 | 原因 |
|---|---|
| 在 handler 转发 HTTP 状态码 | 统一信封由 response 中间件负责 |
| 吞掉错误只记日志 | 上层与前端失去失败感知 |
| 包装后重复加前缀 | 钩子与 service 禁止 wrap,错误原样上抛 |
| 用 error 做流程控制 | 分支结果用返回值表达,error 只表示失败 |
编写检查清单
- 必填字段有 required + msg
- 零值合法的字段不加 required(等值过滤靠 EqFields 零值跳过)
- 文件字段实现 UnmarshalBody
- 业务失败返回哨兵/apperr,不 fmt.Errorf 包装
- 不在 handler 手写参数判断