revel 的 validation 不支持 struct tag 或函数式自定义验证器,仅提供硬编码的 required()、maxsize() 等方法,校验需在 controller 中显式调用并手动处理错误。

Revel 框架的 Validation 不支持像 express-validator 或 class-validator 那样直接注册函数式自定义验证器;它的扩展方式是「在已有验证方法基础上组合逻辑 + 手动调用 Validation.Error()」,本质是命令式、非声明式的校验流程。
为什么不能直接写 validate: "custom_email" 这类 tag
Revel 的 Validation 是轻量级上下文收集器,不是结构体反射驱动的校验引擎。它没有解析 struct tag 的机制,也不内置 validator 注册表。所有校验必须显式调用 c.Validation.Xxx() 方法,比如 Required()、MaxSize(),而这些方法本身是硬编码实现的,不开放扩展入口。
常见误区是试图模仿 Gin 或 Go-Playground 的 tag 风格,在 Revel 里写 binding:"email" 或自定义 tag——这完全无效,Revel 根本不读取或处理这类标签。
- Revel 的校验逻辑全部落在 controller 方法体内,靠 if/else +
c.Validation.Error()控制流程 - 没有中间件式统一校验管道,也没有自动绑定 + 自动报错机制
- 字段名(如
username)会自动作为错误 key 存入Validation上下文,但这个 key 是字符串字面量,不是从 struct 字段反射来的
手动封装可复用的校验逻辑(推荐做法)
虽然不能注册新方法,但你可以把重复校验逻辑抽成普通函数,接收 *revel.Validation 和值,内部调用 .Error():
func ValidateEmail(v *revel.Validation, email string) {
if email == "" {
v.Error("邮箱不能为空", "email")
return
}
if !strings.Contains(email, "@") {
v.Error("邮箱格式不合法", "email")
return
}
// 可继续加 DB 查询:v.Error("邮箱已被注册", "email")
}
// 在 controller 中使用:
func (c App) Register(username, email string) revel.Result {
c.Validation.Required(username).Message("用户名必填")
ValidateEmail(c.Validation, email)
if c.Validation.HasErrors() {
c.Validation.Keep()
c.FlashParams()
return c.Redirect(App.RegisterForm)
}
// ...
}
注意两点:
- 第二个参数
"email"是错误 key,模板中通过.errors.email.Message访问,必须和前端 input 的 name 一致 - 不要返回 error 或抛 panic,Revel 依赖
HasErrors()判断状态,其他方式无法被Keep()和模板识别
跨字段校验只能手写 if 判断
比如“确认密码必须等于密码”,Revel 没有 eqfield 或 custom 回调机制,只能在 controller 里直接比较:
func (c App) Register(password, passwordConfirm string) revel.Result {
if password != passwordConfirm {
c.Validation.Error("两次输入的密码不一致", "passwordConfirm")
}
// 其他校验...
if c.Validation.HasErrors() {
c.Validation.Keep()
c.FlashParams()
return c.Redirect(App.RegisterForm)
}
}
关键点:
- 错误 key 必须是表单字段名(如
passwordConfirm),否则模板找不到对应错误 - 不能依赖结构体字段反射,所有值都来自
c.Params.Get("xxx")或路由参数,需自己提取并传入 - 异步校验(如查库判断用户名是否可用)必须同步完成——Revel 的
Validation不支持 Promise 或 await,DB 查询得用同步驱动(如db.QueryRow().Scan())
真正容易被忽略的是:Revel 的 Validation 错误上下文在每次请求生命周期内只存在一次,且 Keep() 仅把错误序列化进 Flash cookie,下次请求才能读到。如果你在同一个请求里多次调用 Redirect(),或者忘记 Keep(),错误就丢了——这不是 bug,是设计使然。











