|
|
@@ -0,0 +1,311 @@
|
|
|
+package com.zhongzheng.common.utils;
|
|
|
+
|
|
|
+import java.text.ParseException;
|
|
|
+import java.text.SimpleDateFormat;
|
|
|
+import java.util.Calendar;
|
|
|
+import java.util.Date;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.regex.Pattern;
|
|
|
+
|
|
|
+
|
|
|
+public class IdCardUtils {
|
|
|
+
|
|
|
+ // 省份代码映射
|
|
|
+ private static final Map<String, String> PROVINCE_CODES = new HashMap<>();
|
|
|
+ // 加权因子
|
|
|
+ private static final int[] WEIGHT_FACTOR = {7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2};
|
|
|
+ // 校验码对应值
|
|
|
+ private static final char[] CHECK_CODE = {'1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'};
|
|
|
+
|
|
|
+ static {
|
|
|
+ // 初始化省份代码
|
|
|
+ PROVINCE_CODES.put("11", "北京");
|
|
|
+ PROVINCE_CODES.put("12", "天津");
|
|
|
+ PROVINCE_CODES.put("13", "河北");
|
|
|
+ PROVINCE_CODES.put("14", "山西");
|
|
|
+ PROVINCE_CODES.put("15", "内蒙古");
|
|
|
+ PROVINCE_CODES.put("21", "辽宁");
|
|
|
+ PROVINCE_CODES.put("22", "吉林");
|
|
|
+ PROVINCE_CODES.put("23", "黑龙江");
|
|
|
+ PROVINCE_CODES.put("31", "上海");
|
|
|
+ PROVINCE_CODES.put("32", "江苏");
|
|
|
+ PROVINCE_CODES.put("33", "浙江");
|
|
|
+ PROVINCE_CODES.put("34", "安徽");
|
|
|
+ PROVINCE_CODES.put("35", "福建");
|
|
|
+ PROVINCE_CODES.put("36", "江西");
|
|
|
+ PROVINCE_CODES.put("37", "山东");
|
|
|
+ PROVINCE_CODES.put("41", "河南");
|
|
|
+ PROVINCE_CODES.put("42", "湖北");
|
|
|
+ PROVINCE_CODES.put("43", "湖南");
|
|
|
+ PROVINCE_CODES.put("44", "广东");
|
|
|
+ PROVINCE_CODES.put("45", "广西");
|
|
|
+ PROVINCE_CODES.put("46", "海南");
|
|
|
+ PROVINCE_CODES.put("50", "重庆");
|
|
|
+ PROVINCE_CODES.put("51", "四川");
|
|
|
+ PROVINCE_CODES.put("52", "贵州");
|
|
|
+ PROVINCE_CODES.put("53", "云南");
|
|
|
+ PROVINCE_CODES.put("54", "西藏");
|
|
|
+ PROVINCE_CODES.put("61", "陕西");
|
|
|
+ PROVINCE_CODES.put("62", "甘肃");
|
|
|
+ PROVINCE_CODES.put("63", "青海");
|
|
|
+ PROVINCE_CODES.put("64", "宁夏");
|
|
|
+ PROVINCE_CODES.put("65", "新疆");
|
|
|
+ PROVINCE_CODES.put("71", "台湾");
|
|
|
+ PROVINCE_CODES.put("81", "香港");
|
|
|
+ PROVINCE_CODES.put("82", "澳门");
|
|
|
+ PROVINCE_CODES.put("91", "国外");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 身份证脱敏
|
|
|
+ * @param idCard 身份证号码
|
|
|
+ * @return 脱敏后的身份证(如:510***********1234)
|
|
|
+ */
|
|
|
+ public static String maskIdCard(String idCard) {
|
|
|
+ if (idCard == null || idCard.trim().isEmpty()) {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+
|
|
|
+ String trimmedIdCard = idCard.trim();
|
|
|
+ int length = trimmedIdCard.length();
|
|
|
+
|
|
|
+ if (length == 15) {
|
|
|
+ // 15位身份证:显示前6位和后3位
|
|
|
+ return trimmedIdCard.substring(0, 6) + "******" + trimmedIdCard.substring(12);
|
|
|
+ } else if (length == 18) {
|
|
|
+ // 18位身份证:显示前6位和后4位
|
|
|
+ return trimmedIdCard.substring(0, 6) + "********" + trimmedIdCard.substring(14);
|
|
|
+ } else {
|
|
|
+ // 其他情况:保留前后各4位
|
|
|
+ if (length <= 8) {
|
|
|
+ return trimmedIdCard; // 太短不脱敏
|
|
|
+ }
|
|
|
+ int keep = Math.min(4, length / 4);
|
|
|
+
|
|
|
+ // JDK 8 兼容方式生成星号
|
|
|
+ int starsCount = length - 2 * keep;
|
|
|
+ StringBuilder starsBuilder = new StringBuilder();
|
|
|
+ for (int i = 0; i < starsCount; i++) {
|
|
|
+ starsBuilder.append("*");
|
|
|
+ }
|
|
|
+ String stars = starsBuilder.toString();
|
|
|
+
|
|
|
+ return trimmedIdCard.substring(0, keep)
|
|
|
+ + stars
|
|
|
+ + trimmedIdCard.substring(length - keep);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 验证身份证号码是否有效
|
|
|
+ * @param idCard 身份证号码
|
|
|
+ * @return true-有效,false-无效
|
|
|
+ */
|
|
|
+ public static boolean isValidIdCard(String idCard) {
|
|
|
+ if (idCard == null || idCard.trim().isEmpty()) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ String trimmedIdCard = idCard.trim().toUpperCase();
|
|
|
+ int length = trimmedIdCard.length();
|
|
|
+
|
|
|
+ // 长度校验
|
|
|
+ if (length != 15 && length != 18) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 15位身份证校验
|
|
|
+ if (length == 15) {
|
|
|
+ return isValid15IdCard(trimmedIdCard);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 18位身份证校验
|
|
|
+ return isValid18IdCard(trimmedIdCard);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 验证15位身份证
|
|
|
+ */
|
|
|
+ private static boolean isValid15IdCard(String idCard) {
|
|
|
+ // 正则校验:全部为数字
|
|
|
+ if (!Pattern.matches("^[0-9]{15}$", idCard)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 省份校验
|
|
|
+ String provinceCode = idCard.substring(0, 2);
|
|
|
+ if (!PROVINCE_CODES.containsKey(provinceCode)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 出生日期校验
|
|
|
+ String birthday = "19" + idCard.substring(6, 12);
|
|
|
+ return isValidBirthday(birthday);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 验证18位身份证
|
|
|
+ */
|
|
|
+ private static boolean isValid18IdCard(String idCard) {
|
|
|
+ // 正则校验:前17位为数字,最后一位为数字或X
|
|
|
+ if (!Pattern.matches("^[0-9]{17}[0-9X]$", idCard)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 省份校验
|
|
|
+ String provinceCode = idCard.substring(0, 2);
|
|
|
+ if (!PROVINCE_CODES.containsKey(provinceCode)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 出生日期校验
|
|
|
+ String birthday = idCard.substring(6, 14);
|
|
|
+ if (!isValidBirthday(birthday)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 校验码校验
|
|
|
+ return checkCodeValid(idCard);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 校验码验证
|
|
|
+ */
|
|
|
+ private static boolean checkCodeValid(String idCard) {
|
|
|
+ if (idCard.length() != 18) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ char[] chars = idCard.toCharArray();
|
|
|
+ int sum = 0;
|
|
|
+
|
|
|
+ // 计算加权和
|
|
|
+ for (int i = 0; i < 17; i++) {
|
|
|
+ sum += (chars[i] - '0') * WEIGHT_FACTOR[i];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算校验码
|
|
|
+ char checkCode = CHECK_CODE[sum % 11];
|
|
|
+
|
|
|
+ // 校验码比对(不区分大小写)
|
|
|
+ return Character.toUpperCase(chars[17]) == checkCode;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 验证出生日期是否有效
|
|
|
+ */
|
|
|
+ private static boolean isValidBirthday(String birthday) {
|
|
|
+ if (birthday == null || birthday.length() != 8) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
|
|
|
+ sdf.setLenient(false); // 严格模式
|
|
|
+
|
|
|
+ try {
|
|
|
+ Date birthDate = sdf.parse(birthday);
|
|
|
+ Date now = new Date();
|
|
|
+
|
|
|
+ // 出生日期不能晚于当前日期
|
|
|
+ if (birthDate.after(now)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 校验日期合理性(如月份1-12,日根据月份不同)
|
|
|
+ Calendar cal = Calendar.getInstance();
|
|
|
+ cal.setTime(birthDate);
|
|
|
+
|
|
|
+ int year = cal.get(Calendar.YEAR);
|
|
|
+ int month = cal.get(Calendar.MONTH) + 1;
|
|
|
+ int day = cal.get(Calendar.DAY_OF_MONTH);
|
|
|
+
|
|
|
+ // 简单校验(实际可以使用更精确的校验)
|
|
|
+ return year >= 1900 && month >= 1 && month <= 12 && day >= 1 && day <= 31;
|
|
|
+
|
|
|
+ } catch (ParseException e) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从身份证获取出生日期
|
|
|
+ */
|
|
|
+ public static Date getBirthdayFromIdCard(String idCard) {
|
|
|
+ if (!isValidIdCard(idCard)) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ String birthdayStr;
|
|
|
+ if (idCard.length() == 15) {
|
|
|
+ birthdayStr = "19" + idCard.substring(6, 12);
|
|
|
+ } else {
|
|
|
+ birthdayStr = idCard.substring(6, 14);
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
|
|
|
+ return sdf.parse(birthdayStr);
|
|
|
+ } catch (ParseException e) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 从身份证获取性别
|
|
|
+ * @param idCard 身份证
|
|
|
+ * @return 1-男,0-女,-1-未知
|
|
|
+ */
|
|
|
+ public static int getGenderFromIdCard(String idCard) {
|
|
|
+ if (!isValidIdCard(idCard)) {
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ String genderStr;
|
|
|
+ if (idCard.length() == 15) {
|
|
|
+ genderStr = idCard.substring(14, 15); // 第15位
|
|
|
+ } else {
|
|
|
+ genderStr = idCard.substring(16, 17); // 第17位
|
|
|
+ }
|
|
|
+
|
|
|
+ int genderNum = Integer.parseInt(genderStr);
|
|
|
+ return genderNum % 2 == 1 ? 1 : 0; // 奇数男,偶数女
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取省份信息
|
|
|
+ */
|
|
|
+ public static String getProvinceFromIdCard(String idCard) {
|
|
|
+ if (!isValidIdCard(idCard)) {
|
|
|
+ return "未知";
|
|
|
+ }
|
|
|
+
|
|
|
+ String provinceCode = idCard.substring(0, 2);
|
|
|
+ return PROVINCE_CODES.getOrDefault(provinceCode, "未知");
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 15位身份证转18位
|
|
|
+ */
|
|
|
+ public static String convert15To18(String idCard15) {
|
|
|
+ if (!isValid15IdCard(idCard15)) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 在第6位后插入19
|
|
|
+ String idCard17 = idCard15.substring(0, 6) + "19" + idCard15.substring(6);
|
|
|
+
|
|
|
+ // 计算校验码
|
|
|
+ char[] chars = idCard17.toCharArray();
|
|
|
+ int sum = 0;
|
|
|
+
|
|
|
+ for (int i = 0; i < 17; i++) {
|
|
|
+ sum += (chars[i] - '0') * WEIGHT_FACTOR[i];
|
|
|
+ }
|
|
|
+
|
|
|
+ char checkCode = CHECK_CODE[sum % 11];
|
|
|
+
|
|
|
+ return idCard17 + checkCode;
|
|
|
+ }
|
|
|
+
|
|
|
+}
|