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

2023-07-25  本文已影响0人  bingxuePI

一、公民身份号码

    /**
     * 公民身份号码是特征组合码,由十七位数字本体码和一位数字校验码组成.排列顺序从左至右依次为:
     * 六位数字地址码,八位数字出生日期码,三位数字顺序码和一位数字校验码。
     * 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;
    }
}

上一篇下一篇

猜你喜欢

热点阅读