zoukankan      html  css  js  c++  java
  • 面向对象的15、18位中国大陆身份证号码解析、工具

    最近项目中需要给用户增加身份证号字段,参考了几位别人的实现。
    特点:
    1、面向对象:把身份证号封装为一个类,解析各个字段、验证有效性都是对象上的实例方法。对比那种公开多个静态方法的工具类的方式,我觉得这种面向对象的方式更自然一些。
    2、不可变的。身份证号对象是不可变的,减少使用中的复杂性。
    3、不是线程安全的。
     

      1 import java.text.SimpleDateFormat;
      2 import java.util.Date;
      3  
      4 /**
      5  * 身份证号码,可以解析身份证号码的各个字段,以及验证身份证号码是否有效<br>
      6  * 身份证号码构成:6位地址编码+8位生日+3位顺序码+1位校验码
      7  *
      8  * @author liuex
      9  *
     10  */
     11 public class IDCard {
     12     /**
     13      * 完整的身份证号码
     14      */
     15     private final String cardNumber;
     16     // 缓存身份证是否有效,因为验证有效性使用频繁且计算复杂
     17     private Boolean cacheValidateResult = null;
     18     // 缓存出生日期,因为出生日期使用频繁且计算复杂
     19     private Date cacheBirthDate = null;
     20  
     21     public boolean validate() {
     22         if (null == cacheValidateResult) {
     23             boolean result = true;
     24             // 身份证号不能为空
     25             result = result && (null != cardNumber);
     26             // 身份证号长度是18(新证)
     27             result = result && NEW_CARD_NUMBER_LENGTH == cardNumber.length();
     28             // 身份证号的前17位必须是阿拉伯数字
     29             for (int i = 0; result && i < NEW_CARD_NUMBER_LENGTH - 1; i++) {
     30                 char ch = cardNumber.charAt(i);
     31                 result = result && ch >= '0' && ch <= '9';
     32             }
     33             // 身份证号的第18位校验正确
     34             result = result
     35                     && (calculateVerifyCode(cardNumber) == cardNumber
     36                             .charAt(NEW_CARD_NUMBER_LENGTH - 1));
     37             // 出生日期不能晚于当前时间,并且不能早于1900年
     38             try {
     39                 Date birthDate = this.getBirthDate();
     40                 result = result && null != birthDate;
     41                 result = result && birthDate.before(new Date());
     42                 result = result && birthDate.after(MINIMAL_BIRTH_DATE);
     43                 /**
     44                  * 出生日期中的年、月、日必须正确,比如月份范围是[1,12],日期范围是[1,31],还需要校验闰年、大月、小月的情况时,
     45                  * 月份和日期相符合
     46                  */
     47                 String birthdayPart = this.getBirthDayPart();
     48                 String realBirthdayPart = this.createBirthDateParser().format(
     49                         birthDate);
     50                 result = result && (birthdayPart.equals(realBirthdayPart));
     51             } catch (Exception e) {
     52                 result = false;
     53             }
     54             // TODO 完整身份证号码的省市县区检验规则
     55             cacheValidateResult = Boolean.valueOf(result);
     56         }
     57         return cacheValidateResult;
     58     }
     59  
     60     /**
     61      * 如果是15位身份证号码,则自动转换为18位
     62      *
     63      * @param cardNumber
     64      */
     65     public IDCard(String cardNumber) {
     66         if (null != cardNumber) {
     67             cardNumber = cardNumber.trim();
     68             if (OLD_CARD_NUMBER_LENGTH == cardNumber.length()) {
     69                 cardNumber = contertToNewCardNumber(cardNumber);
     70             }
     71         }
     72         this.cardNumber = cardNumber;
     73     }
     74  
     75     public String getCardNumber() {
     76         return cardNumber;
     77     }
     78  
     79     public String getAddressCode() {
     80         this.checkIfValid();
     81         return this.cardNumber.substring(0, 6);
     82     }
     83  
     84     public Date getBirthDate() {
     85         if (null == this.cacheBirthDate) {
     86             try {
     87                 this.cacheBirthDate = this.createBirthDateParser().parse(
     88                         this.getBirthDayPart());
     89             } catch (Exception e) {
     90                 throw new RuntimeException("身份证的出生日期无效");
     91             }
     92         }
     93         return new Date(this.cacheBirthDate.getTime());
     94     }
     95  
     96     public boolean isMale() {
     97         return 1 == this.getGenderCode();
     98     }
     99  
    100     public boolean isFemal() {
    101         return false == this.isMale();
    102     }
    103  
    104     /**
    105      * 获取身份证的第17位,奇数为男性,偶数为女性
    106      *
    107      * @return
    108      */
    109     private int getGenderCode() {
    110         this.checkIfValid();
    111         char genderCode = this.cardNumber.charAt(NEW_CARD_NUMBER_LENGTH - 2);
    112         return (((int) (genderCode - '0')) & 0x1);
    113     }
    114  
    115     private String getBirthDayPart() {
    116         return this.cardNumber.substring(6, 14);
    117     }
    118  
    119     private SimpleDateFormat createBirthDateParser() {
    120         return new SimpleDateFormat(BIRTH_DATE_FORMAT);
    121     }
    122  
    123     private void checkIfValid() {
    124         if (false == this.validate()) {
    125             throw new RuntimeException("身份证号码不正确!");
    126         }
    127     }
    128  
    129     // 身份证号码中的出生日期的格式
    130     private final static String BIRTH_DATE_FORMAT = "yyyyMMdd";
    131     // 身份证的最小出生日期,1900年1月1日
    132     private final static Date MINIMAL_BIRTH_DATE = new Date(-2209017600000L);
    133     private final static int NEW_CARD_NUMBER_LENGTH = 18;
    134     private final static int OLD_CARD_NUMBER_LENGTH = 15;
    135     /**
    136      * 18位身份证中最后一位校验码
    137      */
    138     private final static char[] VERIFY_CODE = { '1', '0', 'X', '9', '8', '7',
    139             '6', '5', '4', '3', '2' };
    140     /**
    141      * 18位身份证中,各个数字的生成校验码时的权值
    142      */
    143     private final static int[] VERIFY_CODE_WEIGHT = { 7, 9, 10, 5, 8, 4, 2, 1,
    144             6, 3, 7, 9, 10, 5, 8, 4, 2 };
    145  
    146     /**
    147      * <li>校验码(第十八位数):<br/>
    148      * <ul>
    149      * <li>十七位数字本体码加权求和公式 S = Sum(Ai * Wi), i = 0...16 ,先对前17位数字的权求和;
    150      * Ai:表示第i位置上的身份证号码数字值 Wi:表示第i位置上的加权因子 Wi: 7 9 10 5 8 4 2 1 6 3 7 9 10 5 8 4
    151      * 2;</li>
    152      * <li>计算模 Y = mod(S, 11)</li>
    153      * <li>通过模得到对应的校验码 Y: 0 1 2 3 4 5 6 7 8 9 10 校验码: 1 0 X 9 8 7 6 5 4 3 2</li>
    154      * </ul>
    155      *
    156      * @param cardNumber
    157      * @return
    158      */
    159     private static char calculateVerifyCode(CharSequence cardNumber) {
    160         int sum = 0;
    161         for (int i = 0; i < NEW_CARD_NUMBER_LENGTH - 1; i++) {
    162             char ch = cardNumber.charAt(i);
    163             sum += ((int) (ch - '0')) * VERIFY_CODE_WEIGHT[i];
    164         }
    165         return VERIFY_CODE[sum % 11];
    166     }
    167  
    168     /**
    169      * 把15位身份证号码转换到18位身份证号码<br>
    170      * 15位身份证号码与18位身份证号码的区别为:<br>
    171      * 1、15位身份证号码中,"出生年份"字段是2位,转换时需要补入"19",表示20世纪<br>
    172      * 2、15位身份证无最后一位校验码。18位身份证中,校验码根据根据前17位生成
    173      *
    174      * @param cardNumber
    175      * @return
    176      */
    177     private static String contertToNewCardNumber(String oldCardNumber) {
    178         StringBuilder buf = new StringBuilder(NEW_CARD_NUMBER_LENGTH);
    179         buf.append(oldCardNumber.substring(0, 6));
    180         buf.append("19");
    181         buf.append(oldCardNumber.substring(6));
    182         buf.append(IDCard.calculateVerifyCode(buf));
    183         return buf.toString();
    184     }
    185 }
  • 相关阅读:
    js 数组详解(javascript array)
    CentOS 修改IP地址, DNS, 网关
    Leetcode 652.寻找重复的子树
    Leetcode 650.只有两个键的键盘
    Leetcode 649.Dota2参议院
    Leetcode 648.单词替换
    Leetcode 647.回文子串
    Leetcode 645.最长数对链
    Leetcode 643.子数组最大平均数I
    Leetcode 640.求解方程
  • 原文地址:https://www.cnblogs.com/Devin-Blog/p/5492077.html
Copyright © 2011-2022 走看看