JAVA:正则验证(严格验证)

一、公民身份号码

    /**
     * 公民身份号码是特征组合码,由十七位数字本体码和一位数字校验码组成.排列顺序从左至右依次为:
     * 六位数字地址码,八位数字出生日期码,三位数字顺序码和一位数字校验码。
     * 1、地址码:表示编码对象常住户口所在县(市、旗、区)的行政区划代码,按 GB/T 2260 的规定执行。
     * 2、出生日期码:表示编码对象出生的年、月、日,按 * GB/T 7408 的规定执行。年、月、日代码之间不用分隔符。
     * 例:某人出生日期为 1966年10月26日,其出生日期码为 19661026。
     * 3、顺序码:表示在同一地址码所标识的区域范围内,
     * 对同年、同月、同日出生的人编定的顺序号,顺序码的奇数分配给男性,偶数千分配给女性。
     * 4、校验码:校验码采用ISO 7064:1983,MOD 11-2 校验码系统。
     * (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 1
     * (2)计算模 Y = mod(S, 11)
     * (3)通过模得到对应的校验码
     * Y: 0 1 2 3 4 5 6 7 8 9 10
     * 校验码: 1 0 X 9 8 7 6 5 4 3 2
     */
    // 加权因子
    private static final int[] weight = new int[]{7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2, 1};
    // 校验码
    private static final int[] checkDigit = new int[]{1, 0, 'X', 9, 8, 7, 6, 5, 4, 3, 2};

    /**
     * 省、直辖市代码表,身份证号的前6位为地址信息,我们只验证前两位
     */
    private static final String[] CITY_CODE = {"11", "12", "13", "14", "15", "21", "22", "23", "31", "32", "33", "34", "35", "36", "37", "41",
            "42", "43", "44", "45", "46", "50", "51", "52", "53", "54", "61", "62", "63", "64", "65", "71", "81", "82", "83", "91"};

    /**
     * 验证身份证是否符合格式(严格验证)
     *
     * @param iDCardNo
     * @return
     */
    public static boolean isIDCard_CN(String iDCardNo) {

        //1.格式验证
        if(iDCardNo == null || !Pattern.matches(REGEX_ID_CARD, iDCardNo)){
            return false;
        }

        //2.验证省、直辖市代码。市、区不作验证,没有规则限制,数字即可
        if(Arrays.binarySearch(CITY_CODE, iDCardNo.substring(0, 2)) == -1){
            return false;
        }

        //3.补齐15位身份证号码
        if (iDCardNo.length() == 15) {
            //15位身份证上的生日中的年份没有19,要加上
            String eighteenCardID = iDCardNo.substring(0, 6) + "19" + iDCardNo.substring(6, 15);
            iDCardNo = eighteenCardID + Validator.getCheckDigit(eighteenCardID);
        }

        //4.验证生日,生日可能存在输入20180231这种情况,所以使用Calendar处理校验
        try {
            String birthday = iDCardNo.substring(6, 14);
            // 如果输入的日期为20180231,通过转换的后realBirthday为20180303
            Calendar calendar = Calendar.getInstance();
            calendar.set(Calendar.YEAR, Integer.parseInt(birthday.substring(0, 4)));
            calendar.set(Calendar.MONTH, Integer.parseInt(birthday.substring(4, 6)) - 1);//月份从0开始,所以减1
            calendar.set(Calendar.DAY_OF_MONTH, Integer.parseInt(birthday.substring(6, 8)));
            Date realBirthday = calendar.getTime();

            //获取当前系统时间
            Calendar cal = Calendar.getInstance();
            //如果出生日期大于当前时间,则抛出异常
            if (cal.before(realBirthday)) {
                //throw new IllegalArgumentException("生日在当前日期之后。这是难以置信的!");
                return false;
            }

            // 转换失败或不相等
            if(realBirthday == null || !birthday.equals(new SimpleDateFormat("yyyyMMdd").format(realBirthday))){
                return false;
            }
        }
        catch (Exception e) {
            return false;
        }


        //5.验证位验证,计算规则为:身份证前17位数字,对应乘以每位的权重因子,然后相加得到数值X,与11取模获得余数,得到数值Y,通过Y得到校验码。
        //获取输入身份证上的最后一位,它是校验码
        String checkDigit = iDCardNo.substring(17, 18);
        //比较获取的校验码与本方法生成的校验码是否相等
        boolean flag = checkDigit.equals(Validator.getCheckDigit(iDCardNo));


        return flag;
    }


    /**
     * 计算18位身份证的校验码
     *
     * @param eighteenCardID 18位身份证
     * @return
     */
    private static String getCheckDigit(String eighteenCardID) {
        int remaining = 0;
        if (eighteenCardID.length() == 18) {
            eighteenCardID = eighteenCardID.substring(0, 17);
        }

        if (eighteenCardID.length() == 17) {
            int sum = 0;
            int[] a = new int[17];
            //先对前17位数字的权求和
            for (int i = 0; i < 17; i++) {
                String k = eighteenCardID.substring(i, i + 1);
                a[i] = Integer.parseInt(k);
            }
            for (int i = 0; i < 17; i++) {
                sum = sum + weight[i] * a[i];
            }
            //再与11取模
            remaining = sum % 11;
        }
        return remaining == 2 ? "X" : String.valueOf(checkDigit[remaining]);
    }

二、验证是否是车牌号(中国)

    /**
     * 正则表达式:验证是否是车牌号(中国)
     */
    public static final String REGEX_CARNUM_CN = "^[\u4e00-\u9fa5]{1}[A-Z]{1}[0-9A-Z]{4,6}$";
    public static final String REGEX_CARNUM_CN_StrictRules = "^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领]{1}[A-Z]{1}([A-HJ-NP-Z0-9]{5,6}|([A-HJ-NP-Z0-9]{4}[挂学警港澳使领]{0,1}))$";

    /**
     * 验证是否是车牌号(中国)
     * 如:赣MW6673,赣A123456,
     * 京A0006警,赣A1234挂,赣AF023学,粤ZF023港,粤ZF023澳,沪A0023领,使014578,WJ京3500B,NL53152
     * PS:“挂”车为车辆类型,实际上是:赣A1234
     *
     * @param carnum
     * @return 校验通过返回true,否则返回false
     */
    public static boolean isCarNum_CN(String carnum) {
        //REGEX_CARNUM_CN_StrictRules
        return Pattern.matches(REGEX_CARNUM_CN_StrictRules, carnum);
    }

三、验证是否是车辆VIN码(严格验证)

    /**
     * 验证是否是车辆VIN码(严格验证)
     * 
     * @param vin
     * @return 校验通过返回true,否则返回false
     */
    public static boolean isVinNum(String vin) {

        //1:长度不为17
        if (null == vin || vin.length() != 17) {
            return false;
        }

        Map<Integer, Integer> vinMapWeighting = null;
        Map<Character, Integer> vinMapValue = null;

        vinMapWeighting = new HashMap<Integer, Integer>();
        vinMapValue = new HashMap<Character, Integer>();
        vinMapWeighting.put(1, 8);
        vinMapWeighting.put(2, 7);
        vinMapWeighting.put(3, 6);
        vinMapWeighting.put(4, 5);
        vinMapWeighting.put(5, 4);
        vinMapWeighting.put(6, 3);
        vinMapWeighting.put(7, 2);
        vinMapWeighting.put(8, 10);
        vinMapWeighting.put(9, 0);
        vinMapWeighting.put(10, 9);
        vinMapWeighting.put(11, 8);
        vinMapWeighting.put(12, 7);
        vinMapWeighting.put(13, 6);
        vinMapWeighting.put(14, 5);
        vinMapWeighting.put(15, 4);
        vinMapWeighting.put(16, 3);
        vinMapWeighting.put(17, 2);

        vinMapValue.put('0', 0);
        vinMapValue.put('1', 1);
        vinMapValue.put('2', 2);
        vinMapValue.put('3', 3);
        vinMapValue.put('4', 4);
        vinMapValue.put('5', 5);
        vinMapValue.put('6', 6);
        vinMapValue.put('7', 7);
        vinMapValue.put('8', 8);
        vinMapValue.put('9', 9);
        vinMapValue.put('A', 1);
        vinMapValue.put('B', 2);
        vinMapValue.put('C', 3);
        vinMapValue.put('D', 4);
        vinMapValue.put('E', 5);
        vinMapValue.put('F', 6);
        vinMapValue.put('G', 7);
        vinMapValue.put('H', 8);
        vinMapValue.put('J', 1);
        vinMapValue.put('K', 2);
        vinMapValue.put('M', 4);
        vinMapValue.put('L', 3);
        vinMapValue.put('N', 5);
        vinMapValue.put('P', 7);
        vinMapValue.put('R', 9);
        vinMapValue.put('S', 2);
        vinMapValue.put('T', 3);
        vinMapValue.put('U', 4);
        vinMapValue.put('V', 5);
        vinMapValue.put('W', 6);
        vinMapValue.put('X', 7);
        vinMapValue.put('Y', 8);
        vinMapValue.put('Z', 9);


        boolean reultFlag = false;
        String uppervin = vin.toUpperCase();
        //VIN中不会包含 I、O、Q 三个英文字母
        if (uppervin.indexOf("O") >= 0 || uppervin.indexOf("I") >= 0|| uppervin.indexOf("Q") >= 0) {
            reultFlag = false;
        } else {
            char[] vinArr = uppervin.toCharArray();
            int amount = 0;
            for (int i = 0; i < vinArr.length; i++) {
                //VIN码从从第一位开始,码数字的对应值×该位的加权值,计算全部17位的乘积值相加
                amount += vinMapValue.get(vinArr[i]) * vinMapWeighting.get(i + 1);
            }
            //乘积值相加除以11、若余数为10,即为字母X
            if (amount % 11 == 10) {
                if (vinArr[8] == 'X') {
                    reultFlag = true;
                } else {
                    reultFlag = false;
                }

            } else {
                //VIN码从从第一位开始,码数字的对应值×该位的加权值,
                //计算全部17位的乘积值相加除以11,所得的余数,即为第九位校验值
                if (amount % 11 != vinMapValue.get(vinArr[8])) {
                    reultFlag = false;
                } else {
                    reultFlag = true;
                }
            }

        }
        return reultFlag;
    }

四、验证是否是银行卡号(严格验证)

银行卡验证,采用Luhn算法

    /**
     * 正则表达式:验证是否是银行卡号(严格验证)
     * 目前银联卡几乎都支持校验码算法,但是也不排除极个别不支持此算法的,如杭州银行早期发行的西湖卡。
     *
     * @param bandCardNumber 银行卡号
     * @return 校验通过返回true,否则返回false
     */
    public static boolean isBankCard(String bandCardNumber) {
        return LuhnUtil.checkString(bandCardNumber);
    }


/**
 * Luhn算法工具类
 * 参考来源:https://www.jianshu.com/p/13fb9e3556da
 *
 * Luhn算法
 * Luhn算法,也称为“模10”算法,是一种简单的校验和(Checksum)算法,一般用于验证身份识别号码,例如信用卡号码、国际移动设备识别码(International Mobile Equipment Identity,缩写为IMEI),美国供应商识别号码,加拿大社会保险号码,以色列身份证号码,希腊社会安全号码等。
 * Luhn算法在ISO/IEC 7812-1中定义,使用Luhn算法进行字符串的校验以及生成校验数字,它不是一种安全的加密哈希函数,设计它的目的只是防止意外出错而不是恶意攻击,即我们常说的防君子不防小人。
 * 使用Luhn算法校验的步骤:
 *     从右边第1个数字(校验数字)开始偶数位乘以2;
 *     把步骤1种获得的乘积的各位数字与原号码中未乘2的各位数字相加;
 *     如果步骤2得到的总和模10为0,则校验通过。
 * 需要注意:2017年发布的ISO/IEC 7812-1中,删除了对MII的定义描述,并将IIN码由6位扩展到了8位,但是由于总位数仍然最多19位,所以中间的个人账户号码对应的最大位数由12位减少至10位。所以卡BIN不再只是6位,也需要考虑兼容8位。
 */
public class LuhnUtil {

    /**
     * 校验字符串
     * <p>
     * 1. 从右边第1个数字(校验数字)开始偶数位乘以2;<br>
     * 2. 把在步骤1种获得的乘积的各位数字与原号码中未乘2的各位数字相加;<br>
     * 3. 如果在步骤2得到的总和模10为0,则校验通过。
     * </p>
     *
     * @param withCheckDigitString 含校验数字的字符串
     * @return true - 校验通过<br>
     *         false-校验不通过
     * @throws IllegalArgumentException 如果字符串为空或不是8~19位的数字
     */
    public static boolean checkString(String withCheckDigitString) {
        if (withCheckDigitString == null) {
            throw new IllegalArgumentException();
        }
        // 6位IIN+最多12位自定义数字+1位校验数字
        // 注意ISO/IEC 7812-1:2017中重新定义8位IIN+最多10位自定义数字+1位校验数字
        // 这里为了兼容2017之前的版本,使用8~19位数字校验
        if (!withCheckDigitString.matches("^\\d{8,19}$")) {
            throw new IllegalArgumentException();
        }
        return sum(withCheckDigitString) % 10 == 0;
    }

    /**
     * 计算校验数字
     * <p>
     * 1. 从右边第1个数字(校验数字)开始偶数位乘以2;<br>
     * 2. 把在步骤1种获得的乘积的各位数字与原号码中未乘2的各位数字相加;<br>
     * 3. 用10减去在步骤2得到的总和模10,得到校验数字。
     * </p>
     *
     * @param withoutCheckDigitString 不含校验数字的字符串
     * @return 校验数字
     * @throws IllegalArgumentException 如果字符串为空或不是7~18位的数字
     */
    public static int computeCheckDigit(String withoutCheckDigitString) {
        if (withoutCheckDigitString == null) {
            throw new IllegalArgumentException();
        }
        // 6位IIN+最多12位自定义数字
        // 注意ISO/IEC 7812-1:2017中重新定义8位IIN+最多10位自定义数字
        // 这里为了兼容2017之前的版本,使用7~18位数字校验
        if (!withoutCheckDigitString.matches("^\\d{7,18}$")) {
            throw new IllegalArgumentException();
        }
        // 因为是不含校验数字的字符串,为了统一sum方法,在后面补0,不会影响计算
        return 10 - sum(withoutCheckDigitString + "0") % 10;
    }

    /**
     * 根据Luhn算法计算字符串各位数字之和
     * <p>
     * 1. 从右边第1个数字(校验数字)开始偶数位乘以2;<br>
     * 2. 把在步骤1种获得的乘积的各位数字与原号码中未乘2的各位数字相加。<br>
     * </p>
     *
     * @param str
     * @return
     */
    private static int sum(String str) {
        char[] strArray = str.toCharArray();
        int n = strArray.length;
        int sum = 0;
        for (int i = n; i >= 1; i--) {
            int a = strArray[n - i] - '0';
            // 偶数位乘以2
            if (i % 2 == 0) {
                a *= 2;
            }
            // 十位数和个位数相加,如果不是偶数位,不乘以2,则十位数为0
            sum = sum + a / 10 + a % 10;
        }
        return sum;
    }
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容