Files
tyapi-server/internal/shared/http/validator_zh.go
2025-07-11 21:05:58 +08:00

237 lines
7.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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())
}