add new api

This commit is contained in:
2025-08-26 14:43:27 +08:00
parent 267ff92998
commit 2a93d120f1
23 changed files with 1503 additions and 39 deletions

View File

@@ -0,0 +1,108 @@
package validator
import (
"testing"
"github.com/go-playground/validator/v10"
)
func TestValidateAuthorizationURL(t *testing.T) {
validate := validator.New()
RegisterCustomValidators(validate)
tests := []struct {
name string
url string
wantErr bool
}{
{
name: "有效的PDF URL",
url: "https://example.com/document.pdf",
wantErr: false,
},
{
name: "有效的JPG URL",
url: "https://example.com/image.jpg",
wantErr: false,
},
{
name: "有效的JPEG URL",
url: "https://example.com/image.jpeg",
wantErr: false,
},
{
name: "有效的PNG URL",
url: "https://example.com/image.png",
wantErr: false,
},
{
name: "有效的BMP URL",
url: "https://example.com/image.bmp",
wantErr: false,
},
{
name: "HTTP协议的PDF URL",
url: "http://example.com/document.pdf",
wantErr: false,
},
{
name: "带查询参数的PDF URL",
url: "https://example.com/document.pdf?version=1.0",
wantErr: false,
},
{
name: "带路径的PDF URL",
url: "https://example.com/files/documents/contract.pdf",
wantErr: false,
},
{
name: "无效的URL格式",
url: "not-a-url",
wantErr: true,
},
{
name: "不支持的文件类型",
url: "https://example.com/document.doc",
wantErr: true,
},
{
name: "不支持的文件类型2",
url: "https://example.com/document.txt",
wantErr: true,
},
{
name: "没有文件扩展名",
url: "https://example.com/document",
wantErr: true,
},
{
name: "FTP协议不支持",
url: "ftp://example.com/document.pdf",
wantErr: true,
},
{
name: "空字符串",
url: "",
wantErr: true,
},
{
name: "大写扩展名",
url: "https://example.com/document.PDF",
wantErr: false,
},
{
name: "混合大小写扩展名",
url: "https://example.com/document.JpG",
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := validate.Var(tt.url, "authorization_url")
if (err != nil) != tt.wantErr {
t.Errorf("validateAuthorizationURL() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

View File

@@ -15,70 +15,79 @@ import (
func RegisterCustomValidators(validate *validator.Validate) {
// 手机号验证器
validate.RegisterValidation("phone", validatePhone)
// 用户名验证器字母开头允许字母数字下划线3-20位
validate.RegisterValidation("username", validateUsername)
// 强密码验证器至少8位包含大小写字母和数字
validate.RegisterValidation("strong_password", validateStrongPassword)
// 统一社会信用代码验证器
validate.RegisterValidation("social_credit_code", validateSocialCreditCode)
// 姓名验证器不能为空字符串长度1-50字符
validate.RegisterValidation("validName", validateName)
// 身份证号验证器(兼容两种标签)
validate.RegisterValidation("validIDCard", validateIDCard)
validate.RegisterValidation("id_card", validateIDCard)
// 统一社会信用代码验证器
validate.RegisterValidation("validUSCI", validateUSCI)
// 手机号验证器
validate.RegisterValidation("validMobileNo", validateMobileNo)
// 手机类型验证器
validate.RegisterValidation("validMobileType", validateMobileType)
// 日期验证器
validate.RegisterValidation("validDate", validateDate)
// 时间范围验证器
validate.RegisterValidation("validTimeRange", validateTimeRange)
// 银行卡验证器
validate.RegisterValidation("validBankCard", validateBankCard)
// 价格验证器(非负数)
validate.RegisterValidation("price", validatePrice)
// 排序方向验证器
validate.RegisterValidation("sort_order", validateSortOrder)
// 产品代码验证器字母数字下划线连字符3-50位
validate.RegisterValidation("product_code", validateProductCode)
// UUID验证器
validate.RegisterValidation("uuid", validateUUID)
// URL验证器
validate.RegisterValidation("url", validateURL)
// 企业邮箱验证器
validate.RegisterValidation("enterprise_email", validateEnterpriseEmail)
// 企业地址验证器
validate.RegisterValidation("enterprise_address", validateEnterpriseAddress)
// IP地址验证器
validate.RegisterValidation("ip", validateIP)
// 非空字符串验证器(不能为空字符串或只包含空格)
validate.RegisterValidation("notEmpty", validateNotEmpty)
// 授权日期验证器
validate.RegisterValidation("auth_date", validateAuthDate)
// 授权书URL验证器
validate.RegisterValidation("authorization_url", validateAuthorizationURL)
// 唯一标识验证器小于等于32位字符串
validate.RegisterValidation("validUniqueID", validateUniqueID)
// 回调地址验证器
validate.RegisterValidation("validReturnURL", validateReturnURL)
}
// validatePhone 手机号验证
@@ -195,7 +204,7 @@ func validateName(fl validator.FieldLevel) bool {
// 必须包含至少一个中文字符或英文字母
hasValidChar := regexp.MustCompile(`[\p{Han}a-zA-Z]`).MatchString(trimmedName)
return hasValidChar
}
}
// validateAuthDate 授权日期验证器
// 格式YYYYMMDD-YYYYMMDD之前的日期范围必须包括今天
@@ -267,7 +276,7 @@ func parseYYYYMMDD(dateStr string) (time.Time, error) {
// 验证日期有效性
date := time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.UTC)
// 检查解析后的日期是否与输入一致防止无效日期如20230230
expectedDateStr := date.Format("20060102")
if expectedDateStr != dateStr {
@@ -315,7 +324,7 @@ func validateDate(fl validator.FieldLevel) bool {
if !matched {
return false
}
// 尝试解析日期
_, err := time.Parse("2006-01-02", dateStr)
return err == nil
@@ -327,29 +336,29 @@ func validateTimeRange(fl validator.FieldLevel) bool {
if timeRange == "" {
return true // 空值由omitempty标签处理
}
// 时间范围格式HH:MM-HH:MM
parts := strings.Split(timeRange, "-")
if len(parts) != 2 {
return false
}
startTime := parts[0]
endTime := parts[1]
// 检查时间格式HH:MM
timePattern := `^([01]?[0-9]|2[0-3]):[0-5][0-9]$`
startMatched, _ := regexp.MatchString(timePattern, startTime)
endMatched, _ := regexp.MatchString(timePattern, endTime)
if !startMatched || !endMatched {
return false
}
// 检查开始时间不能晚于结束时间
start, _ := time.Parse("15:04", startTime)
end, _ := time.Parse("15:04", endTime)
return start.Before(end) || start.Equal(end)
}
@@ -369,7 +378,7 @@ func validateBankCard(fl validator.FieldLevel) bool {
if !matched {
return false
}
// 使用Luhn算法验证银行卡号
return validateLuhn(bankCard)
}
@@ -378,24 +387,104 @@ func validateBankCard(fl validator.FieldLevel) bool {
func validateLuhn(cardNumber string) bool {
sum := 0
alternate := false
// 从右到左遍历
for i := len(cardNumber) - 1; i >= 0; i-- {
digit, err := strconv.Atoi(string(cardNumber[i]))
if err != nil {
return false
}
if alternate {
digit *= 2
if digit > 9 {
digit = digit%10 + digit/10
}
}
sum += digit
alternate = !alternate
}
return sum%10 == 0
}
}
// validateAuthorizationURL 授权书URL验证器
func validateAuthorizationURL(fl validator.FieldLevel) bool {
urlStr := fl.Field().String()
if urlStr == "" {
return true // 空值由required标签处理
}
// 解析URL
parsedURL, err := url.Parse(urlStr)
if err != nil {
return false
}
// 检查协议
if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" {
return false
}
// 检查文件扩展名
path := parsedURL.Path
validExtensions := []string{".pdf", ".jpg", ".jpeg", ".png", ".bmp"}
hasValidExtension := false
for _, ext := range validExtensions {
if strings.HasSuffix(strings.ToLower(path), ext) {
hasValidExtension = true
break
}
}
return hasValidExtension
}
// validateUniqueID 唯一标识验证器小于等于32位字符串
func validateUniqueID(fl validator.FieldLevel) bool {
uniqueID := fl.Field().String()
if uniqueID == "" {
return true // 空值由required标签处理
}
// 检查长度小于等于32位
if len(uniqueID) > 32 {
return false
}
// 检查是否只包含允许的字符:字母、数字、下划线、连字符
matched, _ := regexp.MatchString(`^[a-zA-Z0-9_-]+$`, uniqueID)
return matched
}
// validateReturnURL 回调地址验证器
func validateReturnURL(fl validator.FieldLevel) bool {
returnURL := fl.Field().String()
if returnURL == "" {
return true // 空值由required标签处理
}
// 检查长度不能超过500字符
if len(returnURL) > 500 {
return false
}
// 检查URL格式
parsedURL, err := url.Parse(returnURL)
if err != nil {
return false
}
// 检查协议只允许http和https
if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" {
return false
}
// 检查是否有域名
if parsedURL.Host == "" {
return false
}
return true
}

View File

@@ -194,6 +194,30 @@ func registerCustomFieldTranslations(validate *validator.Validate, trans ut.Tran
t, _ := ut.T("auth_date", getFieldDisplayName(fe.Field()))
return t
})
// 授权书URL翻译
validate.RegisterTranslation("authorization_url", trans, func(ut ut.Translator) error {
return ut.Add("authorization_url", "{0}必须是有效的URL地址且文件类型必须是PDF、JPG、JPEG、PNG或BMP格式", true)
}, func(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T("authorization_url", getFieldDisplayName(fe.Field()))
return t
})
// 唯一标识翻译
validate.RegisterTranslation("validUniqueID", trans, func(ut ut.Translator) error {
return ut.Add("validUniqueID", "{0}格式不正确只能包含字母、数字、下划线和连字符且长度不能超过32位", true)
}, func(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T("validUniqueID", getFieldDisplayName(fe.Field()))
return t
})
// 回调地址翻译
validate.RegisterTranslation("validReturnURL", trans, func(ut ut.Translator) error {
return ut.Add("validReturnURL", "{0}必须是有效的URL地址且长度不能超过500字符", true)
}, func(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T("validReturnURL", getFieldDisplayName(fe.Field()))
return t
})
}
// getFieldDisplayName 获取字段显示名称(中文)
@@ -210,7 +234,10 @@ func getFieldDisplayName(field string) string {
"email": "邮箱",
"enterprise_email": "企业邮箱",
"enterprise_address": "企业地址",
"ip_address": "IP地址",
"ip": "IP地址",
"auth_date": "授权日期",
"unique_id": "唯一标识",
"return_url": "回调地址",
"display_name": "显示名称",
"scene": "使用场景",
"Password": "密码",
@@ -252,6 +279,8 @@ func getFieldDisplayName(field string) string {
"VerificationCode": "验证码",
"contract_url": "合同URL",
"ContractURL": "合同URL",
"authorization_url": "授权书地址",
"AuthorizationURL": "授权书地址",
"amount": "金额",
"Amount": "金额",
"balance": "余额",
@@ -279,8 +308,6 @@ func getFieldDisplayName(field string) string {
"ID": "ID",
"ids": "ID列表",
"IDs": "ID列表",
"auth_date": "授权日期",
"AuthDate": "授权日期",
"id_card": "身份证号",
"IDCard": "身份证号",
}