1. package com.common.utils.util;
    2. import lombok.extern.slf4j.Slf4j;
    3. import org.apache.commons.lang3.StringUtils;
    4. import java.text.ParseException;
    5. import java.text.SimpleDateFormat;
    6. import java.util.Calendar;
    7. import java.util.Date;
    8. import java.util.HashMap;
    9. import java.util.Map;
    10. /**
    11. * @author X-MD
    12. * @date 2020/10/30 0030
    13. * @description: 身份证号码校验
    14. **/
    15. @Slf4j
    16. public class PersonIdCardUtil {
    17. /**
    18. * 省、直辖市代码表:
    19. * 11 : 北京 12 : 天津 13 : 河北 14 : 山西 15 : 内蒙古
    20. * 21 : 辽宁 22 : 吉林 23 : 黑龙江 31 : 上海 32 : 江苏
    21. * 33 : 浙江 34 : 安徽 35 : 福建 36 : 江西 37 : 山东
    22. * 41 : 河南 42 : 湖北 43 : 湖南 44 : 广东 45 : 广西 46 : 海南
    23. * 50 : 重庆 51 : 四川 52 : 贵州 53 : 云南 54 : 西藏
    24. * 61 : 陕西 62 : 甘肃 63 : 青海 64 : 宁夏 65 : 新疆
    25. * 71 : 台湾
    26. * 81 : 香港 82 : 澳门
    27. * 91 : 国外
    28. */
    29. private static String[] cityCode = {"11", "12", "13", "14", "15", "21",
    30. "22", "23", "31", "32", "33", "34", "35", "36", "37", "41", "42",
    31. "43", "44", "45", "46", "50", "51", "52", "53", "54", "61", "62",
    32. "63", "64", "65", "71", "81", "82", "91"};
    33. /**
    34. * 每位加权因子
    35. */
    36. private static int power[] = {7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5,
    37. 8, 4, 2};
    38. /**
    39. * 验证所有的身份证的合法性
    40. *
    41. * @param idcard 身份证
    42. * @return 合法返回true,否则返回false
    43. */
    44. public static boolean isValidatedAllIdcard(String idcard) {
    45. if (StringUtils.isBlank(idcard)) {
    46. return false;
    47. }
    48. if (idcard.length() == 15) {
    49. return validate15IDCard(idcard);
    50. }
    51. if (idcard.length() == 18) {
    52. return validate18Idcard(idcard);
    53. }
    54. return false;
    55. }
    56. /**
    57. * 判断18位身份证的合法性
    58. * 根据〖中华人民共和国国家标准GB11643-1999〗中有关公民身份号码的规定,公民身份号码是特征组合码,由十七位数字本体码和一位数字校验码组成。
    59. * 排列顺序从左至右依次为:六位数字地址码,八位数字出生日期码,三位数字顺序码和一位数字校验码。
    60. *
    61. * 顺序码: 表示在同一地址码所标识的区域范围内,对同年、同月、同 日出生的人编定的顺序号,顺序码的奇数分配给男性,偶数分配 给女性。
    62. *
    63. *
    64. * 1.前1、2位数字表示:所在省份的代码; 2.第3、4位数字表示:所在城市的代码; 3.第5、6位数字表示:所在区县的代码;
    65. * 4.第7~14位数字表示:出生年、月、日; 5.第15、16位数字表示:所在地的派出所的代码;
    66. * 6.第17位数字表示性别:奇数表示男性,偶数表示女性;
    67. * 7.第18位数字是校检码:也有的说是个人信息码,一般是随计算机的随机产生,用来检验身份证的正确性。校检码可以是0~9的数字,有时也用x表示。
    68. *
    69. *
    70. * 第十八位数字(校验码)的计算方法为: 1.将前面的身份证号码17位数分别乘以不同的系数。从第一位到第十七位的系数分别为:7 9 10 5 8 4
    71. * 2 1 6 3 7 9 10 5 8 4 2
    72. *
    73. *
    74. * 2.将这17位数字和系数相乘的结果相加。
    75. *
    76. *
    77. * 3.用加出来和除以11,看余数是多少
    78. *
    79. * 4.余数只可能有0 1 2 3 4 5 6 7 8 9 10这11个数字。其分别对应的最后一位身份证的号码为1 0 X 9 8 7 6 5 4 3
    80. * 2。
    81. *
    82. * 5.通过上面得知如果余数是2,就会在身份证的第18位数字上出现罗马数字的Ⅹ。如果余数是10,身份证的最后一位号码就是2。
    83. *
    84. *
    85. * @param idcard
    86. * @return
    87. */
    88. public static boolean validate18Idcard(String idcard) {
    89. if (idcard == null) {
    90. return false;
    91. }
    92. // 非18位为假
    93. int s = 18;
    94. if (idcard.length() != s) {
    95. log.error("身份证位数不正确!");
    96. return false;
    97. }
    98. // 获取前17位
    99. String idcard17 = idcard.substring(0, 17);
    100. // 前17位全部为数字
    101. if (!isDigital(idcard17)) {
    102. return false;
    103. }
    104. String provinceid = idcard.substring(0, 2);
    105. // 校验省份
    106. if (!checkProvinceid(provinceid)) {
    107. return false;
    108. }
    109. // 校验出生日期
    110. String birthday = idcard.substring(6, 14);
    111. SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
    112. try {
    113. Date birthDate = sdf.parse(birthday);
    114. String tmpDate = sdf.format(birthDate);
    115. // 出生年月日不正确
    116. if (!tmpDate.equals(birthday)) {
    117. return false;
    118. }
    119. } catch (ParseException e1) {
    120. return false;
    121. }
    122. // 获取第18位
    123. String idcard18Code = idcard.substring(17, 18);
    124. char c[] = idcard17.toCharArray();
    125. int bit[] = converCharToInt(c);
    126. int sum17 = getPowerSum(bit);
    127. // 将和值与11取模得到余数进行校验码判断
    128. String checkCode = getCheckCodeBySum(sum17);
    129. if (null == checkCode) {
    130. return false;
    131. }
    132. // 将身份证的第18位与算出来的校码进行匹配,不相等就为假
    133. if (!idcard18Code.equalsIgnoreCase(checkCode)) {
    134. return false;
    135. }
    136. return true;
    137. }
    138. /**
    139. * 校验15位身份证
    140. *
    141. * 只校验省份和出生年月日
    142. *
    143. * @param idcard
    144. * @return
    145. */
    146. public static boolean validate15IDCard(String idcard) {
    147. if (idcard == null) {
    148. return false;
    149. }
    150. // 非15位为假
    151. int s = 15;
    152. if (idcard.length() != s) {
    153. return false;
    154. }
    155. // 15全部为数字
    156. if (!isDigital(idcard)) {
    157. return false;
    158. }
    159. String provinceid = idcard.substring(0, 2);
    160. // 校验省份
    161. if (!checkProvinceid(provinceid)) {
    162. return false;
    163. }
    164. String birthday = idcard.substring(6, 12);
    165. SimpleDateFormat sdf = new SimpleDateFormat("yyMMdd");
    166. try {
    167. Date birthDate = sdf.parse(birthday);
    168. String tmpDate = sdf.format(birthDate);
    169. // 身份证日期错误
    170. if (!tmpDate.equals(birthday)) {
    171. return false;
    172. }
    173. } catch (ParseException e1) {
    174. return false;
    175. }
    176. return true;
    177. }
    178. /**
    179. * 将15位的身份证转成18位身份证
    180. *
    181. * @param idcard
    182. * @return
    183. */
    184. public static String convertIdcarBy15bit(String idcard) {
    185. if (idcard == null) {
    186. return null;
    187. }
    188. // 非15位身份证
    189. int s = 15;
    190. if (idcard.length() != s) {
    191. return null;
    192. }
    193. // 15全部为数字
    194. if (!isDigital(idcard)) {
    195. return null;
    196. }
    197. String provinceid = idcard.substring(0, 2);
    198. // 校验省份
    199. if (!checkProvinceid(provinceid)) {
    200. return null;
    201. }
    202. String birthday = idcard.substring(6, 12);
    203. SimpleDateFormat sdf = new SimpleDateFormat("yyMMdd");
    204. Date birthdate;
    205. try {
    206. birthdate = sdf.parse(birthday);
    207. String tmpDate = sdf.format(birthdate);
    208. // 身份证日期错误
    209. if (!tmpDate.equals(birthday)) {
    210. return null;
    211. }
    212. } catch (ParseException e1) {
    213. return null;
    214. }
    215. Calendar cday = Calendar.getInstance();
    216. cday.setTime(birthdate);
    217. String year = String.valueOf(cday.get(Calendar.YEAR));
    218. String idcard17 = idcard.substring(0, 6) + year + idcard.substring(8);
    219. char c[] = idcard17.toCharArray();
    220. String checkCode = "";
    221. // 将字符数组转为整型数组
    222. int bit[] = converCharToInt(c);
    223. int sum17 = 0;
    224. sum17 = getPowerSum(bit);
    225. // 获取和值与11取模得到余数进行校验码
    226. checkCode = getCheckCodeBySum(sum17);
    227. // 获取不到校验位
    228. if (null == checkCode) {
    229. return null;
    230. }
    231. // 将前17位与第18位校验码拼接
    232. idcard17 += checkCode;
    233. return idcard17;
    234. }
    235. /**
    236. * 校验省份
    237. *
    238. * @param provinceid
    239. * @return 合法返回TRUE,否则返回FALSE
    240. */
    241. private static boolean checkProvinceid(String provinceid) {
    242. for (String id : cityCode) {
    243. if (id.equals(provinceid)) {
    244. return true;
    245. }
    246. }
    247. return false;
    248. }
    249. /**
    250. * 数字验证
    251. *
    252. * @param str
    253. * @return
    254. */
    255. private static boolean isDigital(String str) {
    256. return str.matches("^[0-9]*$");
    257. }
    258. /**
    259. * 将身份证的每位和对应位的加权因子相乘之后,再得到和值
    260. *
    261. * @param bit
    262. * @return
    263. */
    264. private static int getPowerSum(int[] bit) {
    265. int sum = 0;
    266. if (power.length != bit.length) {
    267. return sum;
    268. }
    269. for (int i = 0; i < bit.length; i++) {
    270. for (int j = 0; j < power.length; j++) {
    271. if (i == j) {
    272. sum = sum + bit[i] * power[j];
    273. }
    274. }
    275. }
    276. return sum;
    277. }
    278. /**
    279. * 将和值与11取模得到余数进行校验码判断
    280. *
    281. * @param
    282. * @param sum17
    283. * @return 校验位
    284. */
    285. private static String getCheckCodeBySum(int sum17) {
    286. String checkCode = null;
    287. switch (sum17 % 11) {
    288. case 10:
    289. checkCode = "2";
    290. break;
    291. case 9:
    292. checkCode = "3";
    293. break;
    294. case 8:
    295. checkCode = "4";
    296. break;
    297. case 7:
    298. checkCode = "5";
    299. break;
    300. case 6:
    301. checkCode = "6";
    302. break;
    303. case 5:
    304. checkCode = "7";
    305. break;
    306. case 4:
    307. checkCode = "8";
    308. break;
    309. case 3:
    310. checkCode = "9";
    311. break;
    312. case 2:
    313. checkCode = "x";
    314. break;
    315. case 1:
    316. checkCode = "0";
    317. break;
    318. case 0:
    319. checkCode = "1";
    320. break;
    321. default:
    322. }
    323. return checkCode;
    324. }
    325. /**
    326. * 将字符数组转为整型数组
    327. *
    328. * @param c
    329. * @return
    330. * @throws NumberFormatException
    331. */
    332. private static int[] converCharToInt(char[] c) throws NumberFormatException {
    333. int[] a = new int[c.length];
    334. int k = 0;
    335. for (char temp : c) {
    336. a[k++] = Integer.parseInt(String.valueOf(temp));
    337. }
    338. return a;
    339. }
    340. /**
    341. * 通过身份证号码获取出生日期、性别、年龄
    342. * @param certificateNo
    343. * @return 返回的出生日期格式:1990-01-01 性别格式:F-女,M-男
    344. */
    345. public static Map<String, String> getBirAgeSex(String certificateNo) {
    346. String birthday = "";
    347. String age = "";
    348. String sexCode = "";
    349. int year = Calendar.getInstance().get(Calendar.YEAR);
    350. char[] number = certificateNo.toCharArray();
    351. boolean flag = true;
    352. if (number.length == 15) {
    353. for (int x = 0; x < number.length; x++) {
    354. if (!flag) return new HashMap<>();
    355. flag = Character.isDigit(number[x]);
    356. }
    357. } else if (number.length == 18) {
    358. for (int x = 0; x < number.length - 1; x++) {
    359. if (!flag) return new HashMap<>();
    360. flag = Character.isDigit(number[x]);
    361. }
    362. }
    363. if (flag && certificateNo.length() == 15) {
    364. birthday = "19" + certificateNo.substring(6, 8) + "-"
    365. + certificateNo.substring(8, 10) + "-"
    366. + certificateNo.substring(10, 12);
    367. sexCode = Integer.parseInt(certificateNo.substring(certificateNo.length() - 3, certificateNo.length())) % 2 == 0 ? "F" : "M";
    368. age = (year - Integer.parseInt("19" + certificateNo.substring(6, 8))) + "";
    369. } else if (flag && certificateNo.length() == 18) {
    370. birthday = certificateNo.substring(6, 10) + "-"
    371. + certificateNo.substring(10, 12) + "-"
    372. + certificateNo.substring(12, 14);
    373. sexCode = Integer.parseInt(certificateNo.substring(certificateNo.length() - 4, certificateNo.length() - 1)) % 2 == 0 ? "F" : "M";
    374. age = (year - Integer.parseInt(certificateNo.substring(6, 10))) + "";
    375. }
    376. Map<String, String> map = new HashMap<>();
    377. map.put("birthday", birthday);
    378. map.put("age", age);
    379. map.put("sexCode", sexCode);
    380. return map;
    381. }
    382. }