From 68e0186ca7239c138a0a30d4ebb2045873d54486 Mon Sep 17 00:00:00 2001 From: Looly Date: Wed, 31 Jul 2024 18:10:11 +0800 Subject: [PATCH] fix code --- .../org/dromara/hutool/core/data/CIN10.java | 236 ++++++++++++++++++ .../dromara/hutool/core/data/IdcardUtil.java | 158 +----------- .../hutool/core/data/IdcardUtilTest.java | 11 +- 3 files changed, 249 insertions(+), 156 deletions(-) create mode 100644 hutool-core/src/main/java/org/dromara/hutool/core/data/CIN10.java diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/data/CIN10.java b/hutool-core/src/main/java/org/dromara/hutool/core/data/CIN10.java new file mode 100644 index 000000000..e4af5b2b9 --- /dev/null +++ b/hutool-core/src/main/java/org/dromara/hutool/core/data/CIN10.java @@ -0,0 +1,236 @@ +/* + * Copyright (c) 2024. looly(loolly@aliyun.com) + * Hutool is licensed under Mulan PSL v2. + * You can use this software according to the terms and conditions of the Mulan PSL v2. + * You may obtain a copy of Mulan PSL v2 at: + * https://license.coscl.org.cn/MulanPSL2 + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, + * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, + * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. + * See the Mulan PSL v2 for more details. + */ + +package org.dromara.hutool.core.data; + +import org.dromara.hutool.core.regex.ReUtil; +import org.dromara.hutool.core.text.StrUtil; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +/** + * 10位公民身份号码(Citizen Identification Number),一般用于台湾、澳门、香港 + * + * @author Looly + * @since 6.0.0 + */ +public class CIN10 { + + private static final Pattern PATTERN_TW = Pattern.compile("^[a-zA-Z][0-9]{9}$"); + private static final Pattern PATTERN_MC = Pattern.compile("^[157][0-9]{6}\\(?[0-9A-Z]\\)?$"); + private static final Pattern PATTERN_HK = Pattern.compile("^[A-Z]{1,2}[0-9]{6}\\(?[0-9A]\\)?$"); + /** + * 台湾身份首字母对应数字 + */ + private static final Map TW_FIRST_CODE = new HashMap<>(); + + static { + TW_FIRST_CODE.put('A', 10); + TW_FIRST_CODE.put('B', 11); + TW_FIRST_CODE.put('C', 12); + TW_FIRST_CODE.put('D', 13); + TW_FIRST_CODE.put('E', 14); + TW_FIRST_CODE.put('F', 15); + TW_FIRST_CODE.put('G', 16); + TW_FIRST_CODE.put('H', 17); + TW_FIRST_CODE.put('J', 18); + TW_FIRST_CODE.put('K', 19); + TW_FIRST_CODE.put('L', 20); + TW_FIRST_CODE.put('M', 21); + TW_FIRST_CODE.put('N', 22); + TW_FIRST_CODE.put('P', 23); + TW_FIRST_CODE.put('Q', 24); + TW_FIRST_CODE.put('R', 25); + TW_FIRST_CODE.put('S', 26); + TW_FIRST_CODE.put('T', 27); + TW_FIRST_CODE.put('U', 28); + TW_FIRST_CODE.put('V', 29); + TW_FIRST_CODE.put('X', 30); + TW_FIRST_CODE.put('Y', 31); + TW_FIRST_CODE.put('W', 32); + TW_FIRST_CODE.put('Z', 33); + TW_FIRST_CODE.put('I', 34); + TW_FIRST_CODE.put('O', 35); + } + + /** + * 创建并验证台湾、澳门、香港身份证号码 + * + * @param code 台湾、澳门、香港身份证号码 + * @return CIN10 + */ + public static CIN10 of(final String code) { + return new CIN10(code); + } + + private final String code; + private final String province; + private final Gender gender; + private final boolean verified; + + /** + * 构造 + * + * @param code 身份证号码 + * @throws IllegalArgumentException 身份证格式不支持 + */ + public CIN10(final String code) throws IllegalArgumentException { + this.code = code; + if (StrUtil.isNotBlank(code)) { + if (ReUtil.isMatch(PATTERN_TW, code)) { // 台湾 + this.province = "台湾"; + final char char2 = code.charAt(1); + if ('1' == char2) { + this.gender = Gender.MALE; + } else if ('2' == char2) { + this.gender = Gender.FEMALE; + } else { + this.gender = Gender.UNKNOWN; + } + this.verified = verifyTWCard(code); + return; + } else if (ReUtil.isMatch(PATTERN_MC, code)) { // 澳门 + this.province = "澳门"; + this.gender = Gender.UNKNOWN; + this.verified = true; + return; + } else if (ReUtil.isMatch(PATTERN_HK, code)) { // 香港 + this.province = "香港"; + this.gender = Gender.UNKNOWN; + this.verified = verfyHKCard(code); + return; + } + } + + throw new IllegalArgumentException("Invalid CIN10 code!"); + } + + /** + * 获取CIN10码 + * + * @return CIN10码 + */ + public String getCode() { + return code; + } + + /** + * 获取省份 + * + * @return 省份 + */ + public String getProvince() { + return province; + } + + /** + * 获取性别 + * + * @return 性别 + */ + public Gender getGender() { + return gender; + } + + /** + * 是否验证通过 + * + * @return 是否验证通过 + */ + public boolean isVerified() { + return verified; + } + + /** + * 性别枚举 + */ + public enum Gender { + /** + * 男 + */ + MALE, + /** + * 女 + */ + FEMALE, + /** + * 未知 + */ + UNKNOWN + } + + /** + * 验证台湾身份证号码 + * + * @param code 身份证号码 + * @return 验证码是否符合 + */ + private static boolean verifyTWCard(final String code) { + final Integer iStart = TW_FIRST_CODE.get(code.charAt(0)); + if (null == iStart) { + return false; + } + int sum = iStart / 10 + (iStart % 10) * 9; + + final String mid = code.substring(1, 9); + final char[] chars = mid.toCharArray(); + int iflag = 8; + for (final char c : chars) { + sum += Integer.parseInt(String.valueOf(c)) * iflag; + iflag--; + } + + final String end = code.substring(9, 10); + return (sum % 10 == 0 ? 0 : (10 - sum % 10)) == Integer.parseInt(end); + } + + /** + * 验证香港身份证号码(存在Bug,部份特殊身份证无法检查) + *

+ * 身份证前2位为英文字符,如果只出现一个英文字符则表示第一位是空格,对应数字58 前2位英文字符A-Z分别对应数字10-35 最后一位校验码为0-9的数字加上字符"A","A"代表10 + *

+ *

+ * 将身份证号码全部转换为数字,分别对应乘9-1相加的总和,整除11则证件号码有效 + *

+ * + * @param code 身份证号码 + * @return 验证码是否符合 + */ + private static boolean verfyHKCard(final String code) { + String card = code.replaceAll("[()]", ""); + int sum; + if (card.length() == 9) { + sum = (Character.toUpperCase(card.charAt(0)) - 55) * 9 + (Character.toUpperCase(card.charAt(1)) - 55) * 8; + card = card.substring(1, 9); + } else { + sum = 522 + (Character.toUpperCase(card.charAt(0)) - 55) * 8; + } + + // 首字母A-Z,A表示1,以此类推 + final String mid = card.substring(1, 7); + final String end = card.substring(7, 8); + final char[] chars = mid.toCharArray(); + int iflag = 7; + for (final char c : chars) { + sum = sum + Integer.parseInt(String.valueOf(c)) * iflag; + iflag--; + } + if ("A".equalsIgnoreCase(end)) { + sum += 10; + } else { + sum += Integer.parseInt(end); + } + return sum % 11 == 0; + } +} diff --git a/hutool-core/src/main/java/org/dromara/hutool/core/data/IdcardUtil.java b/hutool-core/src/main/java/org/dromara/hutool/core/data/IdcardUtil.java index dfaf7d0ba..1fa19f1be 100644 --- a/hutool-core/src/main/java/org/dromara/hutool/core/data/IdcardUtil.java +++ b/hutool-core/src/main/java/org/dromara/hutool/core/data/IdcardUtil.java @@ -17,8 +17,6 @@ import org.dromara.hutool.core.date.DateUtil; import org.dromara.hutool.core.text.StrUtil; import java.util.Date; -import java.util.HashMap; -import java.util.Map; /** * 身份证相关工具类,参考标准:GB 11643-1999
@@ -34,39 +32,6 @@ import java.util.Map; */ public class IdcardUtil { - /** - * 台湾身份首字母对应数字 - */ - private static final Map TW_FIRST_CODE = new HashMap<>(); - static { - TW_FIRST_CODE.put('A', 10); - TW_FIRST_CODE.put('B', 11); - TW_FIRST_CODE.put('C', 12); - TW_FIRST_CODE.put('D', 13); - TW_FIRST_CODE.put('E', 14); - TW_FIRST_CODE.put('F', 15); - TW_FIRST_CODE.put('G', 16); - TW_FIRST_CODE.put('H', 17); - TW_FIRST_CODE.put('J', 18); - TW_FIRST_CODE.put('K', 19); - TW_FIRST_CODE.put('L', 20); - TW_FIRST_CODE.put('M', 21); - TW_FIRST_CODE.put('N', 22); - TW_FIRST_CODE.put('P', 23); - TW_FIRST_CODE.put('Q', 24); - TW_FIRST_CODE.put('R', 25); - TW_FIRST_CODE.put('S', 26); - TW_FIRST_CODE.put('T', 27); - TW_FIRST_CODE.put('U', 28); - TW_FIRST_CODE.put('V', 29); - TW_FIRST_CODE.put('X', 30); - TW_FIRST_CODE.put('Y', 31); - TW_FIRST_CODE.put('W', 32); - TW_FIRST_CODE.put('Z', 33); - TW_FIRST_CODE.put('I', 34); - TW_FIRST_CODE.put('O', 35); - } - /** * 将15位身份证号码转换为18位
* 15位身份证号码遵循GB 11643-1989标准。 @@ -112,8 +77,7 @@ public class IdcardUtil { return false; } case 10: {// 10位身份证,港澳台地区 - final String[] cardVal = isValidCard10(idCard); - return null != cardVal && "true".equals(cardVal[2]); + return isValidCard10(idCard); } default: return false; @@ -208,122 +172,16 @@ public class IdcardUtil { } /** - * 验证10位身份编码是否合法 - * - * @param idcard 身份编码 - * @return 身份证信息数组 - *

- * [0] - 台湾、澳门、香港 [1] - 性别(男M,女F,未知N) [2] - 是否合法(合法true,不合法false) 若不是身份证件号码则返回null - *

+ * 是否有效的10位身份证号码,一般用于判断和验证台湾、澳门、香港身份证 + * @param idcard 台湾、澳门、香港身份证号码 + * @return 是否有效的10位身份证号码 */ - public static String[] isValidCard10(final String idcard) { - if (StrUtil.isBlank(idcard)) { - return null; - } - final String[] info = new String[3]; - final String card = idcard.replaceAll("[()]", ""); - if (card.length() != 8 && card.length() != 9 && idcard.length() != 10) { - return null; - } - if (idcard.matches("^[a-zA-Z][0-9]{9}$")) { // 台湾 - info[0] = "台湾"; - final char char2 = idcard.charAt(1); - if ('1' == char2) { - info[1] = "M"; - } else if ('2' == char2) { - info[1] = "F"; - } else { - info[1] = "N"; - info[2] = "false"; - return info; - } - info[2] = isValidTWCard(idcard) ? "true" : "false"; - } else if (idcard.matches("^[157][0-9]{6}\\(?[0-9A-Z]\\)?$")) { // 澳门 - info[0] = "澳门"; - info[1] = "N"; - info[2] = "true"; - } else if (idcard.matches("^[A-Z]{1,2}[0-9]{6}\\(?[0-9A]\\)?$")) { // 香港 - info[0] = "香港"; - info[1] = "N"; - info[2] = isValidHKCard(idcard) ? "true" : "false"; - } else { - return null; - } - return info; - } - - /** - * 验证台湾身份证号码 - * - * @param idcard 身份证号码 - * @return 验证码是否符合 - */ - public static boolean isValidTWCard(final String idcard) { - if (null == idcard || idcard.length() != 10) { + public static boolean isValidCard10(final String idcard){ + try{ + return CIN10.of(idcard).isVerified(); + }catch (final IllegalArgumentException e){ return false; } - final Integer iStart = TW_FIRST_CODE.get(idcard.charAt(0)); - if (null == iStart) { - return false; - } - int sum = iStart / 10 + (iStart % 10) * 9; - - final String mid = idcard.substring(1, 9); - final char[] chars = mid.toCharArray(); - int iflag = 8; - for (final char c : chars) { - sum += Integer.parseInt(String.valueOf(c)) * iflag; - iflag--; - } - - final String end = idcard.substring(9, 10); - return (sum % 10 == 0 ? 0 : (10 - sum % 10)) == Integer.parseInt(end); - } - - /** - * 验证香港身份证号码(存在Bug,部份特殊身份证无法检查) - *

- * 身份证前2位为英文字符,如果只出现一个英文字符则表示第一位是空格,对应数字58 前2位英文字符A-Z分别对应数字10-35 最后一位校验码为0-9的数字加上字符"A","A"代表10 - *

- *

- * 将身份证号码全部转换为数字,分别对应乘9-1相加的总和,整除11则证件号码有效 - *

- * - * @param idcard 身份证号码 - * @return 验证码是否符合 - */ - public static boolean isValidHKCard(final String idcard) { - if (StrUtil.isBlank(idcard)) { - return false; - } - if(false == idcard.matches("^[A-Z]{1,2}[0-9]{6}\\(?[0-9A]\\)?$")){ - return false; - } - - String card = idcard.replaceAll("[()]", ""); - int sum; - if (card.length() == 9) { - sum = (Character.toUpperCase(card.charAt(0)) - 55) * 9 + (Character.toUpperCase(card.charAt(1)) - 55) * 8; - card = card.substring(1, 9); - } else { - sum = 522 + (Character.toUpperCase(card.charAt(0)) - 55) * 8; - } - - // 首字母A-Z,A表示1,以此类推 - final String mid = card.substring(1, 7); - final String end = card.substring(7, 8); - final char[] chars = mid.toCharArray(); - int iflag = 7; - for (final char c : chars) { - sum = sum + Integer.parseInt(String.valueOf(c)) * iflag; - iflag--; - } - if ("A".equalsIgnoreCase(end)) { - sum += 10; - } else { - sum += Integer.parseInt(end); - } - return sum % 11 == 0; } /** diff --git a/hutool-core/src/test/java/org/dromara/hutool/core/data/IdcardUtilTest.java b/hutool-core/src/test/java/org/dromara/hutool/core/data/IdcardUtilTest.java index 4da4bd133..4d2e6591e 100644 --- a/hutool-core/src/test/java/org/dromara/hutool/core/data/IdcardUtilTest.java +++ b/hutool-core/src/test/java/org/dromara/hutool/core/data/IdcardUtilTest.java @@ -163,20 +163,20 @@ public class IdcardUtilTest { @Test public void isValidHKCardIdTest(){ final String hkCard="P174468(6)"; - final boolean flag=IdcardUtil.isValidHKCard(hkCard); + final boolean flag=IdcardUtil.isValidCard10(hkCard); assertTrue(flag); } @Test public void isValidTWCardIdTest() { final String twCard = "B221690311"; - boolean flag = IdcardUtil.isValidTWCard(twCard); + boolean flag = IdcardUtil.isValidCard10(twCard); assertTrue(flag); final String errTwCard1 = "M517086311"; - flag = IdcardUtil.isValidTWCard(errTwCard1); + flag = IdcardUtil.isValidCard10(errTwCard1); assertFalse(flag); final String errTwCard2 = "B2216903112"; - flag = IdcardUtil.isValidTWCard(errTwCard2); + flag = IdcardUtil.isValidCard10(errTwCard2); assertFalse(flag); } @@ -196,8 +196,7 @@ public class IdcardUtilTest { @Test public void issueIAFOLITest() { final String idcard = "H01487002"; - assertFalse(IdcardUtil.isValidHKCard(idcard)); - assertNull(IdcardUtil.isValidCard10(idcard)); + assertFalse(IdcardUtil.isValidCard10(idcard)); assertFalse(IdcardUtil.isValidCard(idcard)); } }