00:00:00
Validation
介绍
Validation 是 MiladyStack 框架中的一个验证包,提供灵活的请求参数验证机制。该包通过反射机制实现了动态的验证函数注册和调用,支持结构体字段的必需性验证以及自定义验证规则的应用。
主要功能包括:
- 自动注册和调用命名规则为
Validate{TypeName}的验证方法 - 验证结构体中的必需字段是否存在且不为空
- 基于规则的结构体字段验证
- 支持选择特定字段进行验证
- 获取结构体中所有可导出字段的名称
代码演示
基本使用
go
package main
import (
"context"
"errors"
"fmt"
"github.com/miladystack/miladystack/pkg/validation"
)
// UserRegisterIn 用户注册请求结构体
type UserRegisterIn struct {
Username string `json:"username"`
Password string `json:"password"`
Profile *Profile `json:"profile"`
}
type Profile struct {
Email string `json:"email"`
}
// CustomValidator 自定义验证器结构体
type CustomValidator struct{}
// ValidateUserRegisterIn 验证用户注册请求
func (v *CustomValidator) ValidateUserRegisterIn(ctx context.Context, req *UserRegisterIn) error {
// 验证必需字段, 只支持 chan, func, interface, map, pointer, or slice value
if err := validation.ValidRequired(req, "Profile"); err != nil {
return err
}
// 自定义验证逻辑
if len(req.Password) < 6 {
return errors.New("password must be at least 6 characters long")
}
return nil
}
func main() {
// 创建自定义验证器实例
customValidator := &CustomValidator{}
// 创建验证器
validator := validation.NewValidator(customValidator)
// 验证用户注册请求
registerReq := &UserRegisterIn{
Username: "user1",
Password: "123456",
Profile: &Profile{
Email: "[email protected]",
},
}
err := validator.Validate(context.Background(), registerReq)
if err != nil {
fmt.Printf("Validation failed: %v\n", err)
} else {
fmt.Println("UserRegisterIn validation passed")
}
}使用规则验证
go
package main
import (
"errors"
"fmt"
"strings"
"github.com/miladystack/miladystack/pkg/validation"
)
// Product 产品结构体
type Product struct {
Name string `json:"name"`
Price float64 `json:"price"`
Description string `json:"description"`
SKU string `json:"sku"`
}
func main() {
// 定义验证规则
rules := validation.Rules{
"Name": func(value any) error {
name := value.(string)
if len(name) < 2 {
return errors.New("product name must be at least 2 characters long")
}
return nil
},
"Price": func(value any) error {
price := value.(float64)
if price <= 0 {
return errors.New("product price must be greater than zero")
}
return nil
},
"SKU": func(value any) error {
sku := value.(string)
if !strings.HasPrefix(sku, "PROD-") {
return errors.New("SKU must start with PROD-")
}
return nil
},
}
// 验证产品
product := Product{
Name: "Laptop",
Price: 999.99,
Description: "A powerful laptop",
SKU: "PROD-12345",
}
// 验证所有字段
err := validation.ValidateAllFields(product, rules)
if err != nil {
fmt.Printf("Validation failed: %v\n", err)
} else {
fmt.Println("All fields validation passed")
}
// 只验证特定字段
err = validation.ValidateSelectedFields(product, rules, "Name", "Price")
if err != nil {
fmt.Printf("Validation failed: %v\n", err)
} else {
fmt.Println("Selected fields validation passed")
}
}自定义验证器
自定义验证器允许用户根据业务需求实现特定的验证逻辑。以下是一个示例,展示了如何创建一个自定义验证器来验证用户注册请求。
go
package main
import (
"context"
"fmt"
"regexp"
"github.com/jinzhu/gorm"
genericvalidation "github.com/miladystack/miladystack/pkg/validation"
)
func main() {
// 创建模拟的数据库连接(实际使用中应替换为真实的数据库连接)
db := (*gorm.DB)(nil)
// 创建验证器实例
validator := &Validator{
db: db,
}
// 创建上下文,包含用户ID
ctx := context.WithValue(context.Background(), userIDKey{}, "user-123")
// 示例1:使用ValidateListUserRequest验证有效的用户列表请求
validListReq := &ListUserRequest{
Offset: 0,
Limit: 10, // 有效的limit值
}
err := validator.ValidateListUserRequest(ctx, validListReq)
if err != nil {
fmt.Printf("验证失败(有效请求): %v\n", err)
} else {
fmt.Println("有效用户列表请求验证通过")
fmt.Printf("查询参数: offset=%d, limit=%d\n", validListReq.Offset, validListReq.Limit)
}
// 示例2:使用ValidateListUserRequest验证无效的用户列表请求(limit <= 0)
invalidListReq := &ListUserRequest{
Offset: 0,
Limit: 0, // 无效的limit值
}
err = validator.ValidateListUserRequest(ctx, invalidListReq)
if err != nil {
fmt.Printf("验证失败(无效请求): %v\n", err)
} else {
fmt.Println("无效用户列表请求验证通过")
}
// 示例3:使用其他验证函数
fmt.Println("\n--- 其他验证函数示例 ---")
// 登录请求验证
loginReq := &LoginRequest{
Username: "valid_user",
Password: "Valid123",
}
if err := validator.ValidateLoginRequest(ctx, loginReq); err != nil {
fmt.Printf("登录请求验证失败: %v\n", err)
} else {
fmt.Println("登录请求验证通过")
}
// 创建用户请求验证
createReq := &CreateUserRequest{
Username: "new_user123",
Password: "NewPass123",
Email: "[email protected]",
Phone: "13800138000",
}
if err := validator.ValidateCreateUserRequest(ctx, createReq); err != nil {
fmt.Printf("创建用户请求验证失败: %v\n", err)
} else {
fmt.Println("创建用户请求验证通过")
}
fmt.Println("\n所有验证示例执行完毕")
}
// 定义用于上下文的键.
type (
// userIDKey 定义用户 ID 的上下文键.
userIDKey struct{}
)
func UserID(ctx context.Context) string {
userID, _ := ctx.Value(userIDKey{}).(string)
return userID
}
// Validator 是验证逻辑的实现结构体.
type Validator struct {
// 有些复杂的验证逻辑,可能需要直接查询数据库
// 这里只是一个举例,如果验证时,有其他依赖的客户端/服务/资源等,
// 都可以一并注入进来
db *gorm.DB
}
// 使用预编译的全局正则表达式,避免重复创建和编译.
var (
lengthRegex = regexp.MustCompile(`^.{3,20}$`) // 长度在 3 到 20 个字符之间
validRegex = regexp.MustCompile(`^[A-Za-z0-9_]+$`) // 仅包含字母、数字和下划线
letterRegex = regexp.MustCompile(`[A-Za-z]`) // 至少包含一个字母
numberRegex = regexp.MustCompile(`\d`) // 至少包含一个数字
emailRegex = regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`) // 邮箱格式
phoneRegex = regexp.MustCompile(`^1[3-9]\d{9}$`) // 中国手机号
)
// isValidUsername 校验用户名是否合法.
func isValidUsername(username string) bool {
// 校验长度
if !lengthRegex.MatchString(username) {
return false
}
// 校验字符合法性
if !validRegex.MatchString(username) {
return false
}
return true
}
// isValidPassword 判断密码是否符合复杂度要求.
func isValidPassword(password string) error {
switch {
// 检查新密码是否为空
case password == "":
return fmt.Errorf("password cannot be empty")
// 检查新密码的长度要求
case len(password) < 6:
return fmt.Errorf("password must be at least 6 characters long")
// 使用正则表达式检查是否至少包含一个字母
case !letterRegex.MatchString(password):
return fmt.Errorf("password must contain at least one letter")
// 使用正则表达式检查是否至少包含一个数字
case !numberRegex.MatchString(password):
return fmt.Errorf("password must contain at least one number")
}
return nil
}
// isValidEmail 判断电子邮件是否合法.
func isValidEmail(email string) error {
// 检查电子邮件地址格式
if email == "" {
return fmt.Errorf("email cannot be empty")
}
// 使用正则表达式校验电子邮件格式
if !emailRegex.MatchString(email) {
return fmt.Errorf("invalid email format")
}
return nil
}
// isValidPhone 判断手机号码是否合法.
func isValidPhone(phone string) error {
// 检查手机号码格式
if phone == "" {
return fmt.Errorf("phone cannot be empty")
}
// 使用正则表达式校验手机号码格式(假设是中国手机号,11 位数字)
if !phoneRegex.MatchString(phone) {
return fmt.Errorf("invalid phone format")
}
return nil
}
// LoginRequest 表示登录请求
type LoginRequest struct {
// username 表示用户名称
Username string `json:"username,omitempty"`
// password 表示用户密码
Password string `json:"password,omitempty"`
}
// ChangePasswordRequest 表示修改密码请求
type ChangePasswordRequest struct {
// userID 表示用户 ID
UserID string `json:"userID,omitempty"`
// oldPassword 表示当前密码
OldPassword string `json:"oldPassword,omitempty"`
// newPassword 表示准备修改的新密码
NewPassword string `json:"newPassword,omitempty"`
}
func (x *ChangePasswordRequest) GetUserID() string {
if x != nil {
return x.UserID
}
return ""
}
// CreateUserRequest 表示创建用户请求
type CreateUserRequest struct {
// username 表示用户名称
Username string `json:"username,omitempty"`
// password 表示用户密码
Password string `json:"password,omitempty"`
// nickname 表示用户昵称
Nickname *string `json:"nickname,omitempty"`
// email 表示用户电子邮箱
Email string `json:"email,omitempty"`
// phone 表示用户手机号
Phone string `json:"phone,omitempty"`
}
// UpdateUserRequest 表示更新用户请求
type UpdateUserRequest struct {
// userID 表示用户 ID
UserID string `json:"userID,omitempty"`
// username 表示可选的用户名称
Username *string `json:"username,omitempty"`
// nickname 表示可选的用户昵称
Nickname *string `json:"nickname,omitempty"`
// email 表示可选的用户电子邮箱
Email *string `json:"email,omitempty"`
// phone 表示可选的用户手机号
Phone *string `json:"phone,omitempty"`
}
func (x *UpdateUserRequest) GetUserID() string {
if x != nil {
return x.UserID
}
return ""
}
// DeleteUserRequest 表示删除用户请求
type DeleteUserRequest struct {
// userID 表示用户 ID
// @gotags: uri:"userID"
UserID string `json:"userID,omitempty" uri:"userID"`
}
// GetUserRequest 表示获取用户请求
type GetUserRequest struct {
// userID 表示用户 ID
// @gotags: uri:"userID"
UserID string `json:"userID,omitempty" uri:"userID"`
}
func (x *GetUserRequest) GetUserID() string {
if x != nil {
return x.UserID
}
return ""
}
// ListUserRequest 表示用户列表请求
type ListUserRequest struct {
// offset 表示偏移量
// @gotags: form:"offset"
Offset int64 `json:"offset,omitempty" form:"offset"`
// limit 表示每页数量
// @gotags: form:"limit"
Limit int64 `json:"limit,omitempty" form:"limit"`
}
func (v *Validator) ValidateUserRules() genericvalidation.Rules {
// 通用的密码校验函数
validatePassword := func() genericvalidation.ValidatorFunc {
return func(value any) error {
return isValidPassword(value.(string))
}
}
// 定义各字段的校验逻辑,通过一个 map 实现模块化和简化
return genericvalidation.Rules{
"Password": validatePassword(),
"OldPassword": validatePassword(),
"NewPassword": validatePassword(),
"UserID": func(value any) error {
if value.(string) == "" {
return fmt.Errorf("userID cannot be empty")
}
return nil
},
"Username": func(value any) error {
if !isValidUsername(value.(string)) {
return fmt.Errorf("Invalid username: Username must consist of letters, digits, and underscores only, and its length must be between 3 and 20 characters.")
}
return nil
},
"Nickname": func(value any) error {
if len(value.(string)) >= 30 {
return fmt.Errorf("nickname must be less than 30 characters")
}
return nil
},
"Email": func(value any) error {
return isValidEmail(value.(string))
},
"Phone": func(value any) error {
return isValidPhone(value.(string))
},
"Limit": func(value any) error {
if value.(int64) <= 0 {
return fmt.Errorf("limit must be greater than 0")
}
return nil
},
"Offset": func(value any) error {
return nil
},
}
}
// ValidateLoginRequest 校验修改密码请求.
func (v *Validator) ValidateLoginRequest(ctx context.Context, rq *LoginRequest) error {
return genericvalidation.ValidateAllFields(rq, v.ValidateUserRules())
}
// ValidateChangePasswordRequest 校验 ChangePasswordRequest 结构体的有效性.
func (v *Validator) ValidateChangePasswordRequest(ctx context.Context, rq *ChangePasswordRequest) error {
if rq.GetUserID() != UserID(ctx) {
return fmt.Errorf("The logged-in user `%s` does not match request user `%s`", UserID(ctx), rq.GetUserID())
}
return genericvalidation.ValidateAllFields(rq, v.ValidateUserRules())
}
// ValidateCreateUserRequest 校验 CreateUserRequest 结构体的有效性.
func (v *Validator) ValidateCreateUserRequest(ctx context.Context, rq *CreateUserRequest) error {
return genericvalidation.ValidateAllFields(rq, v.ValidateUserRules())
}
// ValidateUpdateUserRequest 校验更新用户请求.
func (v *Validator) ValidateUpdateUserRequest(ctx context.Context, rq *UpdateUserRequest) error {
if rq.GetUserID() != UserID(ctx) {
return fmt.Errorf("The logged-in user `%s` does not match request user `%s`", UserID(ctx), rq.GetUserID())
}
return genericvalidation.ValidateSelectedFields(rq, v.ValidateUserRules(), "UserID")
}
// ValidateDeleteUserRequest 校验 DeleteUserRequest 结构体的有效性.
func (v *Validator) ValidateDeleteUserRequest(ctx context.Context, rq *DeleteUserRequest) error {
return genericvalidation.ValidateAllFields(rq, v.ValidateUserRules())
}
// ValidateGetUserRequest 校验 GetUserRequest 结构体的有效性.
func (v *Validator) ValidateGetUserRequest(ctx context.Context, rq *GetUserRequest) error {
if rq.GetUserID() != UserID(ctx) {
return fmt.Errorf("The logged-in user `%s` does not match request user `%s`", UserID(ctx), rq.GetUserID())
}
return genericvalidation.ValidateAllFields(rq, v.ValidateUserRules())
}
// ValidateListUserRequest 校验 ListUserRequest 结构体的有效性.
func (v *Validator) ValidateListUserRequest(ctx context.Context, rq *ListUserRequest) error {
return genericvalidation.ValidateAllFields(rq, v.ValidateUserRules())
}方法
NewValidator(customValidator any) *Validator
创建并初始化一个自定义验证器。
参数:
customValidator any- 包含验证方法的结构体实例
返回值:
*Validator- 初始化后的验证器实例
(v *Validator) Validate(ctx context.Context, request any) error
使用适当的验证方法验证请求。
参数:
ctx context.Context- 上下文request any- 要验证的请求对象指针
返回值:
error- 如果验证失败,返回错误;否则返回nil
ValidRequired(obj any, requiredFields ...string) error
验证结构体中的必需字段是否存在且不为空,只支持 chan, func, interface, map, pointer, or slice value。
参数:
obj any- 结构体或结构体指针requiredFields ...string- 必需字段名称列表
返回值:
error- 如果任何必需字段为空或不存在,返回错误;否则返回nil
ValidateAllFields(obj any, rules Rules) error
验证结构体的所有字段是否符合规则。
参数:
obj any- 结构体或结构体指针rules Rules- 验证规则映射
返回值:
error- 如果任何字段验证失败,返回错误;否则返回nil
ValidateSelectedFields(obj any, rules Rules, fields ...string) error
验证结构体中选定的字段是否符合规则。
参数:
obj any- 结构体或结构体指针rules Rules- 验证规则映射fields ...string- 要验证的字段名称列表
返回值:
error- 如果任何选定字段验证失败,返回错误;否则返回nil
GetExportedFieldNames(obj any) []string
返回传入结构体中所有可导出的字段名字。
参数:
obj any- 结构体或结构体指针
返回值:
[]string- 可导出字段名称列表
功能特性
- 自动方法注册:通过反射自动注册符合命名规则的验证方法
- 类型安全验证:基于请求类型调用对应的验证方法
- 必需字段验证:便捷地验证结构体必需字段
- 灵活的规则验证:支持基于函数的自定义验证规则
- 选择性验证:可以只验证结构体中的特定字段
- 反射机制:利用Go语言的反射机制实现动态验证
- 错误处理:提供清晰的错误信息,便于调试和用户反馈
- 与上下文集成:验证方法支持上下文参数,可以传递额外信息或实现超时控制