mirror of
https://github.com/duke-git/lancet.git
synced 2026-02-04 12:52:28 +08:00
* fix: 修改 README_zh-CN.md datetime 重复 IsLeapYear问题 * fix: 修改 README_zh-CN.md 18. slice 回到目录 * fix(validator): [IsChineseIdNum] fix 身份证效验 新增 省份、生日、效验码效验,修改ContainLower/IsNumber 注释错误 * fix(validator): [IsChineseIdNum] fix 修改测试 --------- Co-authored-by: dengjiaxiang <19181730103@189.cn> Co-authored-by: dengrandpa <dengrandpapa@outlook.com>
550 lines
15 KiB
Go
550 lines
15 KiB
Go
// Copyright 2021 dudaodong@gmail.com. All rights reserved.
|
|
// Use of this source code is governed by MIT license
|
|
|
|
// Package validator implements some validate function for string.
|
|
package validator
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net"
|
|
"net/url"
|
|
"reflect"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
"unicode"
|
|
)
|
|
|
|
var (
|
|
alphaMatcher *regexp.Regexp = regexp.MustCompile(`^[a-zA-Z]+$`)
|
|
letterRegexMatcher *regexp.Regexp = regexp.MustCompile(`[a-zA-Z]`)
|
|
numberRegexMatcher *regexp.Regexp = regexp.MustCompile(`\d`)
|
|
intStrMatcher *regexp.Regexp = regexp.MustCompile(`^[\+-]?\d+$`)
|
|
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-Z]([a-zA-Z0-9\-]+[\.]?)*[a-zA-Z0-9]$`)
|
|
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(`^(\d{17})([0-9]|X|x)$`)
|
|
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}$`)
|
|
unionPay *regexp.Regexp = regexp.MustCompile("^62[0-5]\\d{13,16}$")
|
|
chinaUnionPay *regexp.Regexp = regexp.MustCompile(`^62[0-9]{14,17}$`)
|
|
)
|
|
|
|
var (
|
|
// Identity card formula
|
|
factor = [17]int{7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2}
|
|
// ID verification bit
|
|
verifyStr = [11]string{"1", "0", "X", "9", "8", "7", "6", "5", "4", "3", "2"}
|
|
// Starting year of ID card
|
|
birthStartYear = 1900
|
|
// Province code
|
|
provinceKv = map[string]struct{}{
|
|
"11": {},
|
|
"12": {},
|
|
"13": {},
|
|
"14": {},
|
|
"15": {},
|
|
"21": {},
|
|
"22": {},
|
|
"23": {},
|
|
"31": {},
|
|
"32": {},
|
|
"33": {},
|
|
"34": {},
|
|
"35": {},
|
|
"36": {},
|
|
"37": {},
|
|
"41": {},
|
|
"42": {},
|
|
"43": {},
|
|
"44": {},
|
|
"45": {},
|
|
"46": {},
|
|
"50": {},
|
|
"51": {},
|
|
"52": {},
|
|
"53": {},
|
|
"54": {},
|
|
"61": {},
|
|
"62": {},
|
|
"63": {},
|
|
"64": {},
|
|
"65": {},
|
|
//"71": {},
|
|
//"81": {},
|
|
//"82": {},
|
|
}
|
|
)
|
|
|
|
// IsAlpha checks if the string contains only letters (a-zA-Z).
|
|
// Play: https://go.dev/play/p/7Q5sGOz2izQ
|
|
func IsAlpha(str string) bool {
|
|
return alphaMatcher.MatchString(str)
|
|
}
|
|
|
|
// IsAllUpper check if the string is all upper case letters A-Z.
|
|
// Play: https://go.dev/play/p/ZHctgeK1n4Z
|
|
func IsAllUpper(str string) bool {
|
|
for _, r := range str {
|
|
if !unicode.IsUpper(r) {
|
|
return false
|
|
}
|
|
}
|
|
return str != ""
|
|
}
|
|
|
|
// IsAllLower check if the string is all lower case letters a-z.
|
|
// Play: https://go.dev/play/p/GjqCnOfV6cM
|
|
func IsAllLower(str string) bool {
|
|
for _, r := range str {
|
|
if !unicode.IsLower(r) {
|
|
return false
|
|
}
|
|
}
|
|
return str != ""
|
|
}
|
|
|
|
// IsASCII checks if string is all ASCII char.
|
|
// Play: https://go.dev/play/p/hfQNPLX0jNa
|
|
func IsASCII(str string) bool {
|
|
for i := 0; i < len(str); i++ {
|
|
if str[i] > unicode.MaxASCII {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// IsPrintable checks if string is all printable chars.
|
|
// Play: https://go.dev/play/p/Pe1FE2gdtTP
|
|
func IsPrintable(str string) bool {
|
|
for _, r := range str {
|
|
if !unicode.IsPrint(r) {
|
|
if r == '\n' || r == '\r' || r == '\t' || r == '`' {
|
|
continue
|
|
}
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// ContainUpper check if the string contain at least one upper case letter A-Z.
|
|
// Play: https://go.dev/play/p/CmWeBEk27-z
|
|
func ContainUpper(str string) bool {
|
|
for _, r := range str {
|
|
if unicode.IsUpper(r) && unicode.IsLetter(r) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// ContainLower check if the string contain at least one lower case letter a-z.
|
|
// Play: https://go.dev/play/p/Srqi1ItvnAA
|
|
func ContainLower(str string) bool {
|
|
for _, r := range str {
|
|
if unicode.IsLower(r) && unicode.IsLetter(r) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// ContainLetter check if the string contain at least one letter.
|
|
// Play: https://go.dev/play/p/lqFD04Yyewp
|
|
func ContainLetter(str string) bool {
|
|
return letterRegexMatcher.MatchString(str)
|
|
}
|
|
|
|
// ContainNumber check if the string contain at least one number.
|
|
func ContainNumber(input string) bool {
|
|
return numberRegexMatcher.MatchString(input)
|
|
}
|
|
|
|
// IsJSON checks if the string is valid JSON.
|
|
// Play: https://go.dev/play/p/8Kip1Itjiil
|
|
func IsJSON(str string) bool {
|
|
var js json.RawMessage
|
|
return json.Unmarshal([]byte(str), &js) == nil
|
|
}
|
|
|
|
// IsNumberStr check if the string can convert to a number.
|
|
// Play: https://go.dev/play/p/LzaKocSV79u
|
|
func IsNumberStr(s string) bool {
|
|
return IsIntStr(s) || IsFloatStr(s)
|
|
}
|
|
|
|
// IsFloatStr check if the string can convert to a float.
|
|
// Play: https://go.dev/play/p/LOYwS_Oyl7U
|
|
func IsFloatStr(str string) bool {
|
|
_, e := strconv.ParseFloat(str, 64)
|
|
return e == nil
|
|
}
|
|
|
|
// IsIntStr check if the string can convert to a integer.
|
|
// Play: https://go.dev/play/p/jQRtFv-a0Rk
|
|
func IsIntStr(str string) bool {
|
|
return intStrMatcher.MatchString(str)
|
|
}
|
|
|
|
// IsIp check if the string is a ip address.
|
|
// Play: https://go.dev/play/p/FgcplDvmxoD
|
|
func IsIp(ipstr string) bool {
|
|
ip := net.ParseIP(ipstr)
|
|
return ip != nil
|
|
}
|
|
|
|
// IsIpV4 check if the string is a ipv4 address.
|
|
// Play: https://go.dev/play/p/zBGT99EjaIu
|
|
func IsIpV4(ipstr string) bool {
|
|
ip := net.ParseIP(ipstr)
|
|
if ip == nil {
|
|
return false
|
|
}
|
|
return strings.Contains(ipstr, ".")
|
|
}
|
|
|
|
// IsIpV6 check if the string is a ipv6 address.
|
|
// Play: https://go.dev/play/p/AHA0r0AzIdC
|
|
func IsIpV6(ipstr string) bool {
|
|
ip := net.ParseIP(ipstr)
|
|
if ip == nil {
|
|
return false
|
|
}
|
|
return strings.Contains(ipstr, ":")
|
|
}
|
|
|
|
// IsPort check if the string is a valid net port.
|
|
// Play:
|
|
func IsPort(str string) bool {
|
|
if i, err := strconv.ParseInt(str, 10, 64); err == nil && i > 0 && i < 65536 {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// IsUrl check if the string is url.
|
|
// Play: https://go.dev/play/p/pbJGa7F98Ka
|
|
func IsUrl(str string) bool {
|
|
if str == "" || len(str) >= 2083 || len(str) <= 3 || strings.HasPrefix(str, ".") {
|
|
return false
|
|
}
|
|
u, err := url.Parse(str)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
if strings.HasPrefix(u.Host, ".") {
|
|
return false
|
|
}
|
|
if u.Host == "" && (u.Path != "" && !strings.Contains(u.Path, ".")) {
|
|
return false
|
|
}
|
|
|
|
return urlMatcher.MatchString(str)
|
|
}
|
|
|
|
// IsDns check if the string is dns.
|
|
// Play: https://go.dev/play/p/jlYApVLLGTZ
|
|
func IsDns(dns string) bool {
|
|
return dnsMatcher.MatchString(dns)
|
|
}
|
|
|
|
// IsEmail check if the string is a email address.
|
|
// Play: https://go.dev/play/p/Os9VaFlT33G
|
|
func IsEmail(email string) bool {
|
|
return emailMatcher.MatchString(email)
|
|
}
|
|
|
|
// IsChineseMobile check if the string is chinese mobile number.
|
|
// Play: https://go.dev/play/p/GPYUlGTOqe3
|
|
func IsChineseMobile(mobileNum string) bool {
|
|
return chineseMobileMatcher.MatchString(mobileNum)
|
|
}
|
|
|
|
// IsChineseIdNum check if the string is chinese id card.
|
|
// Play: https://go.dev/play/p/d8EWhl2UGDF
|
|
func IsChineseIdNum(id string) bool {
|
|
// All characters should be numbers, and the last digit can be either x or X
|
|
if !chineseIdMatcher.MatchString(id) {
|
|
return false
|
|
}
|
|
|
|
// Verify province codes and complete all province codes according to GB/T2260
|
|
_, ok := provinceKv[id[0:2]]
|
|
if !ok {
|
|
return false
|
|
}
|
|
|
|
// Verify birthday, must be greater than birthStartYear and less than the current year
|
|
birthStr := fmt.Sprintf("%s-%s-%s", id[6:10], id[10:12], id[12:14])
|
|
birthday, err := time.Parse("2006-01-02", birthStr)
|
|
if err != nil || birthday.After(time.Now()) || birthday.Year() < birthStartYear {
|
|
return false
|
|
}
|
|
|
|
// Verification code
|
|
sum := 0
|
|
for i, c := range id[:17] {
|
|
v, _ := strconv.Atoi(string(c))
|
|
sum += v * factor[i]
|
|
}
|
|
|
|
return verifyStr[sum%11] == strings.ToUpper(id[17:18])
|
|
}
|
|
|
|
// ContainChinese check if the string contain mandarin chinese.
|
|
// Play: https://go.dev/play/p/7DpU0uElYeM
|
|
func ContainChinese(s string) bool {
|
|
return chineseMatcher.MatchString(s)
|
|
}
|
|
|
|
// IsChinesePhone check if the string is chinese phone number.
|
|
// Valid chinese phone is xxx-xxxxxxxx or xxxx-xxxxxxx.
|
|
// Play: https://go.dev/play/p/RUD_-7YZJ3I
|
|
func IsChinesePhone(phone string) bool {
|
|
return chinesePhoneMatcher.MatchString(phone)
|
|
}
|
|
|
|
// IsCreditCard check if the string is credit card.
|
|
// Play: https://go.dev/play/p/sNwwL6B0-v4
|
|
func IsCreditCard(creditCart string) bool {
|
|
return creditCardMatcher.MatchString(creditCart)
|
|
}
|
|
|
|
// IsBase64 check if the string is base64 string.
|
|
// Play: https://go.dev/play/p/sWHEySAt6hl
|
|
func IsBase64(base64 string) bool {
|
|
return base64Matcher.MatchString(base64)
|
|
}
|
|
|
|
// IsEmptyString check if the string is empty.
|
|
// Play: https://go.dev/play/p/dpzgUjFnBCX
|
|
func IsEmptyString(str string) bool {
|
|
return len(str) == 0
|
|
}
|
|
|
|
// IsRegexMatch check if the string match the regexp.
|
|
// Play: https://go.dev/play/p/z_XeZo_litG
|
|
func IsRegexMatch(str, regex string) bool {
|
|
reg := regexp.MustCompile(regex)
|
|
return reg.MatchString(str)
|
|
}
|
|
|
|
// IsStrongPassword check if the string is strong password, if len(password) is less than the length param, return false
|
|
// Strong password: alpha(lower+upper) + number + special chars(!@#$%^&*()?><).
|
|
// Play: https://go.dev/play/p/QHdVcSQ3uDg
|
|
func IsStrongPassword(password string, length int) bool {
|
|
if len(password) < length {
|
|
return false
|
|
}
|
|
var num, lower, upper, special bool
|
|
for _, r := range password {
|
|
switch {
|
|
case unicode.IsDigit(r):
|
|
num = true
|
|
case unicode.IsUpper(r):
|
|
upper = true
|
|
case unicode.IsLower(r):
|
|
lower = true
|
|
case unicode.IsSymbol(r), unicode.IsPunct(r):
|
|
special = true
|
|
}
|
|
}
|
|
|
|
return num && lower && upper && special
|
|
}
|
|
|
|
// IsWeakPassword check if the string is weak password
|
|
// Weak password: only letter or only number or letter + number.
|
|
// Play: https://go.dev/play/p/wqakscZH5gH
|
|
func IsWeakPassword(password string) bool {
|
|
var num, letter, special bool
|
|
for _, r := range password {
|
|
switch {
|
|
case unicode.IsDigit(r):
|
|
num = true
|
|
case unicode.IsLetter(r):
|
|
letter = true
|
|
case unicode.IsSymbol(r), unicode.IsPunct(r):
|
|
special = true
|
|
}
|
|
}
|
|
|
|
return (num || letter) && !special
|
|
}
|
|
|
|
// IsZeroValue checks if value is a zero value.
|
|
// Play: https://go.dev/play/p/UMrwaDCi_t4
|
|
func IsZeroValue(value any) bool {
|
|
if value == nil {
|
|
return true
|
|
}
|
|
|
|
rv := reflect.ValueOf(value)
|
|
if rv.Kind() == reflect.Ptr {
|
|
rv = rv.Elem()
|
|
}
|
|
|
|
if !rv.IsValid() {
|
|
return true
|
|
}
|
|
|
|
switch rv.Kind() {
|
|
case reflect.String:
|
|
return rv.Len() == 0
|
|
case reflect.Bool:
|
|
return !rv.Bool()
|
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
return rv.Int() == 0
|
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
|
return rv.Uint() == 0
|
|
case reflect.Float32, reflect.Float64:
|
|
return rv.Float() == 0
|
|
case reflect.Ptr, reflect.Chan, reflect.Func, reflect.Interface, reflect.Slice, reflect.Map:
|
|
return rv.IsNil()
|
|
}
|
|
|
|
return reflect.DeepEqual(rv.Interface(), reflect.Zero(rv.Type()).Interface())
|
|
}
|
|
|
|
// IsGBK check if data encoding is gbk
|
|
// Note: this function is implemented by whether double bytes fall within the encoding range of gbk,
|
|
// while each byte of utf-8 encoding format falls within the encoding range of gbk.
|
|
// Therefore, utf8.valid() should be called first to check whether it is not utf-8 encoding,
|
|
// and then call IsGBK() to check gbk encoding. like below
|
|
/**
|
|
data := []byte("你好")
|
|
if utf8.Valid(data) {
|
|
fmt.Println("data encoding is utf-8")
|
|
}else if(IsGBK(data)) {
|
|
fmt.Println("data encoding is GBK")
|
|
}
|
|
fmt.Println("data encoding is unknown")
|
|
**/
|
|
// Play: https://go.dev/play/p/E2nt3unlmzP
|
|
func IsGBK(data []byte) bool {
|
|
i := 0
|
|
for i < len(data) {
|
|
if data[i] <= 0xff {
|
|
i++
|
|
continue
|
|
} else {
|
|
if data[i] >= 0x81 &&
|
|
data[i] <= 0xfe &&
|
|
data[i+1] >= 0x40 &&
|
|
data[i+1] <= 0xfe &&
|
|
data[i+1] != 0xf7 {
|
|
i += 2
|
|
continue
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// IsNumber check if the value is number(integer, float) or not.
|
|
// Play: https://go.dev/play/p/mdJHOAvtsvF
|
|
func IsNumber(v any) bool {
|
|
return IsInt(v) || IsFloat(v)
|
|
}
|
|
|
|
// IsFloat check if the value is float(float32, float34) or not.
|
|
// Play: https://go.dev/play/p/vsyG-sxr99_Z
|
|
func IsFloat(v any) bool {
|
|
switch v.(type) {
|
|
case float32, float64:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// IsInt check if the value is integer(int, unit) or not.
|
|
// Play: https://go.dev/play/p/eFoIHbgzl-z
|
|
func IsInt(v any) bool {
|
|
switch v.(type) {
|
|
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr:
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// IsBin check if a give string is a valid binary value or not.
|
|
// Play: https://go.dev/play/p/ogPeg2XJH4P
|
|
func IsBin(v string) bool {
|
|
return binMatcher.MatchString(v)
|
|
}
|
|
|
|
// IsHex check if a give string is a valid hexadecimal value or not.
|
|
// Play: https://go.dev/play/p/M2qpHbEwmm7
|
|
func IsHex(v string) bool {
|
|
return hexMatcher.MatchString(v)
|
|
}
|
|
|
|
// IsBase64URL check if a give string is a valid URL-safe Base64 encoded string.
|
|
// Play: https://go.dev/play/p/vhl4mr8GZ6S
|
|
func IsBase64URL(v string) bool {
|
|
return base64URLMatcher.MatchString(v)
|
|
}
|
|
|
|
// IsJWT check if a give string is a valid JSON Web Token (JWT).
|
|
// Play: https://go.dev/play/p/R6Op7heJbKI
|
|
func IsJWT(v string) bool {
|
|
strings := strings.Split(v, ".")
|
|
if len(strings) != 3 {
|
|
return false
|
|
}
|
|
|
|
for _, s := range strings {
|
|
if !IsBase64URL(s) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// IsVisa check if a give string is a valid visa card nubmer or not.
|
|
// Play: https://go.dev/play/p/SdS2keOyJsl
|
|
func IsVisa(v string) bool {
|
|
return visaMatcher.MatchString(v)
|
|
}
|
|
|
|
// IsMasterCard check if a give string is a valid master card nubmer or not.
|
|
// Play: https://go.dev/play/p/CwWBFRrG27b
|
|
func IsMasterCard(v string) bool {
|
|
return masterCardMatcher.MatchString(v)
|
|
}
|
|
|
|
// IsAmericanExpress check if a give string is a valid american expression card nubmer or not.
|
|
// Play: https://go.dev/play/p/HIDFpcOdpkd
|
|
func IsAmericanExpress(v string) bool {
|
|
return americanExpressMatcher.MatchString(v)
|
|
}
|
|
|
|
// 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 unionPay.MatchString(v)
|
|
}
|
|
|
|
// 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 chinaUnionPay.MatchString(v)
|
|
}
|