package http import ( "strings" "tyapi-server/internal/shared/interfaces" "github.com/gin-gonic/gin" "github.com/go-playground/locales/zh" ut "github.com/go-playground/universal-translator" "github.com/go-playground/validator/v10" zh_translations "github.com/go-playground/validator/v10/translations/zh" ) // RequestValidatorZh 中文验证器实现 type RequestValidatorZh struct { validator *validator.Validate translator ut.Translator response interfaces.ResponseBuilder } // NewRequestValidatorZh 创建支持中文翻译的请求验证器 func NewRequestValidatorZh(response interfaces.ResponseBuilder) interfaces.RequestValidator { // 创建验证器实例 validate := validator.New() // 创建中文locale zhLocale := zh.New() uni := ut.New(zhLocale, zhLocale) // 获取中文翻译器 trans, _ := uni.GetTranslator("zh") // 注册中文翻译 zh_translations.RegisterDefaultTranslations(validate, trans) // 注册自定义验证器 registerCustomValidatorsZh(validate, trans) return &RequestValidatorZh{ validator: validate, translator: trans, response: response, } } // Validate 验证请求体 func (v *RequestValidatorZh) Validate(c *gin.Context, dto interface{}) error { if err := v.validator.Struct(dto); err != nil { validationErrors := v.formatValidationErrorsZh(err) v.response.ValidationError(c, validationErrors) return err } return nil } // ValidateQuery 验证查询参数 func (v *RequestValidatorZh) ValidateQuery(c *gin.Context, dto interface{}) error { if err := c.ShouldBindQuery(dto); err != nil { v.response.BadRequest(c, "查询参数格式错误", err.Error()) return err } if err := v.validator.Struct(dto); err != nil { validationErrors := v.formatValidationErrorsZh(err) v.response.ValidationError(c, validationErrors) return err } return nil } // ValidateParam 验证路径参数 func (v *RequestValidatorZh) ValidateParam(c *gin.Context, dto interface{}) error { if err := c.ShouldBindUri(dto); err != nil { v.response.BadRequest(c, "路径参数格式错误", err.Error()) return err } if err := v.validator.Struct(dto); err != nil { validationErrors := v.formatValidationErrorsZh(err) v.response.ValidationError(c, validationErrors) return err } return nil } // BindAndValidate 绑定并验证请求 func (v *RequestValidatorZh) BindAndValidate(c *gin.Context, dto interface{}) error { // 绑定请求体 if err := c.ShouldBindJSON(dto); err != nil { v.response.BadRequest(c, "请求体格式错误", err.Error()) return err } // 验证数据 return v.Validate(c, dto) } // formatValidationErrorsZh 格式化验证错误(中文翻译版) func (v *RequestValidatorZh) formatValidationErrorsZh(err error) map[string][]string { errors := make(map[string][]string) if validationErrors, ok := err.(validator.ValidationErrors); ok { for _, fieldError := range validationErrors { fieldName := v.getFieldNameZh(fieldError) // 首先尝试使用翻译器获取翻译后的错误消息 errorMessage := fieldError.Translate(v.translator) // 如果翻译后的消息包含英文字段名,则替换为中文字段名 fieldDisplayName := v.getFieldDisplayName(fieldError.Field()) if fieldDisplayName != fieldError.Field() { // 替换字段名为中文 errorMessage = strings.ReplaceAll(errorMessage, fieldError.Field(), fieldDisplayName) } if _, exists := errors[fieldName]; !exists { errors[fieldName] = []string{} } errors[fieldName] = append(errors[fieldName], errorMessage) } } return errors } // getFieldNameZh 获取字段名(JSON标签优先) func (v *RequestValidatorZh) getFieldNameZh(fieldError validator.FieldError) string { fieldName := fieldError.Field() return v.toSnakeCase(fieldName) } // getFieldDisplayName 获取字段显示名称(中文) func (v *RequestValidatorZh) getFieldDisplayName(field string) string { fieldNames := map[string]string{ "phone": "手机号", "password": "密码", "confirm_password": "确认密码", "old_password": "原密码", "new_password": "新密码", "confirm_new_password": "确认新密码", "code": "验证码", "username": "用户名", "email": "邮箱", "display_name": "显示名称", "scene": "使用场景", "Password": "密码", "NewPassword": "新密码", "ConfirmPassword": "确认密码", } if displayName, exists := fieldNames[field]; exists { return displayName } return field } // toSnakeCase 转换为snake_case func (v *RequestValidatorZh) toSnakeCase(str string) string { var result strings.Builder for i, r := range str { if i > 0 && (r >= 'A' && r <= 'Z') { result.WriteRune('_') } result.WriteRune(r) } return strings.ToLower(result.String()) } // registerCustomValidatorsZh 注册自定义验证器和中文翻译 func registerCustomValidatorsZh(v *validator.Validate, trans ut.Translator) { // 注册手机号验证器 v.RegisterValidation("phone", validatePhoneZh) v.RegisterTranslation("phone", trans, func(ut ut.Translator) error { return ut.Add("phone", "{0}必须是有效的手机号", true) }, func(ut ut.Translator, fe validator.FieldError) string { t, _ := ut.T("phone", fe.Field()) return t }) // 注册用户名验证器 v.RegisterValidation("username", validateUsernameZh) v.RegisterTranslation("username", trans, func(ut ut.Translator) error { return ut.Add("username", "{0}格式不正确,只能包含字母、数字、下划线,且不能以数字开头", true) }, func(ut ut.Translator, fe validator.FieldError) string { t, _ := ut.T("username", fe.Field()) return t }) // 注册密码强度验证器 v.RegisterValidation("strong_password", validateStrongPasswordZh) v.RegisterTranslation("strong_password", trans, func(ut ut.Translator) error { return ut.Add("strong_password", "{0}强度不足,必须包含大小写字母和数字,且不少于8位", true) }, func(ut ut.Translator, fe validator.FieldError) string { t, _ := ut.T("strong_password", fe.Field()) return t }) // 自定义eqfield翻译 v.RegisterTranslation("eqfield", trans, func(ut ut.Translator) error { return ut.Add("eqfield", "{0}必须等于{1}", true) }, func(ut ut.Translator, fe validator.FieldError) string { t, _ := ut.T("eqfield", fe.Field(), fe.Param()) return t }) } // validatePhoneZh 验证手机号 func validatePhoneZh(fl validator.FieldLevel) bool { phone := fl.Field().String() if phone == "" { return true // 空值由required标签处理 } // 中国手机号验证:11位,以1开头 if len(phone) != 11 { return false } if !strings.HasPrefix(phone, "1") { return false } // 检查是否全是数字 for _, r := range phone { if r < '0' || r > '9' { return false } } return true } // validateUsernameZh 验证用户名 func validateUsernameZh(fl validator.FieldLevel) bool { username := fl.Field().String() if username == "" { return true // 空值由required标签处理 } // 用户名规则:3-30个字符,只能包含字母、数字、下划线,不能以数字开头 if len(username) < 3 || len(username) > 30 { return false } // 不能以数字开头 if username[0] >= '0' && username[0] <= '9' { return false } // 只能包含字母、数字、下划线 for _, r := range username { if !((r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '_') { return false } } return true } // validateStrongPasswordZh 验证密码强度 func validateStrongPasswordZh(fl validator.FieldLevel) bool { password := fl.Field().String() if password == "" { return true // 空值由required标签处理 } // 密码强度规则:至少8个字符,包含大小写字母、数字 if len(password) < 8 { return false } hasUpper := false hasLower := false hasDigit := false for _, r := range password { switch { case r >= 'A' && r <= 'Z': hasUpper = true case r >= 'a' && r <= 'z': hasLower = true case r >= '0' && r <= '9': hasDigit = true } } return hasUpper && hasLower && hasDigit } // ValidateStruct 直接验证结构体 func (v *RequestValidatorZh) ValidateStruct(dto interface{}) error { return v.validator.Struct(dto) }