Files
easygoadmin-wiki/EasyGoAdmin_Gin_AntdVue/zh/guide/validation.md
T
2026-10-25 17:24:27 +08:00

116 lines
4.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 3.8 参数校验与错误上抛
:::tip 概述
校验在契约层用标签声明,错误在业务层用哨兵错误上抛——两头都不写手工 if。本页说明绑定优先级、校验标签用法与错误传播路径。
:::
## 绑定优先级
请求参数按固定顺序写入,**后写覆盖**:
```text
query(GET 参数) → body(JSON 体) → path(路径参数)
```
最终优先级表现为 **path > body > query**:同名字段以路径参数为准。每个字段同时带 `json`(body)与 `form`(query)标签即可参与两种来源绑定。
绑定引擎的入口在 `router.Bind` 包装函数内,handler 拿到的 req 已完成解析与校验。
## binding / msg 标签
校验读标准 `binding` 标签,错误消息取 `msg` 标签:
```go
// 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` 即可接管解析:
```go
// 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 错误或哨兵错误,**原样上抛,不做包装**:
```go
// internal/service/auth.go(实际代码)
return nil, consts.CodeCaptchaError
return nil, consts.CodeUserDisabled
// internal/service/category.go(实际代码,格式化消息)
return apperr.Newf(consts.CodeParamError.Code(), "上级不能选择自己")
```
response 中间件捕获 error 后:
```text
apperr 错误 → 按其 code/msg 直接输出
普通 error → 映射为通用失败码,msg 取错误文本
panic → recovery 捕获,输出 500 信封并打错误栈日志
```
## 错误处理禁则
| 禁则 | 原因 |
|---|---|
| 在 handler 转发 HTTP 状态码 | 统一信封由 response 中间件负责 |
| 吞掉错误只记日志 | 上层与前端失去失败感知 |
| 包装后重复加前缀 | 钩子与 service 禁止 wrap,错误原样上抛 |
| 用 error 做流程控制 | 分支结果用返回值表达,error 只表示失败 |
## 编写检查清单
- [ ] 必填字段有 required + msg
- [ ] 零值合法的字段不加 required(等值过滤靠 EqFields 零值跳过)
- [ ] 文件字段实现 UnmarshalBody
- [ ] 业务失败返回哨兵/apperr,不 fmt.Errorf 包装
- [ ] 不在 handler 手写参数判断
## 下一步
→ [3.9 权限节点配置](/zh/guide/permission.html)