• 身份证号码,格式校验:@IdCard(自定义注解)


    目标

    自定义一个用于校验 身份证号码 格式的注解@IdCard,能够和现有的 Validation 参数校验机制兼容,使用方式和其他校验注解保持一致(使用 @Valid 注解接口参数)。

    本文使用原生方式实现校验逻辑,校验规则的实现较为基础;Hutool工具提供了更加完善的校验工具,可以考虑使用其来实现校验逻辑。

    使用 Hutool 身份证号码格式校验工具,实现校验逻辑,参考博客如下:
    《身份证号码,格式校验:@IdCard(Validation + Hutool)》

    在这里插入图片描述

    校验逻辑

    有效格式

    符合国家标准。

    公民身份号码按照GB11643-1999《公民身份号码》国家标准编制,由18位数字组成:前6位为行政区划代码,第7至14位为出生日期码,第15至17位为顺序码,第18位为校验码

    严格校验

    本文采用的校验方式,采用严格校验,第18位校验码,只能为数字大写X小写x无法通过校验。

    不校验非空

    身份证号码注解,校验的是格式;不校验是否为空(null 或 空字符串)。如果身份证号码为空,此注解校验是可以通过的;

    是否校验非空,要根据业务逻辑来确定;如果业务逻辑需要校验非空,则使用注解 @NotEmpty

    核心代码

    需要定义的内容包含三个部分:

    1. 注解@IdCard
    2. 校验器IdCardValidator
    3. 校验工具类 IdCardUtil

    注解:@IdCard

    package com.example.core.validation.idcard;
    
    import javax.validation.Constraint;
    import javax.validation.Payload;
    import java.lang.annotation.Documented;
    import java.lang.annotation.Retention;
    import java.lang.annotation.Target;
    
    import static java.lang.annotation.ElementType.FIELD;
    import static java.lang.annotation.RetentionPolicy.RUNTIME;
    
    /**
     * 身份证号码。字符串必须是格式正确的身份证号码。
     * 

    * {@code null} 或 空字符串,是有效的(能够通过校验)。 *

    * 支持的类型:字符串 * * @author songguanxun * @since 1.0 */ @Target({FIELD}) @Retention(RUNTIME) @Documented @Constraint(validatedBy = IdCardValidator.class) public @interface IdCard { /** * @return the error message template */ String message() default "身份证号码,格式错误"; /** * @return the groups the constraint belongs to */ Class<?>[] groups() default {}; /** * @return the payload associated to the constraint */ Class<? extends Payload>[] payload() default {}; }

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    校验器:IdCardValidator

    package com.example.core.validation.idcard;
    
    import com.example.core.util.IdCardUtil;
    import org.springframework.util.ObjectUtils;
    
    import javax.validation.ConstraintValidator;
    import javax.validation.ConstraintValidatorContext;
    
    /**
     * 身份证号码,格式校验器
     */
    public class IdCardValidator implements ConstraintValidator<IdCard, String> {
    
        @Override
        public void initialize(IdCard constraintAnnotation) {
            ConstraintValidator.super.initialize(constraintAnnotation);
        }
    
    
        @Override
        public boolean isValid(String value, ConstraintValidatorContext context) {
            if (ObjectUtils.isEmpty(value)) {
                return true;
            }
    
            return IdCardUtil.isValid(value);
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    校验工具类

    package com.example.core.util;
    
    /**
     * 身份证号码,校验工具类
     */
    public class IdCardUtil {
    
        // 每位加权因子
        private static final int[] power = {7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2};
    
    
        /**
         * 是格式正确的身份证号码
         */
        public static boolean isValid(String idCard) {
            // null ,为假
            if (idCard == null) {
                return false;
            }
    
            // 非18位,为假
            if (idCard.length() != 18) {
                return false;
            }
    
            // 获取前17位
            String idCard17 = idCard.substring(0, 17);
            // 获取第18位
            String idCard18Code = idCard.substring(17, 18);
    
            // 前17位,不全部为数字,为假
            if (!isDigital(idCard17)) {
                return false;
            }
    
            char[] c = idCard17.toCharArray();
    
            int[] bit = convertCharToInt(c);
            int sum17 = getPowerSum(bit);
            // 将和值与11取模得到余数进行校验码判断
            String checkCode = getCheckCodeBySum(sum17);
            if (null == checkCode) {
                return false;
            }
            // 将身份证的第18位,与算出来的校码进行匹配,不相等就为假
            return idCard18Code.equals(checkCode);
        }
    
    
        /**
         * 数字验证
         */
        private static boolean isDigital(String str) {
            return str != null && !str.isEmpty() && str.matches("^[0-9]*$");
        }
    
    
        /**
         * 将字符数组转为整型数组
         */
        private static int[] convertCharToInt(char[] c) throws NumberFormatException {
            int[] a = new int[c.length];
            int k = 0;
            for (char temp : c) {
                a[k++] = Integer.parseInt(String.valueOf(temp));
            }
            return a;
        }
    
    
        /**
         * 将身份证的每位和对应位的加权因子相乘之后,再得到和值
         */
        private static int getPowerSum(int[] bit) {
            if (power.length != bit.length) {
                return 0;
            }
    
            int sum = 0;
            for (int i = 0; i < bit.length; i++) {
                for (int j = 0; j < power.length; j++) {
                    if (i == j) {
                        sum = sum + bit[i] * power[j];
                    }
                }
            }
            return sum;
        }
    
    
        /**
         * 将和值与11取模得到余数进行校验码判断
         *
         * @return 校验位
         */
        private static String getCheckCodeBySum(int sum17) {
            String checkCode = null;
            switch (sum17 % 11) {
                case 10:
                    checkCode = "2";
                    break;
                case 9:
                    checkCode = "3";
                    break;
                case 8:
                    checkCode = "4";
                    break;
                case 7:
                    checkCode = "5";
                    break;
                case 6:
                    checkCode = "6";
                    break;
                case 5:
                    checkCode = "7";
                    break;
                case 4:
                    checkCode = "8";
                    break;
                case 3:
                    checkCode = "9";
                    break;
                case 2:
                    checkCode = "X";
                    break;
                case 1:
                    checkCode = "0";
                    break;
                case 0:
                    checkCode = "1";
                    break;
            }
            return checkCode;
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136

    使用

    @IdCard 放在需要校验格式的 身份证号码 字段上。

    package com.example.web.response.model.param;
    
    import com.example.core.validation.idcard.IdCard;
    import io.swagger.v3.oas.annotations.media.Schema;
    import lombok.Data;
    
    @Data
    @Schema(name = "新增用户Param")
    public class UserAddParam {
        
        // 其他字段
    
        @IdCard
        @Schema(description = "身份证号码", example = "110101202301024130")
        private String idCard;
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    校验效果

    校验工具类,单元测试

    package com.example;
    
    import com.example.core.util.IdCardUtil;
    import lombok.extern.slf4j.Slf4j;
    import org.junit.jupiter.api.Test;
    
    @Slf4j
    public class IdCardTest {
    
        @Test
        void test() {
            test("110101202301024130");
            test("11010120230102857X");
            test("11010120230102857x");
            test("110101202301024130啊啊啊啊");
        }
    
    
        private void test(String idCard) {
            log.info("是否为身份证号码格式:{} = {}", idCard, IdCardUtil.isValid(idCard));
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    在这里插入图片描述

    接口测试

    校验结果为 成功

    在这里插入图片描述
    在这里插入图片描述

    校验结果为 失败

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

  • 相关阅读:
    【Linux】Linux终端执行docker内部shell脚本
    SpringBoot实战(1)
    可恶的一直按键又来了
    Python中的%3d %9.2f %02d %-*s %*s %.*s解释
    带有 Spring AOP 和自定义注解的 Java 观察者模式
    CTFHub | Refer注入
    代码随想录刷题记录 5 - 栈和队列
    【问题解决】 网关代理Nginx 301暴露自身端口号
    Impala计算日期差datediff
    26_ue4进阶末日生存游戏开发[僵尸添加动画和扣血效果]
  • 原文地址:https://blog.csdn.net/sgx1825192/article/details/133792981