package http import ( "fmt" "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 { response interfaces.ResponseBuilder translator ut.Translator } // NewRequestValidatorZh 创建支持中文翻译的请求验证器 func NewRequestValidatorZh(response interfaces.ResponseBuilder) interfaces.RequestValidator { // 创建中文locale zhLocale := zh.New() uni := ut.New(zhLocale, zhLocale) // 获取中文翻译器 trans, _ := uni.GetTranslator("zh") // 注册官方中文翻译 zh_translations.RegisterDefaultTranslations(validator.New(), trans) // 注册自定义翻译 registerCustomTranslations(trans) return &RequestValidatorZh{ response: response, translator: trans, } } // registerCustomTranslations 注册自定义翻译 func registerCustomTranslations(trans ut.Translator) { // 自定义 eqfield 翻译(更友好的提示) _ = trans.Add("eqfield", "{0}必须与{1}一致", true) // 自定义 required 翻译 _ = trans.Add("required", "{0}不能为空", true) // 自定义 min 翻译 _ = trans.Add("min", "{0}长度不能少于{1}位", true) // 自定义 max 翻译 _ = trans.Add("max", "{0}长度不能超过{1}位", true) // 自定义 len 翻译 _ = trans.Add("len", "{0}长度必须为{1}位", true) // 自定义 email 翻译 _ = trans.Add("email", "{0}必须是有效的邮箱地址", true) // 自定义手机号翻译 _ = trans.Add("phone", "{0}必须是有效的手机号", true) // 自定义用户名翻译 _ = trans.Add("username", "{0}格式不正确,只能包含字母、数字、下划线,且不能以数字开头", true) // 自定义强密码翻译 _ = trans.Add("strong_password", "{0}强度不足,必须包含大小写字母和数字,且不少于8位", true) } // Validate 验证请求体 func (v *RequestValidatorZh) Validate(c *gin.Context, dto interface{}) error { // 直接使用 Gin 的绑定和验证 return v.BindAndValidate(c, dto) } // ValidateQuery 验证查询参数 func (v *RequestValidatorZh) ValidateQuery(c *gin.Context, dto interface{}) error { if err := c.ShouldBindQuery(dto); err != nil { // 处理查询参数验证错误 if _, ok := err.(validator.ValidationErrors); ok { validationErrors := v.formatValidationErrorsZh(err) v.response.ValidationError(c, validationErrors) } else { v.response.BadRequest(c, "查询参数格式错误", err.Error()) } return err } return nil } // ValidateParam 验证路径参数 func (v *RequestValidatorZh) ValidateParam(c *gin.Context, dto interface{}) error { if err := c.ShouldBindUri(dto); err != nil { // 处理路径参数验证错误 if _, ok := err.(validator.ValidationErrors); ok { validationErrors := v.formatValidationErrorsZh(err) v.response.ValidationError(c, validationErrors) } else { v.response.BadRequest(c, "路径参数格式错误", err.Error()) } return err } return nil } // BindAndValidate 绑定并验证请求 func (v *RequestValidatorZh) BindAndValidate(c *gin.Context, dto interface{}) error { // 绑定请求体(Gin 会自动进行 binding 标签验证) if err := c.ShouldBindJSON(dto); err != nil { // 处理 Gin binding 验证错误 if _, ok := err.(validator.ValidationErrors); ok { // 所有验证错误都使用 422 状态码 validationErrors := v.formatValidationErrorsZh(err) v.response.ValidationError(c, validationErrors) } else { v.response.BadRequest(c, "请求体格式错误", err.Error()) } return err } return nil } // 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 := v.getFriendlyErrorMessage(fieldError) if _, exists := errors[fieldName]; !exists { errors[fieldName] = []string{} } errors[fieldName] = append(errors[fieldName], errorMessage) } } return errors } // getFriendlyErrorMessage 获取友好的中文错误消息 func (v *RequestValidatorZh) getFriendlyErrorMessage(fieldError validator.FieldError) string { field := fieldError.Field() tag := fieldError.Tag() param := fieldError.Param() fieldDisplayName := v.getFieldDisplayName(field) // 优先使用官方翻译器 errorMessage := fieldError.Translate(v.translator) // 如果官方翻译成功且不是英文,使用官方翻译 if errorMessage != fieldError.Error() { // 替换字段名为中文 if fieldDisplayName != fieldError.Field() { errorMessage = strings.ReplaceAll(errorMessage, fieldError.Field(), fieldDisplayName) } return errorMessage } // 回退到自定义翻译 switch tag { case "required": return fmt.Sprintf("%s不能为空", fieldDisplayName) case "email": return fmt.Sprintf("%s必须是有效的邮箱地址", fieldDisplayName) case "min": return fmt.Sprintf("%s长度不能少于%s位", fieldDisplayName, param) case "max": return fmt.Sprintf("%s长度不能超过%s位", fieldDisplayName, param) case "len": return fmt.Sprintf("%s长度必须为%s位", fieldDisplayName, param) case "eqfield": paramDisplayName := v.getFieldDisplayName(param) return fmt.Sprintf("%s必须与%s一致", fieldDisplayName, paramDisplayName) case "phone": return fmt.Sprintf("%s必须是有效的手机号", fieldDisplayName) case "username": return fmt.Sprintf("%s格式不正确,只能包含字母、数字、下划线,且不能以数字开头", fieldDisplayName) case "strong_password": return fmt.Sprintf("%s强度不足,必须包含大小写字母和数字,且不少于8位", fieldDisplayName) default: // 默认错误消息 return fmt.Sprintf("%s格式不正确", fieldDisplayName) } } // 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()) }