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

4.3 KiB
Raw Blame History

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 手写参数判断

下一步

→ 3.9 权限节点配置