发布于2026-06-30 阅读(0)
扫一扫,手机访问
可扩展校验的核心在于解耦:校验函数只接收原始数据,返回错误列表;每条业务规则封装成独立子函数。用 Functional Options 构造 Validator 来注入上下文,避免掉进 struct tag 校验器的坑;路径和查询参数必须手动转类型再校验。这套设计的目标是:改一个条件不用动整个函数,加一个场景不用重新造轮子。

想象一下,把校验规则硬塞进 handler 里,或者写一个巨无霸 Validate() 函数——后续要加一条“支付金额不得超过用户余额”,就得改函数签名、改逻辑、补测试。这哪是扩展,分明是推倒重来。真正可扩展的校验函数,得让规则和执行彻底分手。
正确做法:校验函数只接收原始数据(比如 map[string][]string 或已反序列化的结构体),返回 error 或 []string 错误消息列表;所有业务规则封装成独立函数,按需组合。举个例子:
ValidateLogin(req *LoginReq) 是具体接口的入口,但内部不写一堆 if/else,而是调用 validateRequiredFields(req)、validatePasswordStrength(req.Password)、validateUserExists(ctx, req.User)。validateEmailFormat 可以被注册、找回密码、邀请接口共用。很多校验依赖运行时信息:当前租户 ID、请求 IP、数据库连接、缓存客户端。如果把这些全塞进每个校验函数的参数列表,调用点立刻爆炸。Functional Options 模式在这里不是用来构造 Client,而是构造 Validator 实例。
定义:type ValidatorOption func(*Validator),然后提供 WithDB(db *sql.DB)、WithTenantID(tenantID string)、WithContext(ctx context.Context) 等选项。
WithDB 里做 db.Ping()——构造阶段应轻量,校验时再检查连接有效性。validateEmail,而非拆成两个 Option。go-playground/validator 的 binding:"required,min=2" 看起来很香,但很快就会撞墙。它不支持条件校验、跨字段约束、动态规则加载,更无法注入上下文。强行用它撑复杂场景,错误信息会变成 “Key: 'User.Email' Error:Field validation for 'Email' failed on the 'required' tag”,用户根本不知道哪错了。
Address 内含 Province 和 City)若字段为 nil 指针,required 不触发——必须手动初始化或用 omitempty 配合额外判断。Type == "email" 时,Value 必须符合邮箱格式” 这类逻辑,struct tag 表达不了,得靠代码分支。func ValidateEmail(s string) bool 返回 false,框架不知道该归到哪个 field,最终报全局错误。别信框架的自动类型转换。chi 的 chi.URLParam(r, "id") 或 gin 的 c.Param("id") 返回 string,传 id=abc 就得到 "abc",strconv.Atoi("abc") panic 是迟早的事。
strconv.ParseUint(c.Param("id"), 10, 64),检查 error,再业务校验(如 id > 0)。c.Query("page") 直接转 int,应先收进 url.Values,再映射到结构体(可用 mapstructure.Decode),最后走 validator 或自定义校验函数。page="" 不等于未传——它是明确传了空值,应视为非法,而不是 fallback 到默认值 1。校验最难的部分从来不是写 if,而是决定「谁负责检查、在哪检查、错的时候告诉用户什么」。Functional Options 可以帮你组织上下文,但无法替代对业务约束的清晰建模。字段级校验函数要语义化(validateRefundAmount 而不是 validateFloat64),错误消息要带字段名和业务含义(“退款金额不能超过订单实付金额”),否则再漂亮的模式也只是包装纸。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8