在Golang的Web开发场景中,表单提交、接口参数传递都需要对输入数据进行合法性校验,比如判断字段是否为空、长度是否符合要求、格式是否匹配等。如果每次处理请求都单独编写校验逻辑,不仅会产生大量重复代码,还会让业务逻辑变得冗余。实现一个通用的表单校验库,把常用校验规则封装起来,支持规则灵活复用,就能很好地解决这个问题。
基础校验规则定义
首先我们需要定义校验规则的结构,每个规则需要包含校验函数、错误提示信息两个核心部分。校验函数接收待校验的值和规则参数,返回校验是否通过的结果。
package validator
import (
"fmt"
"reflect"
"strings"
)
// Rule 定义单个校验规则结构
type Rule struct {
// Check 校验函数,参数依次为待校验值、规则额外参数,返回是否通过和错误信息
Check func(value interface{}, params string) (bool, string)
// Msg 默认错误提示信息
Msg string
}
// 预定义常用校验规则
var (
// Required 必填规则,校验值不为空
Required = Rule{
Check: func(value interface{}, params string) (bool, string) {
v := reflect.ValueOf(value)
switch v.Kind() {
case reflect.String:
return strings.TrimSpace(v.String()) != "", "字段不能为空"
case reflect.Ptr, reflect.Interface, reflect.Slice, reflect.Map, reflect.Array:
return !v.IsNil(), "字段不能为空"
default:
return true, ""
}
},
Msg: "字段不能为空",
}
// MinLength 最小长度规则,参数为最小长度数值
MinLength = Rule{
Check: func(value interface{}, params string) (bool, string) {
str, ok := value.(string)
if !ok {
return false, "值必须为字符串类型"
}
minLen := 0
fmt.Sscanf(params, "%d", &minLen)
return len([]rune(str)) >= minLen, fmt.Sprintf("长度不能小于%d", minLen)
},
Msg: "长度不符合要求",
}
// MaxLength 最大长度规则,参数为最大长度数值
MaxLength = Rule{
Check: func(value interface{}, params string) (bool, string) {
str, ok := value.(string)
if !ok {
return false, "值必须为字符串类型"
}
maxLen := 0
fmt.Sscanf(params, "%d", &maxLen)
return len([]rune(str)) <= maxLen, fmt.Sprintf("长度不能大于%d", maxLen)
},
Msg: "长度不符合要求",
}
)
校验器核心结构实现
接下来需要封装校验器核心结构,支持为不同字段绑定多个校验规则,同时执行所有规则的校验,收集错误信息。
// Validator 校验器核心结构
type Validator struct {
// rules 存储字段对应的校验规则列表,key为字段名
rules map[string][]Rule
// errors 存储校验错误信息,key为字段名
errors map[string][]string
}
// NewValidator 创建新的校验器实例
func NewValidator() *Validator {
return &Validator{
rules: make(map[string][]Rule),
errors: make(map[string][]string),
}
}
// AddRule 为指定字段添加校验规则
func (v *Validator) AddRule(field string, rule Rule, customMsg ...string) *Validator {
// 如果传入自定义错误信息,替换规则的默认信息
if len(customMsg) > 0 {
rule.Msg = customMsg[0]
}
v.rules[field] = append(v.rules[field], rule)
return v
}
// Validate 执行校验,传入待校验的数据结构体,返回是否全部通过
func (v *Validator) Validate(data interface{}) bool {
// 清空之前的错误信息
v.errors = make(map[string][]string)
val := reflect.ValueOf(data)
// 如果是指针类型,获取指向的元素
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
// 只处理结构体类型
if val.Kind() != reflect.Struct {
return false
}
typ := val.Type()
// 遍历所有字段
for i := 0; i < val.NumField(); i++ {
field := typ.Field(i)
fieldName := field.Name
// 获取字段对应的值
fieldValue := val.Field(i).Interface()
// 获取该字段绑定的所有规则
rules, ok := v.rules[fieldName]
if !ok {
continue
}
// 执行每个规则的校验
for _, rule := range rules {
// 这里默认规则不需要额外参数,实际使用中可以根据规则类型传递参数
pass, errMsg := rule.Check(fieldValue, "")
if !pass {
// 如果规则返回了错误信息,使用返回的信息,否则使用规则默认信息
msg := rule.Msg
if errMsg != "" {
msg = errMsg
}
v.errors[fieldName] = append(v.errors[fieldName], msg)
}
}
}
return len(v.errors) == 0
}
// GetErrors 获取所有校验错误信息
func (v *Validator) GetErrors() map[string][]string {
return v.errors
}
规则复用与自定义扩展
校验库的核心优势是规则可以复用,同时支持开发者根据业务需求自定义新的校验规则,只需要遵循规则的结构定义即可。
已有规则复用
同一个校验规则可以绑定到多个不同的字段,不需要重复编写校验逻辑。比如多个字段都需要必填校验,直接复用Required规则即可。
// 定义用户注册表单结构
type RegisterForm struct {
Username string
Password string
Email string
}
func main() {
form := RegisterForm{
Username: "test",
Password: "123",
Email: "",
}
// 创建校验器
v := NewValidator()
// 复用Required规则到多个字段
v.AddRule("Username", Required, "用户名不能为空")
v.AddRule("Password", Required, "密码不能为空")
v.AddRule("Email", Required, "邮箱不能为空")
// 执行校验
if !v.Validate(form) {
fmt.Println("校验失败,错误信息:", v.GetErrors())
}
}
自定义校验规则
如果预定义规则无法满足业务需求,可以自定义新的校验规则,只需要实现Rule结构的Check函数即可。
// 自定义邮箱格式校验规则
var EmailFormat = Rule{
Check: func(value interface{}, params string) (bool, string) {
str, ok := value.(string)
if !ok {
return false, "值必须为字符串类型"
}
// 简单的邮箱格式校验逻辑
if !strings.Contains(str, "@") || !strings.Contains(str, ".") {
return false, "邮箱格式不正确"
}
return true, ""
},
Msg: "邮箱格式不正确",
}
func main() {
form := RegisterForm{
Username: "test",
Password: "123456",
Email: "testippipp.com",
}
v := NewValidator()
v.AddRule("Username", Required)
v.AddRule("Password", Required)
v.AddRule("Email", Required, "邮箱不能为空")
// 添加自定义邮箱格式校验规则
v.AddRule("Email", EmailFormat)
if !v.Validate(form) {
fmt.Println("校验失败,错误信息:", v.GetErrors())
}
}
优化规则参数传递
之前的规则实现中,部分规则需要额外参数(比如MinLength需要长度参数),我们可以优化校验器的AddRule方法,支持传递规则参数,让规则使用更灵活。
// 优化后的AddRule方法,支持传递规则参数
func (v *Validator) AddRuleWithParam(field string, rule Rule, param string, customMsg ...string) *Validator {
if len(customMsg) > 0 {
rule.Msg = customMsg[0]
}
// 把规则和参数绑定,执行校验时传递参数
type ruleWithParam struct {
rule Rule
param string
}
// 存储带参数的规则,这里可以扩展rules的结构,实际需要调整rules的类型定义
// 简化示例,直接在校验时处理参数逻辑,此处省略完整扩展代码
return v
}
通过以上实现,我们就完成了一个基础的Golang表单校验库,支持常用规则定义、规则复用、自定义规则扩展,能够满足大部分Web开发中的表单校验需求。开发者可以根据实际业务场景继续扩展更多预定义规则,比如数字范围校验、正则匹配校验等,让校验库的功能更完善。