All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.feiyizhan.idcard.IdCardUtils Maven / Gradle / Ivy

package io.github.feiyizhan.idcard;

import io.github.feiyizhan.idcard.data.ZoneCodeUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;

import java.time.LocalDate;
import java.time.temporal.ChronoField;
import java.time.temporal.ChronoUnit;
import java.util.*;

import static io.github.feiyizhan.idcard.DateUtils.*;

/**
 *
 * 身份证工具类
 * 1、号码的结构
 * 公民身份号码是特征组合码,由十七位数字本体码和一位校验码组成。从左至右依次为:六位数字地址码,
 * 八位数字出生日期码,三位数字顺序码和一位数字校验码。
 * 2、地址码(前六位数)
 * 表示编码对象常住户口所在县(市、旗、区)的行政区划代码,按GB/T2260的规定执行。
 * 3、出生日期码(第七位至十四位)
 * 表示编码对象出生的年、月、日,按GB/T7408的规定执行,年、月、日代码之间不用分隔符。
 * 4、顺序码(第十五位至十七位)
 * 表示在同一地址码所标识的区域范围内,对同年、同月、同日出生的人编定的顺序号,
 * 顺序码的奇数分配给男性,偶数分配给女性。
 * 5、校验码(第十八位数)
 * (1)十七位数字本体码加权求和公式 S = Sum(Ai Wi), i = 0, , 16 ,先对前17位数字的权求和 ;
 * Ai:表示第i位置上的身份证号码数字值; Wi:表示第i位置上的加权因子 Wi: 7 9 10 5 8 4 2 1 6 3 7 9 10 5 8 4 2
 * (2)计算模 Y = mod(S, 11)
 * (3)通过模( 0 1 2 3 4 5 6 7 8 9 10)得到对应的校验码 Y:1 0 X 9 8 7 6 5 4 3 2
 *
 * @author 徐明龙 XuMingLong 2019-07-23
 **/
public class IdCardUtils {

    /**
     * 加权值
     * @author 徐明龙 XuMingLong 2019-07-23
     */
    private final static int[] POWER_LIST = {7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2};

    /**
     * 校验位
     * @author 徐明龙 XuMingLong 2019-07-23
     */
    private final static char[] PARITY_BIT = {'1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'};


    /**
     * 身份证允许的最小日期
     * @author 徐明龙 XuMingLong 2019-07-23
     */
    private static final LocalDate MIN_DATE = convertToDate("19000101",DATE_FORMAT_1);

    /**
     * 生成可通过校验的假数据
     * 

* is18Bit 是否生成 18 位号码,默认为 true,false生成15位号码; *

* address 地址,即省市县三级地区官方全称,如北京市、台湾省、香港特别行政区、深圳市、黄浦区等, * 可以只提供省或者市,将会取省或市下随机的区县,默认或参数非法,则生成合法的随机地址; *

* year 出生日期-年,合法的值为1900~本年,默认或参数非法,则随机取1900~本年的年; *

* month 出生日期-月,合法的值为1-12,默认或参数非法,则随机取1~12的月; *

* day 日,合法的值为1-31, 默认或参数非法,则随机取1~31的日; *

* 1、如果随机到年月为本年月,且如果指定的日大于`当天`,则随机取1~`当天`的日; *

* 2、如果随机到的年月不是本年月,且如果指定的日大于`所在月的最后一天`,则随机取1~`所在月的最后一天`的日; *

* 3、否则使用指定的日; *

* sex 性别,1为男,0为女,默认或参数非法,则生成合法的随机性别; * @author 徐明龙 XuMingLong 2019-07-25 * @param is18Bit 是否生成 18 位号码 * @param address 地址,即省市县三级地区官方全称 * @param year 出生日期-年 * @param month 出生日期-月 * @param day 出生日期-日 * @param sex 性别 * @return java.lang.String */ public static String fakeId(Boolean is18Bit,String address,Integer year,Integer month,Integer day,Integer sex ){ //设置长度 int length = BooleanUtils.isNotFalse(is18Bit)?18:15; //获取区域编码 String zoneCode = ZoneCodeUtils.getRandomZoneCode(address); //获取出生日期 LocalDate birthday = DateUtils.getRandomDate(year,month,day); //获取顺序码 int sequenceCode = RandomUtils.nextInt(1,999); sex = sex==null || (sex >1 && sex <0) ? RandomUtils.nextInt(0,2):sex; int evenOrOdd = (sequenceCode&1); if(sex == 1 && evenOrOdd == 1 ){ //需要偶数,但随机到奇数 sequenceCode+=1; }else if(sex==0 && evenOrOdd == 0){ //需要奇数,但随机到偶数 sequenceCode+=1; } //计算校验码 StringBuilder sb = new StringBuilder(); sb.append(zoneCode); if(length==15){ sb.append(birthday.format(DATE_FORMAT_1).substring(2)); }else{ sb.append(birthday.format(DATE_FORMAT_1)); } sb.append(String.format("%03d",sequenceCode)); //转换为字符数组 if(length==18){ final char[] idCardArray = new char[18]; sb.getChars(0,17,idCardArray,0); sb.setLength(0); //获取校验码 idCardArray[17] = getParityBit(idCardArray); return new String(idCardArray); }else{ return sb.toString(); } } /** * 升级15位身份证为18位,如果已经是18位身份证,直接返回,如果身份证校验失败,返回null * @author 徐明龙 XuMingLong 2019-07-25 * @param idCard 身份证号码 * @return java.lang.String */ public static String upgradeId(String idCard){ if(!isValid(idCard)){ return null; } if(idCard.length()==18){ return idCard; } /* * 升级算法为: 出生日期转换为8位的日期,再重新计算出校验位 *

* 6位日期转换为8位日期时,前两位的年设置为19,因此对于1900年之前的出生的人身份证会处理结果会不正确,现在应该没有19世纪的人了吧:-) */ //先升级为17 StringBuilder sb = new StringBuilder(); sb.append(idCard.substring(0,6)).append("19").append(idCard.substring(6)); //转换为字符数组 final char[] idCardArray = new char[18]; sb.getChars(0,17,idCardArray,0); sb.setLength(0); //获取校验码 idCardArray[17] = getParityBit(idCardArray); return new String(idCardArray); } /** * 获取身份证信息,如果身份证无效返回null * @author 徐明龙 XuMingLong 2019-07-24 * @param idCard 身份证号码 * @return io.github.feiyizhan.idcard.IdCardInfo */ public static IdCardInfo getIdCardInfo(String idCard){ if(!isValid(idCard)){ return null; } //获取长度 int length = idCard.length(); //获取出生日期 String birthdayStr = getBirthday(idCard); //获取LocalDate 对象的出生日期 LocalDate birthday = convertToDate(birthdayStr, DATE_FORMAT_1); //获取年龄 int age = (int)ChronoUnit.YEARS.between(birthday,getToday()); //获取区域编码 String zoneCode = idCard.substring(0, 6); //获取区域信息 String address = ZoneCodeUtils.getZoneFullDescription(zoneCode,birthday.get(ChronoField.YEAR),"-"); List addressList = Arrays.asList(StringUtils.split(address,"-")); address = StringUtils.remove(address,"-"); //获取顺序码 String sequenceCode = getSequenceCode(idCard); //获取性别 int sex = Integer.parseInt(sequenceCode)&1; //获取生肖 String chineseZodiac = ChineseZodiacUtils.getChineseZodiac(birthday.get(ChronoField.YEAR)); //获取星座 String constellation = ConstellationUtils.getConstellationByMonthAndDay(birthdayStr.substring(4)); //获取校验码 String checkBit = null; if(length==18){ checkBit = idCard.substring(length-1).toUpperCase(); } //判断是否废弃 boolean abandoned = !ZoneCodeUtils.isExistedInCurrent(zoneCode); IdCardInfo info = IdCardInfo.builder() .zoneCode(zoneCode) .birthday(birthday.format(DATE_FORMAT_2)) .age(age) .address(address) .addressList(addressList) .chineseZodiac(chineseZodiac) .constellation(constellation) .length(length) .sex(sex) .checkBit(checkBit) .abandoned(abandoned) .build(); return info; } /** * 校验身份证号是否有效,支持15位和18位身份证,不验证地区编号,因为地区编号数据目前没有一个准确的数据源 * @author 徐明龙 XuMingLong 2020-03-26 * @param idCard 身份证号码 * @return boolean */ public static boolean isValidWithDoNotVerifyRegionCode(String idCard){ //先校验身份证号长度 if (idCard == null || (idCard.length() != 15 && idCard.length() != 18)){ return false; } if(idCard.length()==15){ return isValid_15(idCard,false); }else{ return isValid_18(idCard,false); } } /** * 校验身份证号是否有效,支持15位和18位身份证 * @author 徐明龙 XuMingLong 2019-07-23 * @param idCard 身份证号码 * @return boolean */ @Deprecated public static boolean isValid(String idCard){ //先校验身份证号长度 if (idCard == null || (idCard.length() != 15 && idCard.length() != 18)){ return false; } if(idCard.length()==15){ return isValid_15(idCard,true); }else{ return isValid_18(idCard,true); } } /** * 校验15位身份证号码 * @author 徐明龙 XuMingLong 2019-07-23 * @param idCard 身份证号码 * @param verifyRegionCode 是否验证地区编号 * @return boolean */ private static boolean isValid_15(String idCard,boolean verifyRegionCode){ //转换为字符数组 final char[] idCardArray = idCard.toCharArray(); for (int i = 0; i < idCardArray.length; i++) { if (idCardArray[i] < '0' || idCardArray[i] > '9'){ return false; } } //校验区位码 if(verifyRegionCode){ String zoneCode = idCard.substring(0, 6); if(!ZoneCodeUtils.isExisted(zoneCode)){ return false; } } //校验日期 if(!checkBirthDay(getBirthday(idCard))){ return false; } return true; } /** * 校验18位身份证号码 * @author 徐明龙 XuMingLong 2019-07-23 * @param idCard 身份证号码 * @param verifyRegionCode 是否验证地区编号 * @return boolean */ private static boolean isValid_18(String idCard,boolean verifyRegionCode){ //转换为大写的字符数组 final char[] idCardArray = idCard.toUpperCase().toCharArray(); //检查每一位的是否有效,并计算前17位的权和 int sumPower = 0; for (int i = 0; i < idCardArray.length; i++) { if (i == idCardArray.length - 1 && idCardArray[i] == 'X'){ break; } if (idCardArray[i] < '0' || idCardArray[i] > '9'){ return false; } if (i < idCardArray.length - 1) { sumPower += (idCardArray[i] - '0') * POWER_LIST[i]; } } //校验区位码 if(verifyRegionCode){ String zoneCode = idCard.substring(0, 6); if(!ZoneCodeUtils.isExisted(zoneCode)){ return false; } } //校验日期,日期必须有效,且不能大于当天,不能小于1900-01-01 if(!checkBirthDay(getBirthday(idCard))){ return false; } //校验"校验位" return idCardArray[idCardArray.length - 1] == PARITY_BIT[sumPower % 11]; } /** * 获取顺序码 * @author 徐明龙 XuMingLong 2019-07-24 * @param idCard 身份证号码 * @return java.lang.String */ private static String getSequenceCode(String idCard){ return idCard.length()==18?idCard.substring(14, 17):idCard.substring(12); } /** * 获取身份证上出生日期 * @author 徐明龙 XuMingLong 2019-07-24 * @param idCard 身份证号码 * @return java.lang.String */ private static String getBirthday(String idCard){ return idCard.length()==18?idCard.substring(6, 14):String.join("","19",idCard.substring(6, 12)); } /** * 检查出生日期,校验日期,日期必须有效,且不能大于当天,不能小于1900-01-01 * @author 徐明龙 XuMingLong 2019-07-23 * @param dateStr 出生日期字符串 * @return boolean */ private static boolean checkBirthDay(String dateStr){ LocalDate date = convertToDate(dateStr, DATE_FORMAT_1); return !(date==null|| date.isAfter(getToday()) || date.isBefore(MIN_DATE)); } /** * 获取日历中的两位数的年 * @author 徐明龙 XuMingLong 2019-07-23 * @return int */ private static int getYear2BitWithCalendar() { //获取当前系统日落 GregorianCalendar curDay = new GregorianCalendar(TimeZone.getTimeZone("UTC+8")); //获取日历中的年 int curYear = curDay.get(Calendar.YEAR); //获取两位数的年 int year2bit = Integer.parseInt(String.valueOf(curYear).substring(2)); return year2bit; } /** * 获取校验码 * @author 徐明龙 XuMingLong 2019-07-25 * @param idCardArray * @return char */ private static char getParityBit(final char[] idCardArray){ //计算校验位 int sumPower = 0; for (int i = 0; i < 17; i++) { sumPower += (idCardArray[i] - '0') * POWER_LIST[i]; } //获取校验码 return PARITY_BIT[sumPower % 11]; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy