From 7d4b9510a234a3fbbae2e2d57dde8828beefba32 Mon Sep 17 00:00:00 2001 From: dudaodong Date: Thu, 21 Aug 2025 14:13:43 +0800 Subject: [PATCH] feat: add IsChineseHMPassport --- cryptor/rsa_private_example.pem | 98 ++++++++++++++--------------- cryptor/rsa_public_example.pem | 24 +++---- validator/validator.go | 87 +++++++++++++++++++------ validator/validator_example_test.go | 21 +++++++ validator/validator_test.go | 62 +++++++++++++++++- 5 files changed, 209 insertions(+), 83 deletions(-) diff --git a/cryptor/rsa_private_example.pem b/cryptor/rsa_private_example.pem index 1b8ec2a..c613bba 100644 --- a/cryptor/rsa_private_example.pem +++ b/cryptor/rsa_private_example.pem @@ -1,51 +1,51 @@ -----BEGIN rsa private key----- -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 -----END rsa private key----- diff --git a/cryptor/rsa_public_example.pem b/cryptor/rsa_public_example.pem index 8032dac..2f8ba82 100644 --- a/cryptor/rsa_public_example.pem +++ b/cryptor/rsa_public_example.pem @@ -1,14 +1,14 @@ -----BEGIN rsa public key----- -MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAt8K0R1gvIA4MAX0lpFtV -hcZQaGd5/4I2W+LESIO/7o1PWh2LodghN4OFF0Wfg+/TBZXXAfzCH5MlLtu7w89f -ee/vLMUpRqTxIR26pybgKKi3RUEQb69PMuq8fFEpNwYc5n+rFNg531Jn/qylDnMS -pfTn2qHNrLUnMOMR+hnAh3yBJpAx61V/8BlFuCUx/99r3uZY0xKA7Hr7OlEvCsvg -uAzHhPPnhTf37zsiebpoWluTadq9XO2XfBKcIb/MXzOhb+fR9mqJ3ajy8xGhtI3H -QK1wfjF1c/uzyatjDHfzpCTF00v6k3pEH646sHJjgzCs+ZT4wXZU2YiIyrFpr64R -vpBaERXBjBe2jlEC9fKnFCYfkBSW3oYHi1Mh5C7DW/ttzKBjVyS8sBF2L9X+PoRJ -QNH7yp4kED18XQwrzuLaJ8xJzQNnvVjWxzPeXngmX+CIIlNCt3zc2hLPTteqbpmK -VuZusrSz4O8d6Qqwo4ofhj/gz67v31lsrOCOc5tkuXRfikUHGbEOE7S2zfSlh2wA -sgSX/LjQZH6WHyZqLJIVTcrccouZDkCBbQhuGJMTA2VOUZkKPB8RJ8Fw3su/ge9N -dig8YNSAjbWFFSFl1tyncWuadS046IXVLF80a/z66i8OOQ6D/uUjGWWKIbUFp/w3 -hOlLNXLCnCRV/LJ1L4UmzusCAwEAAQ== +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxBXtNRZ18uz+ujnKC/CB +6bYjOHn0++xX1tbqRoRfP4VP01R/k9d28sI9cqQa9Oqo3TPehKAyR4m3p9ZJ+pOg +NLpe7QEPWSGqIvPILRqS/4NnnjBuKvRHRTqiQwiuzTo/BBTxJCtF028Q7yRKmloE +dtrdFryqsZmTrtMIvaNneTa/Bnx33s4vfAm76wNKF5li0AN7NsmKfWjOG7/f29qp +9rTJEBnCIKImrwTFY9BWrx83LPDAUq8tpn8zevEe0c/42yZNlBprea4FIdZs8A/3 +gDZE7kltYjdybox0XWjR9znWPLmH3De+1xJ0woJ53f3jYDJ3REwmm8+sX26FIT3W +PN/ksVgHsoaj2IZ3MaEkxf4E4GEp9sMSP8b+ans/B5gmcZi2pO8nx/ASVlxCthUS +aK92CsoKHzqTotaZ+/dt689qT4zuqUWZpnlLIUKd0t1G485S8km15GgPcMY5vXe1 +3i8dmsxVRIjgYclSClKCnDBTTAV5GUSPnanPU4N0dlKpO0UxzILixaw227yw7zDy ++rQ8ixchQ+ROQ6I4bdced7FuVXfEAa2gVuzm3uZ1J/xDnV6xwLYye2SitlzAL0wA +/mFAF6koSa6+MYV+tzdPta2Xkw9xmO3a5PKwhCvOUbEMBJfLI2GPAfTuPh1LH4MZ +qFUgzYHUiBCI21N3kMVPGAUCAwEAAQ== -----END rsa public key----- diff --git a/validator/validator.go b/validator/validator.go index 744e975..5c80737 100644 --- a/validator/validator.go +++ b/validator/validator.go @@ -27,21 +27,22 @@ var ( urlMatcher *regexp.Regexp = regexp.MustCompile(`^((ftp|http|https?):\/\/)?(\S+(:\S*)?@)?((([1-9]\d?|1\d\d|2[01]\d|22[0-3])(\.(1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.([0-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(([a-zA-Z0-9]+([-\.][a-zA-Z0-9]+)*)|((www\.)?))?(([a-z\x{00a1}-\x{ffff}0-9]+-?-?)*[a-z\x{00a1}-\x{ffff}0-9]+)(?:\.([a-z\x{00a1}-\x{ffff}]{2,}))?))(:(\d{1,5}))?((\/|\?|#)[^\s]*)?$`) dnsMatcher *regexp.Regexp = regexp.MustCompile(`^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$`) // emailMatcher *regexp.Regexp = regexp.MustCompile(`^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,4}$`) - emailMatcher *regexp.Regexp = regexp.MustCompile(`\w+(-+.\w+)*@\w+(-.\w+)*.\w+(-.\w+)*`) - chineseMobileMatcher *regexp.Regexp = regexp.MustCompile(`^1(?:3\d|4[4-9]|5[0-35-9]|6[67]|7[013-8]|8\d|9\d)\d{8}$`) - chineseIdMatcher *regexp.Regexp = regexp.MustCompile(`([1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx])|([1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{2}[0-9Xx])`) - chineseMatcher *regexp.Regexp = regexp.MustCompile("[\u4e00-\u9fa5]") - chinesePhoneMatcher *regexp.Regexp = regexp.MustCompile(`\d{3}-\d{8}|\d{4}-\d{7}|\d{4}-\d{8}`) - creditCardMatcher *regexp.Regexp = regexp.MustCompile(`^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|(222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11}|6[27][0-9]{14})$`) - base64Matcher *regexp.Regexp = regexp.MustCompile(`^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$`) - base64URLMatcher *regexp.Regexp = regexp.MustCompile(`^([A-Za-z0-9_-]{4})*([A-Za-z0-9_-]{2}(==)?|[A-Za-z0-9_-]{3}=?)?$`) - binMatcher *regexp.Regexp = regexp.MustCompile(`^(0b)?[01]+$`) - hexMatcher *regexp.Regexp = regexp.MustCompile(`^(#|0x|0X)?[0-9a-fA-F]+$`) - visaMatcher *regexp.Regexp = regexp.MustCompile(`^4[0-9]{12}(?:[0-9]{3})?$`) - masterCardMatcher *regexp.Regexp = regexp.MustCompile(`^5[1-5][0-9]{14}$`) - americanExpressMatcher *regexp.Regexp = regexp.MustCompile(`^3[47][0-9]{13}$`) - unionPayMatcher *regexp.Regexp = regexp.MustCompile(`^62[0-5]\\d{13,16}$`) - chinaUnionPayMatcher *regexp.Regexp = regexp.MustCompile(`^62[0-9]{14,17}$`) + emailMatcher *regexp.Regexp = regexp.MustCompile(`\w+(-+.\w+)*@\w+(-.\w+)*.\w+(-.\w+)*`) + chineseMobileMatcher *regexp.Regexp = regexp.MustCompile(`^1(?:3\d|4[4-9]|5[0-35-9]|6[67]|7[013-8]|8\d|9\d)\d{8}$`) + chineseIdMatcher *regexp.Regexp = regexp.MustCompile(`([1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx])|([1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{2}[0-9Xx])`) + chineseMatcher *regexp.Regexp = regexp.MustCompile("[\u4e00-\u9fa5]") + chinesePhoneMatcher *regexp.Regexp = regexp.MustCompile(`\d{3}-\d{8}|\d{4}-\d{7}|\d{4}-\d{8}`) + creditCardMatcher *regexp.Regexp = regexp.MustCompile(`^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|(222[1-9]|22[3-9][0-9]|2[3-6][0-9]{2}|27[01][0-9]|2720)[0-9]{12}|6(?:011|5[0-9][0-9])[0-9]{12}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11}|6[27][0-9]{14})$`) + base64Matcher *regexp.Regexp = regexp.MustCompile(`^(?:[A-Za-z0-9+\\/]{4})*(?:[A-Za-z0-9+\\/]{2}==|[A-Za-z0-9+\\/]{3}=|[A-Za-z0-9+\\/]{4})$`) + base64URLMatcher *regexp.Regexp = regexp.MustCompile(`^([A-Za-z0-9_-]{4})*([A-Za-z0-9_-]{2}(==)?|[A-Za-z0-9_-]{3}=?)?$`) + binMatcher *regexp.Regexp = regexp.MustCompile(`^(0b)?[01]+$`) + hexMatcher *regexp.Regexp = regexp.MustCompile(`^(#|0x|0X)?[0-9a-fA-F]+$`) + visaMatcher *regexp.Regexp = regexp.MustCompile(`^4[0-9]{12}(?:[0-9]{3})?$`) + masterCardMatcher *regexp.Regexp = regexp.MustCompile(`^5[1-5][0-9]{14}$`) + americanExpressMatcher *regexp.Regexp = regexp.MustCompile(`^3[47][0-9]{13}$`) + unionPayMatcher *regexp.Regexp = regexp.MustCompile(`^62[0-5]\\d{13,16}$`) + chinaUnionPayMatcher *regexp.Regexp = regexp.MustCompile(`^62[0-9]{14,17}$`) + chineseHMPassportMatcher *regexp.Regexp = regexp.MustCompile(`^[CM]\d{8}$`) ) var passportMatcher = map[string]*regexp.Regexp{ @@ -601,14 +602,55 @@ func IsAmericanExpress(v string) bool { // IsUnionPay check if a give string is a valid union pay nubmer or not. // Play: https://go.dev/play/p/CUHPEwEITDf -func IsUnionPay(v string) bool { - return unionPayMatcher.MatchString(v) +func IsUnionPay(cardNo string) bool { + if len(cardNo) < 16 || len(cardNo) > 19 { + return false + } + + matched, _ := regexp.MatchString(`^\d+$`, cardNo) + if !matched { + return false + } + + if len(cardNo) < 3 { + return false + } + + prefix := cardNo[:3] + prefixNum, err := strconv.Atoi(prefix) + if err != nil { + return false + } + + if prefixNum < 620 || prefixNum > 625 { + return false + } + + return true } // IsChinaUnionPay check if a give string is a valid china union pay nubmer or not. // Play: https://go.dev/play/p/yafpdxLiymu -func IsChinaUnionPay(v string) bool { - return chinaUnionPayMatcher.MatchString(v) +func IsChinaUnionPay(cardNo string) bool { + return chinaUnionPayMatcher.MatchString(cardNo) +} + +// luhnCheck checks if the credit card number is valid using the Luhn algorithm. +func luhnCheck(card string) bool { + var sum int + alt := false + for i := len(card) - 1; i >= 0; i-- { + n := int(card[i] - '0') + if alt { + n *= 2 + if n > 9 { + n -= 9 + } + } + sum += n + alt = !alt + } + return sum%10 == 0 } // IsPassport checks if the passport number is valid for a given country. @@ -621,3 +663,10 @@ func IsPassport(passport, country string) bool { return false } + +// IsChineseHMPassport checks if the string is a valid Chinese Hong Kong and Macau Travel Permit number. +// Chinese Hong Kong and Macau Travel Permit format: C or M + 8 digits (e.g., C12345678, M12345678). +// Play: https://go.dev/play/p/TODO +func IsChineseHMPassport(hmPassport string) bool { + return chineseHMPassportMatcher.MatchString(hmPassport) +} diff --git a/validator/validator_example_test.go b/validator/validator_example_test.go index 829095b..65622f9 100644 --- a/validator/validator_example_test.go +++ b/validator/validator_example_test.go @@ -701,3 +701,24 @@ func ExampleIsPassport() { // true // false } + +func ExampleIsChineseHMPassport() { + result1 := IsChineseHMPassport("C12345678") + result2 := IsChineseHMPassport("C00000000") + result3 := IsChineseHMPassport("M12345678") + result4 := IsChineseHMPassport("c12345678") + result5 := IsChineseHMPassport("C1234567") + + fmt.Println(result1) + fmt.Println(result2) + fmt.Println(result3) + fmt.Println(result4) + fmt.Println(result5) + + // Output: + // true + // true + // true + // false + // false +} diff --git a/validator/validator_test.go b/validator/validator_test.go index 3c690b5..842753d 100644 --- a/validator/validator_test.go +++ b/validator/validator_test.go @@ -933,7 +933,7 @@ func TestIsUnionPay(t *testing.T) { t.Parallel() assert := internal.NewAssert(t, "TestIsUnionPay") - assert.Equal(true, IsUnionPay("6221263430109903")) + assert.Equal(true, IsUnionPay("6228480402564890")) assert.Equal(false, IsUnionPay("3782822463100007")) } @@ -941,8 +941,25 @@ func TestIsChinaUnionPay(t *testing.T) { t.Parallel() assert := internal.NewAssert(t, "TestIsChinaUnionPay") - assert.Equal(true, IsChinaUnionPay("6250941006528599")) - assert.Equal(false, IsChinaUnionPay("3782822463100007")) + tests := []struct { + cardNumber string + expected bool + }{ + {"6228480420000000000", true}, + {"6214830000000000", true}, + {"6230580000000000000", true}, + {"6259640000000000000", true}, + {"6260000000000000000", true}, + {"6288888888888888", true}, + + // 非银联前缀 + {"4123456789012345", false}, + {"3528000000000000", false}, + } + + for _, tt := range tests { + assert.Equal(tt.expected, IsChinaUnionPay(tt.cardNumber)) + } } func TestIsAlphaNumeric(t *testing.T) { @@ -1000,3 +1017,42 @@ func TestIsPassport(t *testing.T) { assert.Equal(tt.expected, IsPassport(tt.passport, tt.countryCode)) } } + +func TestIsChineseHMPassport(t *testing.T) { + t.Parallel() + + assert := internal.NewAssert(t, "TestIsChineseHMPassport") + + tests := []struct { + input string + expected bool + }{ + {"C12345678", true}, + {"C00000000", true}, + {"C99999999", true}, + {"M12345678", true}, // M prefix + {"M00000000", true}, // M prefix + {"M99999999", true}, // M prefix + {"c12345678", false}, // lowercase c + {"m12345678", false}, // lowercase m + {"C1234567", false}, // 7 digits + {"M1234567", false}, // 7 digits with M + {"C123456789", false}, // 9 digits + {"M123456789", false}, // 9 digits with M + {"C1234567a", false}, // contains letter + {"M1234567a", false}, // contains letter with M + {"D12345678", false}, // starts with D + {"12345678", false}, // no prefix + {"CC12345678", false}, // double C + {"MM12345678", false}, // double M + {"C 12345678", false}, // contains space + {"M 12345678", false}, // contains space with M + {"C12345-678", false}, // contains dash + {"M12345-678", false}, // contains dash with M + {"", false}, + } + + for _, tt := range tests { + assert.Equal(tt.expected, IsChineseHMPassport(tt.input)) + } +}