# Validator 封装设计 ## 1. 整体架构设计 ``` shared/ ├── validator/ # 格式校验器封装 │ ├── validator.go # 校验器初始化和接口定义 │ ├── format_validator.go # 格式校验实现 │ ├── custom_rules.go # 自定义校验规则 │ ├── messages.go # 错误消息配置 │ └── middleware.go # 校验中间件 ├── errcode/ │ ├── validator_errors.go # 校验相关错误码 └── response/ └── validator_response.go # 校验错误响应格式 domains/ └── product/rpc/internal/logic/ └── validator/ # 业务校验器封装 ├── base.go # 业务校验器基类 ├── product_validator.go └── category_validator.go ``` ## 2. 格式校验器封装(shared/validator) ### 2.1 校验器接口定义 ```go // shared/validator/validator.go package validator import ( "reflect" "github.com/go-playground/validator/v10" "tianyuan/shared/errcode" ) // 校验器接口 type IValidator interface { Validate(data interface{}) error ValidateStruct(data interface{}) error AddCustomRule(tag string, fn validator.Func) error } // 全局校验器实例 var GlobalValidator IValidator // 初始化校验器 func Init() error { v := &FormatValidator{ validator: validator.New(), } // 注册自定义规则 if err := v.registerCustomRules(); err != nil { return err } // 注册中文错误消息 if err := v.registerMessages(); err != nil { return err } GlobalValidator = v return nil } // 便捷函数 func Validate(data interface{}) error { return GlobalValidator.Validate(data) } func ValidateStruct(data interface{}) error { return GlobalValidator.ValidateStruct(data) } ``` ### 2.2 格式校验器实现 ```go // shared/validator/format_validator.go package validator import ( "fmt" "reflect" "strings" "github.com/go-playground/validator/v10" "tianyuan/shared/errcode" ) type FormatValidator struct { validator *validator.Validate } // 校验接口实现 func (v *FormatValidator) Validate(data interface{}) error { if err := v.validator.Struct(data); err != nil { return v.formatError(err) } return nil } func (v *FormatValidator) ValidateStruct(data interface{}) error { return v.Validate(data) } func (v *FormatValidator) AddCustomRule(tag string, fn validator.Func) error { return v.validator.RegisterValidation(tag, fn) } // 错误格式化 func (v *FormatValidator) formatError(err error) error { if validationErrors, ok := err.(validator.ValidationErrors); ok { var errMsgs []string for _, fieldError := range validationErrors { errMsg := v.getErrorMessage(fieldError) errMsgs = append(errMsgs, errMsg) } return errcode.NewValidationError(strings.Join(errMsgs, "; ")) } return err } // 获取错误消息 func (v *FormatValidator) getErrorMessage(fieldError validator.FieldError) string { // 获取字段的中文名称 fieldName := v.getFieldName(fieldError) // 根据校验标签获取错误消息 switch fieldError.Tag() { case "required": return fmt.Sprintf("%s不能为空", fieldName) case "min": return fmt.Sprintf("%s最小值为%s", fieldName, fieldError.Param()) case "max": return fmt.Sprintf("%s最大值为%s", fieldName, fieldError.Param()) case "email": return fmt.Sprintf("%s格式不正确", fieldName) case "mobile": return fmt.Sprintf("%s格式不正确", fieldName) default: return fmt.Sprintf("%s校验失败", fieldName) } } // 获取字段中文名称 func (v *FormatValidator) getFieldName(fieldError validator.FieldError) string { // 可以通过反射获取struct tag中的中文名称 // 或者维护一个字段名映射表 fieldName := fieldError.Field() // 简单示例,实际可以更复杂 nameMap := map[string]string{ "CategoryId": "分类ID", "PageNum": "页码", "PageSize": "每页数量", "Keyword": "关键词", "Mobile": "手机号", "Email": "邮箱", } if chineseName, exists := nameMap[fieldName]; exists { return chineseName } return fieldName } ``` ### 2.3 自定义校验规则 ```go // shared/validator/custom_rules.go package validator import ( "regexp" "github.com/go-playground/validator/v10" ) // 注册自定义校验规则 func (v *FormatValidator) registerCustomRules() error { // 手机号校验 if err := v.validator.RegisterValidation("mobile", validateMobile); err != nil { return err } // 身份证号校验 if err := v.validator.RegisterValidation("idcard", validateIDCard); err != nil { return err } // 企业统一社会信用代码校验 if err := v.validator.RegisterValidation("creditcode", validateCreditCode); err != nil { return err } return nil } // 手机号校验函数 func validateMobile(fl validator.FieldLevel) bool { mobile := fl.Field().String() pattern := `^1[3-9]\d{9}$` matched, _ := regexp.MatchString(pattern, mobile) return matched } // 身份证号校验函数 func validateIDCard(fl validator.FieldLevel) bool { idcard := fl.Field().String() pattern := `^[1-9]\d{5}(18|19|20)\d{2}((0[1-9])|(1[0-2]))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$` matched, _ := regexp.MatchString(pattern, idcard) return matched } // 企业统一社会信用代码校验函数 func validateCreditCode(fl validator.FieldLevel) bool { code := fl.Field().String() pattern := `^[0-9A-HJ-NPQRTUWXY]{2}\d{6}[0-9A-HJ-NPQRTUWXY]{10}$` matched, _ := regexp.MatchString(pattern, code) return matched } ``` ### 2.4 校验中间件 ```go // shared/validator/middleware.go package validator import ( "net/http" "github.com/zeromicro/go-zero/rest/httpx" "tianyuan/shared/response" ) // 校验中间件 func ValidationMiddleware() func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // 这里可以添加全局校验逻辑 // 比如请求头校验、通用参数校验等 next.ServeHTTP(w, r) }) } } // Handler校验辅助函数 func ValidateAndParse(r *http.Request, req interface{}) error { // 1. 参数绑定 if err := httpx.Parse(r, req); err != nil { return err } // 2. 格式校验 if err := Validate(req); err != nil { return err } return nil } ``` ## 3. 业务校验器封装(各域内部) ### 3.1 业务校验器基类 ```go // domains/product/rpc/internal/logic/validator/base.go package validator import ( "context" "tianyuan/domains/product/rpc/internal/svc" "tianyuan/shared/errcode" ) // 业务校验器基类 type BaseValidator struct { svcCtx *svc.ServiceContext ctx context.Context } func NewBaseValidator(ctx context.Context, svcCtx *svc.ServiceContext) *BaseValidator { return &BaseValidator{ svcCtx: svcCtx, ctx: ctx, } } // 业务校验接口 type IBusinessValidator interface { ValidatePermission(userId int64, action string) error ValidateResourceExists(resourceType string, resourceId int64) error ValidateBusinessRules(data interface{}) error } // 通用业务校验方法 func (v *BaseValidator) ValidatePermission(userId int64, action string) error { // 调用用户域RPC检查权限 resp, err := v.svcCtx.UserRpc.CheckPermission(v.ctx, &user.CheckPermissionReq{ UserId: userId, Permission: action, }) if err != nil { return err } if !resp.HasPermission { return errcode.NewBusinessError("用户无权限执行此操作") } return nil } func (v *BaseValidator) ValidateResourceExists(resourceType string, resourceId int64) error { // 根据资源类型检查资源是否存在 switch resourceType { case "category": return v.validateCategoryExists(resourceId) case "product": return v.validateProductExists(resourceId) default: return errcode.NewBusinessError("未知的资源类型") } } func (v *BaseValidator) validateCategoryExists(categoryId int64) error { category, err := v.svcCtx.CategoryModel.FindOne(v.ctx, categoryId) if err != nil { return errcode.NewBusinessError("分类不存在") } if category.Status != 1 { return errcode.NewBusinessError("分类已禁用") } return nil } func (v *BaseValidator) validateProductExists(productId int64) error { product, err := v.svcCtx.ProductModel.FindOne(v.ctx, productId) if err != nil { return errcode.NewBusinessError("产品不存在") } if product.Status != 1 { return errcode.NewBusinessError("产品已下架") } return nil } ``` ### 3.2 具体业务校验器 ```go // domains/product/rpc/internal/logic/validator/product_validator.go package validator import ( "context" "tianyuan/domains/product/rpc/internal/svc" "tianyuan/domains/product/rpc/product" "tianyuan/shared/errcode" ) type ProductValidator struct { *BaseValidator } func NewProductValidator(ctx context.Context, svcCtx *svc.ServiceContext) *ProductValidator { return &ProductValidator{ BaseValidator: NewBaseValidator(ctx, svcCtx), } } // 校验获取产品列表请求 func (v *ProductValidator) ValidateGetProductListRequest(req *product.GetProductListReq) error { // 1. 校验用户权限 if err := v.ValidatePermission(req.UserId, "product:list"); err != nil { return err } // 2. 校验分类存在性 if req.CategoryId > 0 { if err := v.ValidateResourceExists("category", req.CategoryId); err != nil { return err } } // 3. 校验用户是否有查看该分类的权限 if req.CategoryId > 0 { if err := v.validateCategoryAccess(req.UserId, req.CategoryId); err != nil { return err } } return nil } // 校验创建产品请求 func (v *ProductValidator) ValidateCreateProductRequest(req *product.CreateProductReq) error { // 1. 校验用户权限 if err := v.ValidatePermission(req.UserId, "product:create"); err != nil { return err } // 2. 校验分类存在性 if err := v.ValidateResourceExists("category", req.CategoryId); err != nil { return err } // 3. 校验产品名称是否重复 if err := v.validateProductNameUnique(req.Name, req.CategoryId); err != nil { return err } return nil } // 校验分类访问权限 func (v *ProductValidator) validateCategoryAccess(userId, categoryId int64) error { // 检查用户是否有访问该分类的权限 // 这里可能涉及到用户等级、VIP权限等业务逻辑 userInfo, err := v.svcCtx.UserRpc.GetUserInfo(v.ctx, &user.GetUserInfoReq{ UserId: userId, }) if err != nil { return err } // 示例:VIP用户可以访问所有分类,普通用户只能访问基础分类 category, err := v.svcCtx.CategoryModel.FindOne(v.ctx, categoryId) if err != nil { return err } if category.RequireVip && userInfo.UserType != "vip" { return errcode.NewBusinessError("该分类需要VIP权限") } return nil } // 校验产品名称唯一性 func (v *ProductValidator) validateProductNameUnique(name string, categoryId int64) error { exists, err := v.svcCtx.ProductModel.CheckNameExists(v.ctx, name, categoryId) if err != nil { return err } if exists { return errcode.NewBusinessError("同分类下产品名称已存在") } return nil } ``` ## 4. 使用示例 ### 4.1 在 Handler 中使用格式校验 ```go // client/internal/handler/product/getproductlisthandler.go func (h *GetProductListHandler) GetProductList(w http.ResponseWriter, r *http.Request) { var req types.GetProductListReq // 使用封装的校验函数 if err := validator.ValidateAndParse(r, &req); err != nil { response.ParamErrorResponse(w, err) return } // 调用Logic层 resp, err := h.logic.GetProductList(&req) if err != nil { response.ErrorResponse(w, err) return } response.SuccessResponse(w, resp) } ``` ### 4.2 在 RPC Logic 中使用业务校验 ```go // domains/product/rpc/internal/logic/getproductlistlogic.go func (l *GetProductListLogic) GetProductList(req *product.GetProductListReq) (*product.GetProductListResp, error) { // 创建业务校验器 validator := validator.NewProductValidator(l.ctx, l.svcCtx) // 执行业务校验 if err := validator.ValidateGetProductListRequest(req); err != nil { return nil, err } // 执行业务逻辑 products, total, err := l.svcCtx.ProductModel.FindList(l.ctx, req) if err != nil { return nil, err } return &product.GetProductListResp{ List: products, Total: total, }, nil } ``` ### 4.3 在 main.go 中初始化 ```go // client/client.go func main() { // 初始化格式校验器 if err := validator.Init(); err != nil { log.Fatalf("初始化校验器失败: %v", err) } // 其他初始化代码... } ``` ## 5. 封装的优势 1. **统一的接口**:所有校验都通过统一的接口调用 2. **错误处理标准化**:统一的错误格式和消息 3. **可扩展性**:容易添加新的校验规则 4. **代码复用**:通用校验逻辑可以复用 5. **测试友好**:校验器可以独立测试 6. **配置化**:错误消息和校验规则可以配置化管理 这样的封装既保持了格式校验和业务校验的清晰边界,又提供了便捷的使用接口。