From 249f20d0ed1a0b3398f3ad6a21f435f4c6167787 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 19 Mar 2020 11:27:29 +0800 Subject: [PATCH] add CreditCodeUtil --- CHANGELOG.md | 1 + .../java/cn/hutool/core/lang/PatternPool.java | 139 ++++++++++++------ .../java/cn/hutool/core/lang/Validator.java | 57 ++----- .../cn/hutool/core/util/CreditCodeUtil.java | 131 +++++++++++++++++ .../hutool/core/util/CreditCodeUtilTest.java | 25 ++++ 5 files changed, 266 insertions(+), 87 deletions(-) create mode 100644 hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java create mode 100644 hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c3a4ea97..e57893b90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ * 【db 】 设置全局忽略大小写DbUtil.setCaseInsensitiveGlobal(true)(issue#784@Github) * 【core 】 增加CallerUtil.getCallerMethodName方法 * 【core 】 Tree增加getParent方法,可以获取父节点,抽象Node接口 +* 【core 】 增加社会信用代码工具CreditCodeUtil(pr#112@Gitee) ### Bug修复 * 【core 】 修复TypeUtil无法获取泛型接口的泛型参数问题(issue#I1BRFI@Gitee) diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/PatternPool.java b/hutool-core/src/main/java/cn/hutool/core/lang/PatternPool.java index db8efd5e1..9778c61d3 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/PatternPool.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/PatternPool.java @@ -1,86 +1,137 @@ package cn.hutool.core.lang; -import java.util.ArrayList; -import java.util.List; -import java.util.regex.Pattern; - import cn.hutool.core.util.ReUtil; +import java.util.regex.Pattern; + /** * 常用正则表达式集合 * * @author Looly - * */ public class PatternPool { - /** 英文字母 、数字和下划线 */ + /** + * 英文字母 、数字和下划线 + */ public final static Pattern GENERAL = Pattern.compile("^\\w+$"); - /** 数字 */ + /** + * 数字 + */ public final static Pattern NUMBERS = Pattern.compile("\\d+"); - /** 字母 */ + /** + * 字母 + */ public final static Pattern WORD = Pattern.compile("[a-zA-Z]+"); - /** 单个中文汉字 */ + /** + * 单个中文汉字 + */ public final static Pattern CHINESE = Pattern.compile(ReUtil.RE_CHINESE); - /** 中文汉字 */ + /** + * 中文汉字 + */ public final static Pattern CHINESES = Pattern.compile(ReUtil.RE_CHINESES); - /** 分组 */ + /** + * 分组 + */ public final static Pattern GROUP_VAR = Pattern.compile("\\$(\\d+)"); - /** IP v4 */ + /** + * IP v4 + */ public final static Pattern IPV4 = Pattern.compile("\\b((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\.((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\.((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\.((?!\\d\\d\\d)\\d+|1\\d\\d|2[0-4]\\d|25[0-5])\\b"); - /** IP v6 */ + /** + * IP v6 + */ public final static Pattern IPV6 = Pattern.compile("(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]+|::(ffff(:0{1,4})?:)?((25[0-5]|(2[0-4]|1?[0-9])?[0-9])\\.){3}(25[0-5]|(2[0-4]|1?[0-9])?[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1?[0-9])?[0-9])\\.){3}(25[0-5]|(2[0-4]|1?[0-9])?[0-9]))"); - /** 货币 */ + /** + * 货币 + */ public final static Pattern MONEY = Pattern.compile("^(\\d+(?:\\.\\d+)?)$"); - /** 邮件,符合RFC 5322规范,正则来自:http://emailregex.com/ */ + /** + * 邮件,符合RFC 5322规范,正则来自:http://emailregex.com/ + */ // public final static Pattern EMAIL = Pattern.compile("(\\w|.)+@\\w+(\\.\\w+){1,2}"); public final static Pattern EMAIL = Pattern.compile("(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)])", Pattern.CASE_INSENSITIVE); - /** 移动电话 */ + /** + * 移动电话 + */ public final static Pattern MOBILE = Pattern.compile("(?:0|86|\\+86)?1[3456789]\\d{9}"); - /** 18位身份证号码 */ + /** + * 18位身份证号码 + */ public final static Pattern CITIZEN_ID = Pattern.compile("[1-9]\\d{5}[1-2]\\d{3}((0\\d)|(1[0-2]))(([012]\\d)|3[0-1])\\d{3}(\\d|X|x)"); - /** 邮编 */ + /** + * 邮编 + */ public final static Pattern ZIP_CODE = Pattern.compile("[1-9]\\d{5}(?!\\d)"); - /** 生日 */ + /** + * 生日 + */ public final static Pattern BIRTHDAY = Pattern.compile("^(\\d{2,4})([/\\-.年]?)(\\d{1,2})([/\\-.月]?)(\\d{1,2})日?$"); - /** URL */ + /** + * URL + */ public final static Pattern URL = Pattern.compile("[a-zA-z]+://[^\\s]*"); - /** Http URL */ + /** + * Http URL + */ public final static Pattern URL_HTTP = Pattern.compile("(https://|http://)?([\\w-]+\\.)+[\\w-]+(:\\d+)*(/[\\w- ./?%&=]*)?"); - /** 中文字、英文字母、数字和下划线 */ + /** + * 中文字、英文字母、数字和下划线 + */ public final static Pattern GENERAL_WITH_CHINESE = Pattern.compile("^[\u4E00-\u9FFF\\w]+$"); - /** UUID */ + /** + * UUID + */ public final static Pattern UUID = Pattern.compile("^[0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12}$"); - /** 不带横线的UUID */ + /** + * 不带横线的UUID + */ public final static Pattern UUID_SIMPLE = Pattern.compile("^[0-9a-z]{32}$"); - /** MAC地址正则 */ + /** + * MAC地址正则 + */ public static final Pattern MAC_ADDRESS = Pattern.compile("((?:[A-F0-9]{1,2}[:-]){5}[A-F0-9]{1,2})|(?:0x)(\\d{12})(?:.+ETHER)", Pattern.CASE_INSENSITIVE); - /** 16进制字符串 */ + /** + * 16进制字符串 + */ public static final Pattern HEX = Pattern.compile("^[a-f0-9]+$", Pattern.CASE_INSENSITIVE); - /** 时间正则 */ + /** + * 时间正则 + */ public static final Pattern TIME = Pattern.compile("\\d{1,2}:\\d{1,2}(:\\d{1,2})?"); - /** 中国车牌号码(兼容新能源车牌) */ + /** + * 中国车牌号码(兼容新能源车牌) + */ public final static Pattern PLATE_NUMBER = Pattern.compile( //https://gitee.com/loolly/hutool/issues/I1B77H?from=project-issue "^(([京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z](([0-9]{5}[ABCDEFGHJK])|([ABCDEFGHJK]([A-HJ-NP-Z0-9])[0-9]{4})))|" + - //https://gitee.com/loolly/hutool/issues/I1BJHE?from=project-issue - "([京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领]\\d{3}\\d{1,3}[领])|" + - "([京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z][A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳使领]))$"); + //https://gitee.com/loolly/hutool/issues/I1BJHE?from=project-issue + "([京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领]\\d{3}\\d{1,3}[领])|" + + "([京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领][A-Z][A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳使领]))$"); - /** 社会统一信用代码 */ - public static final String CREDIT_CODE = "^[A-Z0-9]{18}$"; - - public static final String BASE_CODE_REGEX = "[0-9A-Y]{18}"; - + /** + * 社会统一信用代码 + *
+	 * 第一部分:登记管理部门代码1位 (数字或大写英文字母)
+	 * 第二部分:机构类别代码1位 (数字或大写英文字母)
+	 * 第三部分:登记管理机关行政区划码6位 (数字)
+	 * 第四部分:主体标识码(组织机构代码)9位 (数字或大写英文字母)
+	 * 第五部分:校验码1位 (数字或大写英文字母)
+	 * 
+ */ + public static final Pattern CREDIT_CODE = Pattern.compile("^[0-9A-HJ-NPQRTUWXY]{2}\\d{6}[0-9A-HJ-NPQRTUWXY]{10}$"); // ------------------------------------------------------------------------------------------------------------------------------------------------------------------- - /** Pattern池 */ + /** + * Pattern池 + */ private static final SimpleCache POOL = new SimpleCache<>(); /** * 先从Pattern池中查找正则对应的{@link Pattern},找不到则编译正则表达式并入池。 - * + * * @param regex 正则表达式 * @return {@link Pattern} */ @@ -90,7 +141,7 @@ public class PatternPool { /** * 先从Pattern池中查找正则对应的{@link Pattern},找不到则编译正则表达式并入池。 - * + * * @param regex 正则表达式 * @param flags 正则标识位集合 {@link Pattern} * @return {@link Pattern} @@ -108,7 +159,7 @@ public class PatternPool { /** * 移除缓存 - * + * * @param regex 正则 * @param flags 标识 * @return 移除的{@link Pattern},可能为{@code null} @@ -125,11 +176,11 @@ public class PatternPool { } // --------------------------------------------------------------------------------------------------------------------------------- + /** * 正则表达式和正则标识位的包装 - * - * @author Looly * + * @author Looly */ private static class RegexWithFlag { private String regex; @@ -137,9 +188,9 @@ public class PatternPool { /** * 构造 - * + * * @param regex 正则 - * @param flag 标识 + * @param flag 标识 */ public RegexWithFlag(String regex, int flag) { this.regex = regex; diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/Validator.java b/hutool-core/src/main/java/cn/hutool/core/lang/Validator.java index 372169145..5482b5e30 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/Validator.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/Validator.java @@ -2,14 +2,13 @@ package cn.hutool.core.lang; import cn.hutool.core.date.DateUtil; import cn.hutool.core.exceptions.ValidateException; +import cn.hutool.core.util.CreditCodeUtil; import cn.hutool.core.util.NumberUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ReUtil; import cn.hutool.core.util.StrUtil; import java.net.MalformedURLException; -import java.util.ArrayList; -import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -89,17 +88,6 @@ public class Validator { */ public final static Pattern PLATE_NUMBER = PatternPool.PLATE_NUMBER; - /** - * 社会统一信用代码 - */ - public static final List BASE_CODES = new ArrayList<>(); - - public static final String BASE_CODE_STRING = "0123456789ABCDEFGHJKLMNPQRTUWXY"; - - public static final int[] WEIGHT = {1, 3, 9, 27, 19, 26, 16, 17, 20, 29, 25, 13, 8, 24, 10, 30, 28}; - - public static final char[] BASE_CODE_ARRAY = BASE_CODE_STRING.toCharArray(); - /** * 给定值是否为true * @@ -136,7 +124,7 @@ public class Validator { if (isFalse(value)) { throw new ValidateException(errorMsgTemplate, params); } - return value; + return true; } /** @@ -153,7 +141,7 @@ public class Validator { if (isTrue(value)) { throw new ValidateException(errorMsgTemplate, params); } - return value; + return false; } /** @@ -191,7 +179,7 @@ public class Validator { if (isNotNull(value)) { throw new ValidateException(errorMsgTemplate, params); } - return value; + return null; } /** @@ -1117,38 +1105,21 @@ public class Validator { } } - /** - * 简单校验统一社会信用代码 - * 18位(大写字母+数字) - * - * @param creditCode 统一社会信用代码 - * @return 校验结果 - */ - public static boolean isCreditCodeBySimple(String creditCode) { - if (StrUtil.isBlank(creditCode)) { - return false; - } - return Pattern.matches(PatternPool.CREDIT_CODE, creditCode); - } - /** * 是否是有效的统一社会信用代码 + *
+	 * 第一部分:登记管理部门代码1位 (数字或大写英文字母)
+	 * 第二部分:机构类别代码1位 (数字或大写英文字母)
+	 * 第三部分:登记管理机关行政区划码6位 (数字)
+	 * 第四部分:主体标识码(组织机构代码)9位 (数字或大写英文字母)
+	 * 第五部分:校验码1位 (数字或大写英文字母)
+	 * 
* * @param creditCode 统一社会信用代码 * @return 校验结果 + * @since 5.2.4 */ - public static boolean isCreditCode(String creditCode) { - if (StrUtil.isBlank(creditCode) || !Pattern.matches(PatternPool.BASE_CODE_REGEX, creditCode)) { - return false; - } - char[] businessCodeArray = creditCode.toCharArray(); - char check = businessCodeArray[17]; - int sum = 0, length = 17; - for (int i = 0; i < length; i++) { - char key = businessCodeArray[i]; - sum += (BASE_CODES.indexOf(key) * WEIGHT[i]); - } - int value = 31 - sum % 31; - return check == BASE_CODE_ARRAY[value % 31]; + public static boolean isCreditCode(CharSequence creditCode) { + return CreditCodeUtil.isCreditCode(creditCode); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java new file mode 100644 index 000000000..557ccb3d4 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/util/CreditCodeUtil.java @@ -0,0 +1,131 @@ +package cn.hutool.core.util; + +import cn.hutool.core.lang.PatternPool; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; + +/** + * 统一社会信用代码工具类 + * + *
+ * 第一部分:登记管理部门代码1位 (数字或大写英文字母)
+ * 第二部分:机构类别代码1位 (数字或大写英文字母)
+ * 第三部分:登记管理机关行政区划码6位 (数字)
+ * 第四部分:主体标识码(组织机构代码)9位 (数字或大写英文字母)
+ * 第五部分:校验码1位 (数字或大写英文字母)
+ * 
+ * + * @author looly + * @since 5.2.4 + */ +public class CreditCodeUtil { + + public static final Pattern CREDIT_CODE_PATTERN = PatternPool.CREDIT_CODE; + + private static final int[] WEIGHT = {1, 3, 9, 27, 19, 26, 16, 17, 20, 29, 25, 13, 8, 24, 10, 30, 28}; + private static final char[] BASE_CODE_ARRAY = "0123456789ABCDEFGHJKLMNPQRTUWXY".toCharArray(); + private static final Map CODE_INDEX_MAP; + + static { + CODE_INDEX_MAP = new ConcurrentHashMap<>(); + for (int i = 0; i < BASE_CODE_ARRAY.length; i++) { + CODE_INDEX_MAP.put(BASE_CODE_ARRAY[i], i); + } + } + + /** + * 正则校验统一社会信用代码(18位) + * + *
+	 * 第一部分:登记管理部门代码1位 (数字或大写英文字母)
+	 * 第二部分:机构类别代码1位 (数字或大写英文字母)
+	 * 第三部分:登记管理机关行政区划码6位 (数字)
+	 * 第四部分:主体标识码(组织机构代码)9位 (数字或大写英文字母)
+	 * 第五部分:校验码1位 (数字或大写英文字母)
+	 * 
+ * + * @param creditCode 统一社会信用代码 + * @return 校验结果 + */ + public static boolean isCreditCodeSimple(CharSequence creditCode) { + if (StrUtil.isBlank(creditCode)) { + return false; + } + return ReUtil.isMatch(CREDIT_CODE_PATTERN, creditCode); + } + + /** + * 是否是有效的统一社会信用代码 + *
+	 * 第一部分:登记管理部门代码1位 (数字或大写英文字母)
+	 * 第二部分:机构类别代码1位 (数字或大写英文字母)
+	 * 第三部分:登记管理机关行政区划码6位 (数字)
+	 * 第四部分:主体标识码(组织机构代码)9位 (数字或大写英文字母)
+	 * 第五部分:校验码1位 (数字或大写英文字母)
+	 * 
+ * + * @param creditCode 统一社会信用代码 + * @return 校验结果 + */ + public static boolean isCreditCode(CharSequence creditCode) { + if (false == isCreditCodeSimple(creditCode)) { + return false; + } + + final int parityBit = getParityBit(creditCode); + if (parityBit < 0) { + return false; + } + + return creditCode.charAt(17) == BASE_CODE_ARRAY[parityBit]; + } + + /** + * 获取一个随机的统一社会信用代码 + * + * @return 统一社会信用代码 + */ + public static String randomCreditCode() { + final StringBuilder buf = new StringBuilder(18); + + + // + for (int i = 0; i < 2; i++) { + int num = RandomUtil.randomInt(BASE_CODE_ARRAY.length - 1); + buf.append(Character.toUpperCase(BASE_CODE_ARRAY[num])); + } + for (int i = 2; i < 8; i++) { + int num = RandomUtil.randomInt(10); + buf.append(Character.toUpperCase(BASE_CODE_ARRAY[num])); + } + for (int i = 8; i < 17; i++) { + int num = RandomUtil.randomInt(BASE_CODE_ARRAY.length - 1); + buf.append(Character.toUpperCase(BASE_CODE_ARRAY[num])); + } + + final String code = buf.toString(); + return code + BASE_CODE_ARRAY[getParityBit(code)]; + } + + /** + * 获取校验码 + * + * @param creditCode 统一社会信息代码 + * @return 获取较验位的值 + */ + private static int getParityBit(CharSequence creditCode) { + int sum = 0; + Integer codeIndex; + for (int i = 0; i < 17; i++) { + codeIndex = CODE_INDEX_MAP.get(creditCode.charAt(i)); + if (null == codeIndex) { + return -1; + } + sum += codeIndex * WEIGHT[i]; + } + final int result = 31 - sum % 31; + return result == 31 ? 0 : result; + } +} diff --git a/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java new file mode 100644 index 000000000..9355c6711 --- /dev/null +++ b/hutool-core/src/test/java/cn/hutool/core/util/CreditCodeUtilTest.java @@ -0,0 +1,25 @@ +package cn.hutool.core.util; + +import org.junit.Assert; +import org.junit.Test; + +public class CreditCodeUtilTest { + + @Test + public void isCreditCodeBySimple() { + String testCreditCode = "91310115591693856A"; + Assert.assertTrue(CreditCodeUtil.isCreditCodeSimple(testCreditCode)); + } + + @Test + public void isCreditCode() { + String testCreditCode = "91310110666007217T"; + Assert.assertTrue(CreditCodeUtil.isCreditCode(testCreditCode)); + } + + @Test + public void randomCreditCode() { + final String s = CreditCodeUtil.randomCreditCode(); + Assert.assertTrue(CreditCodeUtil.isCreditCode(s)); + } +} \ No newline at end of file