diff --git a/internal/domains/api/dto/api_request_dto.go b/internal/domains/api/dto/api_request_dto.go index b0cba38..99b6980 100644 --- a/internal/domains/api/dto/api_request_dto.go +++ b/internal/domains/api/dto/api_request_dto.go @@ -105,13 +105,13 @@ type QYGL6F2DReq struct { IDCard string `json:"id_card" validate:"required,validIDCard"` } type QYGL45BDReq struct { - EntName string `json:"ent_name" validate:"required,min=1,validName"` + EntName string `json:"ent_name" validate:"required,min=1,validEnterpriseName"` LegalPerson string `json:"legal_person" validate:"required,min=1,validName"` EntCode string `json:"ent_code" validate:"required,validUSCI"` IDCard string `json:"id_card" validate:"required,validIDCard"` } type QYGL8261Req struct { - EntName string `json:"ent_name" validate:"required,min=1,validName"` + EntName string `json:"ent_name" validate:"required,min=1,validEnterpriseName"` } type QYGL8271Req struct { EntName string `json:"ent_name" validate:"required,min=1,validName"` @@ -221,7 +221,7 @@ type QCXG7A2BReq struct { } type COMENT01Req struct { - EntName string `json:"ent_name" validate:"required,min=1,validName"` + EntName string `json:"ent_name" validate:"required,min=1,validEnterpriseName"` EntCode string `json:"ent_code" validate:"required,validUSCI"` } diff --git a/internal/shared/crypto/west_crypto_test.go b/internal/shared/crypto/west_crypto_test.go new file mode 100644 index 0000000..9534665 --- /dev/null +++ b/internal/shared/crypto/west_crypto_test.go @@ -0,0 +1,213 @@ +package crypto + +import ( + "testing" +) + +func TestWestDexEncryptDecrypt(t *testing.T) { + testCases := []struct { + name string + data string + secretKey string + }{ + { + name: "简单文本", + data: "hello world", + secretKey: "mySecretKey123", + }, + { + name: "中文文本", + data: "你好世界", + secretKey: "中文密钥", + }, + { + name: "JSON数据", + data: `{"name":"张三","age":30,"city":"北京"}`, + secretKey: "jsonSecretKey", + }, + { + name: "长文本", + data: "这是一个很长的文本,用来测试加密解密功能是否正常工作。包含各种字符:123456789!@#$%^&*()_+-=[]{}|;':\",./<>?", + secretKey: "longTextKey", + }, + { + name: "空字符串", + data: "", + secretKey: "emptyDataKey", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // 加密 + encrypted, err := WestDexEncrypt(tc.data, tc.secretKey) + if err != nil { + t.Fatalf("加密失败: %v", err) + } + + t.Logf("原始数据: %s", tc.data) + t.Logf("密钥: %s", tc.secretKey) + t.Logf("加密结果: %s", encrypted) + + // 解密 + decrypted, err := WestDexDecrypt(encrypted, tc.secretKey) + if err != nil { + t.Fatalf("解密失败: %v", err) + } + + decryptedStr := string(decrypted) + t.Logf("解密结果: %s", decryptedStr) + + // 验证解密结果是否与原始数据一致 + if decryptedStr != tc.data { + t.Errorf("解密结果不匹配: 期望 %s, 实际 %s", tc.data, decryptedStr) + } + }) + } +} + +func TestWestDexDecryptOutput(t *testing.T) { + // 专门用来查看解密结果的测试 + testData := []struct { + name string + data string + secretKey string + encryptedData string // 预设的加密数据 + }{ + { + name: "测试数据1", + data: "DLrbtEki5o/5yTvQWR+dWWUZYEo5s58D8LTnhhlAl99SwZbECa34KpStmR+Qr0gbbKzh3y4t5+/vbFFZgv03DtnYlLQcQt+rSgtxkCN/PCBPaFE0QZRTufd7djJfUww0Eh6DMHD7NS9pcuCa0PHGVoE+Vwo2YSwOnh2gtx3Bt0Qhs+w76tfCwIeufZ8tcpFs/nb84HIZxk+0cH1bTfNE6VsXI6vMpKvnS02O3oE2642ozeHgglCNuiOFMcCL8Erw4FKPnfRCUYdeKc2dZ7OF2IZqt0t4WiJBxjB/6k4tgAj/HepE2gaulWU8RVvAF+vPF5i3ekHHq8T7226rNlVfuagodaRXiOqO5E1h6Mx9ygcDL0HXvQKsxxJdl/bUP+t/+rOjA+k/IR/vF1UJGrGrkSJVfkcWXPP85cgws18gE9rIs2Ji1HGjvOmnez370L0+", + secretKey: "121a1e41fc1690dd6b90afbcacd80cf4", + }, + { + name: "中文数据", + data: "用户数据", + secretKey: "密钥123", + }, + { + name: "API数据", + data: "api_call_data", + secretKey: "production_key", + }, + { + name: "JSON格式", + data: `{"user_id":12345,"name":"张三","status":"active"}`, + secretKey: "json_key", + }, + } + + for i, td := range testData { + decrypted, err := WestDexDecrypt(td.data, td.secretKey) + if err != nil { + t.Fatalf("解密失败: %v", err) + } + + t.Logf("测试 %d - %s:", i+1, td.name) + t.Logf(" 原始数据: %s", td.data) + t.Logf(" 使用密钥: %s", td.secretKey) + t.Logf(" 解密结果: %s", string(decrypted)) + t.Logf(" 解密正确: %v", string(decrypted) == td.data) + t.Log("---") + } +} + +func TestSpecificDecrypt(t *testing.T) { + // 如果你有特定的加密数据想要解密,可以在这里测试 + specificTests := []struct { + name string + encryptedData string + secretKey string + expectedData string // 如果知道预期结果的话 + }{ + // 示例:如果你有具体的加密数据想要解密,可以添加到这里 + // { + // name: "特定数据解密", + // encryptedData: "你的加密数据", + // secretKey: "你的密钥", + // expectedData: "预期的解密结果", + // }, + } + + t.Log("=== 特定数据解密测试 ===") + for _, test := range specificTests { + decrypted, err := WestDexDecrypt(test.encryptedData, test.secretKey) + if err != nil { + t.Logf("%s - 解密失败: %v", test.name, err) + continue + } + + result := string(decrypted) + t.Logf("%s:", test.name) + t.Logf(" 加密数据: %s", test.encryptedData) + t.Logf(" 使用密钥: %s", test.secretKey) + t.Logf(" 解密结果: %s", result) + + if test.expectedData != "" { + t.Logf(" 预期结果: %s", test.expectedData) + t.Logf(" 解密正确: %v", result == test.expectedData) + } + t.Log("---") + } +} + +func TestWestDexDecryptWithWrongKey(t *testing.T) { + // 测试用错误密钥解密 + data := "sensitive data" + correctKey := "correct_key" + wrongKey := "wrong_key" + + // 用正确密钥加密 + encrypted, err := WestDexEncrypt(data, correctKey) + if err != nil { + t.Fatalf("加密失败: %v", err) + } + + // 用错误密钥解密 + decrypted, err := WestDexDecrypt(encrypted, wrongKey) + if err != nil { + t.Logf("用错误密钥解密失败(这是预期的): %v", err) + return + } + + decryptedStr := string(decrypted) + t.Logf("原始数据: %s", data) + t.Logf("用错误密钥解密结果: %s", decryptedStr) + + // 验证解密结果应该与原始数据不同 + if decryptedStr == data { + t.Error("用错误密钥解密不应该得到正确结果") + } +} + +// 基准测试 +func BenchmarkWestDexEncrypt(b *testing.B) { + data := "这是一个用于基准测试的数据字符串" + secretKey := "benchmarkKey" + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := WestDexEncrypt(data, secretKey) + if err != nil { + b.Fatalf("加密失败: %v", err) + } + } +} + +func BenchmarkWestDexDecrypt(b *testing.B) { + data := "这是一个用于基准测试的数据字符串" + secretKey := "benchmarkKey" + + // 先加密一次获得密文 + encrypted, err := WestDexEncrypt(data, secretKey) + if err != nil { + b.Fatalf("预加密失败: %v", err) + } + + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := WestDexDecrypt(encrypted, secretKey) + if err != nil { + b.Fatalf("解密失败: %v", err) + } + } +} diff --git a/internal/shared/validator/custom_validators.go b/internal/shared/validator/custom_validators.go index 0976cf1..d9790cb 100644 --- a/internal/shared/validator/custom_validators.go +++ b/internal/shared/validator/custom_validators.go @@ -89,6 +89,10 @@ func RegisterCustomValidators(validate *validator.Validate) { // 回调地址验证器 validate.RegisterValidation("validReturnURL", validateReturnURL) + + // 企业名称验证器 + validate.RegisterValidation("enterprise_name", validateEnterpriseName) + validate.RegisterValidation("validEnterpriseName", validateEnterpriseName) } // validatePhone 手机号验证 @@ -490,6 +494,76 @@ func validateReturnURL(fl validator.FieldLevel) bool { return true } +// validateEnterpriseName 企业名称验证器 +func validateEnterpriseName(fl validator.FieldLevel) bool { + enterpriseName := fl.Field().String() + + // 去除首尾空格 + trimmedName := strings.TrimSpace(enterpriseName) + if trimmedName == "" { + return false + } + + // 长度验证:2-100个字符 + if len(trimmedName) < 2 || len(trimmedName) > 100 { + return false + } + + // 检查是否包含非法字符(允许中英文括号) + invalidChars := []string{ + "`", "~", "!", "@", "#", "$", "%", "^", "&", "*", + "+", "=", "{", "}", "[", "]", "\\", "|", ";", ":", "'", "\"", "<", ">", ",", ".", "?", "/", + } + for _, char := range invalidChars { + if strings.Contains(trimmedName, char) { + return false + } + } + + // 必须包含至少一个中文字符或英文字母 + hasValidChar := regexp.MustCompile(`[\p{Han}a-zA-Z]`).MatchString(trimmedName) + if !hasValidChar { + return false + } + + // 验证企业名称的基本格式(支持各种类型的企业) + // 支持:有限公司、股份有限公司、工作室、个体工商户、合伙企业等 + validSuffixes := []string{ + "有限公司", "有限责任公司", "股份有限公司", "股份公司", + "工作室", "个体工商户", "个人独资企业", "合伙企业", + "集团有限公司", "集团股份有限公司", + "Co.,Ltd", "Co., Ltd", "Ltd", "LLC", "Inc", "Corp", + "Company", "Studio", "Workshop", "Enterprise", + } + + // 检查是否以合法的企业类型结尾(不强制要求,因为有些企业名称可能没有标准后缀) + // 但如果有后缀,必须是合法的 + hasValidSuffix := false + for _, suffix := range validSuffixes { + if strings.HasSuffix(trimmedName, suffix) { + hasValidSuffix = true + break + } + } + + // 如果名称中包含常见的企业类型关键词,则必须是合法的后缀 + enterpriseKeywords := []string{"公司", "工作室", "企业", "集团", "Co", "Ltd", "LLC", "Inc", "Corp", "Company", "Studio", "Workshop", "Enterprise"} + containsKeyword := false + for _, keyword := range enterpriseKeywords { + if strings.Contains(trimmedName, keyword) { + containsKeyword = true + break + } + } + + // 如果包含企业关键词但没有合法后缀,则验证失败 + if containsKeyword && !hasValidSuffix { + return false + } + + return true +} + // ================ 统一的业务校验方法 ================ // ValidatePhone 验证手机号 @@ -694,6 +768,80 @@ func ValidateSliceNotEmpty(slice interface{}, fieldName string) error { return nil } +// ValidateEnterpriseName 验证企业名称 +func ValidateEnterpriseName(enterpriseName string) error { + if enterpriseName == "" { + return fmt.Errorf("企业名称不能为空") + } + + // 去除首尾空格 + trimmedName := strings.TrimSpace(enterpriseName) + if trimmedName == "" { + return fmt.Errorf("企业名称不能为空") + } + + // 长度验证:2-100个字符 + if len(trimmedName) < 2 { + return fmt.Errorf("企业名称长度不能少于2个字符") + } + if len(trimmedName) > 100 { + return fmt.Errorf("企业名称长度不能超过100个字符") + } + + // 检查是否包含非法字符(允许中英文括号) + invalidChars := []string{ + "`", "~", "!", "@", "#", "$", "%", "^", "&", "*", + "+", "=", "{", "}", "[", "]", "\\", "|", ";", ":", "'", "\"", "<", ">", ",", ".", "?", "/", + } + for _, char := range invalidChars { + if strings.Contains(trimmedName, char) { + return fmt.Errorf("企业名称不能包含特殊字符: %s", char) + } + } + + // 必须包含至少一个中文字符或英文字母 + hasValidChar := regexp.MustCompile(`[\p{Han}a-zA-Z]`).MatchString(trimmedName) + if !hasValidChar { + return fmt.Errorf("企业名称必须包含中文字符或英文字母") + } + + // 验证企业名称的基本格式(支持各种类型的企业) + // 支持:有限公司、股份有限公司、工作室、个体工商户、合伙企业等 + validSuffixes := []string{ + "有限公司", "有限责任公司", "股份有限公司", "股份公司", + "工作室", "个体工商户", "个人独资企业", "合伙企业", + "集团有限公司", "集团股份有限公司", + "Co.,Ltd", "Co., Ltd", "Ltd", "LLC", "Inc", "Corp", + "Company", "Studio", "Workshop", "Enterprise", + } + + // 检查是否以合法的企业类型结尾 + hasValidSuffix := false + for _, suffix := range validSuffixes { + if strings.HasSuffix(trimmedName, suffix) { + hasValidSuffix = true + break + } + } + + // 如果名称中包含常见的企业类型关键词,则必须是合法的后缀 + enterpriseKeywords := []string{"公司", "工作室", "企业", "集团", "Co", "Ltd", "LLC", "Inc", "Corp", "Company", "Studio", "Workshop", "Enterprise"} + containsKeyword := false + for _, keyword := range enterpriseKeywords { + if strings.Contains(trimmedName, keyword) { + containsKeyword = true + break + } + } + + // 如果包含企业关键词但没有合法后缀,则验证失败 + if containsKeyword && !hasValidSuffix { + return fmt.Errorf("企业名称格式不正确,请使用标准的企业类型后缀(如:有限公司、工作室等)") + } + + return nil +} + // ================ 便捷的校验器创建函数 ================ // NewBusinessValidator 创建业务验证器(保持向后兼容) diff --git a/internal/shared/validator/enterprise_name_test.go b/internal/shared/validator/enterprise_name_test.go new file mode 100644 index 0000000..be083a5 --- /dev/null +++ b/internal/shared/validator/enterprise_name_test.go @@ -0,0 +1,135 @@ +package validator + +import ( + "testing" +) + +func TestValidateEnterpriseName(t *testing.T) { + tests := []struct { + name string + input string + expectError bool + errorMsg string + }{ + { + name: "有效的有限公司名称", + input: "北京天远数据科技有限公司", + expectError: false, + }, + { + name: "有效的工作室名称", + input: "张三设计工作室", + expectError: false, + }, + { + name: "有效的英文公司名称", + input: "Apple Inc", + expectError: false, + }, + { + name: "有效的英文有限公司", + input: "Google LLC", + expectError: false, + }, + { + name: "有效的股份有限公司", + input: "中国移动股份有限公司", + expectError: false, + }, + { + name: "有效的个体工商户", + input: "李四个体工商户", + expectError: false, + }, + { + name: "空字符串", + input: "", + expectError: true, + errorMsg: "企业名称不能为空", + }, + { + name: "只有空格", + input: " ", + expectError: true, + errorMsg: "企业名称不能为空", + }, + { + name: "长度过短", + input: "A", + expectError: true, + errorMsg: "企业名称长度不能少于2个字符", + }, + { + name: "包含非法字符", + input: "测试公司@#$", + expectError: true, + errorMsg: "企业名称不能包含特殊字符", + }, + { + name: "包含公司关键词但后缀不合法", + input: "测试公司123", + expectError: true, + errorMsg: "企业名称格式不正确,请使用标准的企业类型后缀", + }, + { + name: "包含工作室关键词但后缀不合法", + input: "设计工作室ABC", + expectError: true, + errorMsg: "企业名称格式不正确,请使用标准的企业类型后缀", + }, + { + name: "只包含数字和特殊字符", + input: "12345-67890", + expectError: true, + errorMsg: "企业名称必须包含中文字符或英文字母", + }, + { + name: "没有企业类型关键词的个人名称", + input: "张三理发店", + expectError: false, // 没有企业关键词,所以不强制要求后缀 + }, + { + name: "包含括号的企业名称", + input: "北京天远数据科技有限公司(分公司)", + expectError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := ValidateEnterpriseName(tt.input) + + if tt.expectError { + if err == nil { + t.Errorf("期望有错误,但没有返回错误") + } else if tt.errorMsg != "" && !containsString(err.Error(), tt.errorMsg) { + t.Errorf("期望错误消息包含 '%s',但得到 '%s'", tt.errorMsg, err.Error()) + } + } else { + if err != nil { + t.Errorf("期望没有错误,但得到错误: %v", err) + } + } + }) + } +} + +// containsString 检查字符串是否包含子字符串 +func containsString(s, substr string) bool { + return len(s) >= len(substr) && + (s == substr || + len(s) > len(substr) && + (s[:len(substr)] == substr || + s[len(s)-len(substr):] == substr || + containsSubstring(s, substr))) +} + +// containsSubstring 辅助函数检查子字符串 +func containsSubstring(s, substr string) bool { + for i := 0; i <= len(s)-len(substr); i++ { + if s[i:i+len(substr)] == substr { + return true + } + } + return false +}