身份证自定义校验注解validation

该文章提供了一个使用Java编写的身份证号码校验的注解和工具类。注解包含多个验证规则,如非空、长度和格式验证。工具类包含了一套完整的身份证号码合法性检查方法,包括正则匹配、省编码验证、校验位计算等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

/**
 * 身份证号 @IdCard代码如下
 */
@Excel(name = "身份证号", orderNum = "1", width = 50)
@NotNull(message = "身份证号不能为空")
@Length(min = 18, max = 18, message = "身份证号长度有误")
@IdCard(message = "身份证格式不对")
private String idCard;
/**
 * 校验身份证号码
 * @author zhouyanhaung
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = IdCardValidator.class)
@Documented
public @interface IdCard {
 
    String message() default "";
 
    Class<?>[] groups() default {};
 
    Class<? extends Payload>[] payload() default {};
}
/**
 * @author zhouyanhuang
 * @date 2021-08-06 14:50
 * 实现注解IdCard
 */
public class IdCardValidator implements ConstraintValidator<IdCard, String> {
 
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        // 校验逻辑...
        if (value == null) {
            return true;
        }
        return IdCardUtil.identityCodeValid(value).isSuccess();
    }
}

编写身份证校验的工具类

import io.micrometer.common.util.StringUtils;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.regex.Pattern;

/**
 * @Author zhouyanhuang:
 * @Date 2023/4/24 17:06
 * @Version 1.0
 */

public class IdCardUtil {
    /**
     * 一代身份证
     */
    private static final Integer FIRST_GENERATION_ID_CARD = 15;

    /**
     * 二代身份证
     */
    private static final Integer SECOND_GENERATION_ID_CARD = 18;

    /**
     * 省编码前2位
     */
    private static final List<String> PROVINCES = Arrays.asList(
            "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", "91"
    );

    /**
     * 加权因子
     */
    private static final Integer[] FACTOR = {7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2};

    /**
     * 身份证正则
     */
    private static final String REGEX = "^[1-9]\\d{7}((0\\d)|(1[0-2]))(([0|1|2]\\d)|3[0-1])\\d{3}$|^[1-9]\\d{5}[1-9]\\d{3}((0\\d)|(1[0-2]))(([0|1|2]\\d)|3[0-1])\\d{3}([0-9]|X)$";

    /**
     * 校验位
     */
    private static final String[] PARITY = {"1", "0", "X", "9", "8", "7", "6", "5", "4", "3", "2"};

    /**
     * format
     */
    private static final SimpleDateFormat FORMAT = new SimpleDateFormat("yyyy-MM-dd");

    /**
     * now现在
     */
    private static final Date NOW = new Date();

    /**
     * 验证身份证是否合法
     *
     * @param idCard 身份证号码
     * @return 身份证信息对象
     */
    public static IdCardInformation identityCodeValid(String idCard) {
        final String finalIdCard = trim(idCard);
        // 校验身份证
        if (StringUtils.isBlank(finalIdCard) || finalIdCard.length() != 18) {
            return new IdCardInformation();
        }
        // 匹配正则
        if (!Pattern.compile(REGEX).matcher(finalIdCard).matches()) {
            return new IdCardInformation();
        }
        // 校验身份证前2位省编码是否正确
        if (PROVINCES.stream().noneMatch(i -> i.equals(finalIdCard.substring(0, 2)))) {
            return new IdCardInformation();
        }
        // 切割厂
        String[] code = finalIdCard.split("");
        // 拿到最后一位字母
        String lastOneCode = code[17];
        int sum = 0, ai, wi;
        // 取前17位,(每一位数字 * 每一位数字在加权因子数组的对应的数字) 之和
        for (int i = 0; i < 17; i++) {
            ai = Integer.parseInt(code[i]);
            wi = FACTOR[i];
            sum += ai * wi;
        }
        // 算出来的和 mod 11 得到 最后一位,再把传进来的身份证的最后一位和算出来的最后一位对比
        return lastOneCode.equals(PARITY[sum % 11]) ? new IdCardInformation(true, getSex(finalIdCard), getAge(finalIdCard), getBirthday(finalIdCard)) : new IdCardInformation();
    }

    /**
     * 根据身份证号获取性别
     *
     * @param idCard 身份证号码
     * @return 性别
     */
    private static boolean getSex(String idCard) {
        // 一代身份证
        if (idCard.length() == FIRST_GENERATION_ID_CARD) {
            return Integer.parseInt(idCard.substring(14, 15)) % 2 != 0;
        } else if (idCard.length() == SECOND_GENERATION_ID_CARD) {
            // 二代身份证 判断性别
            return Integer.parseInt(idCard.substring(16).substring(0, 1)) % 2 != 0;
        } else {
            return true;
        }
    }

    /**
     * 根据身份证号获取年龄
     *
     * @param idCard 身份证号码
     * @return age
     */
    private static Integer getAge(String idCard) {
        int age = 0;
        //15位身份证号
        if (idCard.length() == FIRST_GENERATION_ID_CARD) {
            // 身份证上的年份(15位身份证为1980年前的)
            String idCardYear = "19" + idCard.substring(6, 8);
            // 身份证上的月份
            String idCardMonth = idCard.substring(8, 10);
            age = getAge(idCardYear, idCardMonth);
            //18位身份证号
        } else if (idCard.length() == SECOND_GENERATION_ID_CARD) {
            // 身份证上的年份
            String idCardYear = idCard.substring(6).substring(0, 4);
            // 身份证上的月份
            String idCardMonth = idCard.substring(10).substring(0, 2);
            age = getAge(idCardYear, idCardMonth);
        }
        return age;
    }

    /**
     * 计算年月获得年龄
     *
     * @param idCardYear  身份证的上年份
     * @param idCardMonth 身份的证上月份
     * @return age
     */
    private static Integer getAge(String idCardYear, String idCardMonth) {
        // 当前年份
        String nowYear = FORMAT.format(NOW).substring(0, 4);
        // 当前月份
        String nowMonth = FORMAT.format(NOW).substring(5, 7);
        int yearDiff = Integer.parseInt(nowYear) - Integer.parseInt(idCardYear);
        if (Integer.parseInt(idCardMonth) <= Integer.parseInt(nowMonth)) {
            return yearDiff + 1;
            // 当前用户还没过生
        } else {
            return yearDiff;
        }
    }

    /**
     * 获取出生日期  yyyy年MM月dd日
     *
     * @param idCard 身份证号码
     * @return 生日
     */
    private static LocalDate getBirthday(String idCard) {
        String year = "";
        String month = "";
        String day = "";
        //15位身份证号
        if (idCard.length() == FIRST_GENERATION_ID_CARD) {
            // 身份证上的年份(15位身份证为1980年前的)
            year = "19" + idCard.substring(6, 8);
            //身份证上的月份
            month = idCard.substring(8, 10);
            //身份证上的日期
            day = idCard.substring(10, 12);
        } else if (idCard.length() == SECOND_GENERATION_ID_CARD) {
            // 18位身份证号 身份证上的年份
            year = idCard.substring(6).substring(0, 4);
            // 身份证上的月份
            month = idCard.substring(10).substring(0, 2);
            //身份证上的日期
            day = idCard.substring(12).substring(0, 2);
        }
        try {
            return LocalDate.now().withYear(Integer.parseInt(year)).withMonth(Integer.parseInt(month)).withDayOfMonth(Integer.parseInt(day));
        } catch (Exception e) {
            int start = idCard.indexOf("19");
            return LocalDate.now().withYear(Integer.parseInt(idCard.substring(start, start + 4))).withMonth(Integer.parseInt(idCard.substring(start + 4, start + 6)))
                    .withDayOfMonth(Integer.parseInt(idCard.substring(start + 6, start + 8)));
        }
    }

    /**
     * 去空格
     *
     * @param str 处理字符串
     * @return 结果字符串
     */
    private static String trim(String str) {
        return str.replaceAll("\n", "").replace(" ", "").trim();
    }

    /**
     * 身份证信息
     */
    @Getter
    @NoArgsConstructor
    public static class IdCardInformation {

        /**
         * 是否成功
         */
        private boolean success = false;

        /**
         * 性别
         */
        private boolean sex;

        /**
         * 年龄
         */
        private Integer age;

        /**
         * 生日
         */
        private LocalDate birthday;

        public IdCardInformation(boolean success, boolean sex, Integer age, LocalDate birthday) {
            this.success = success;
            this.sex = sex;
            this.age = age;
            this.birthday = birthday;
        }
    }
}
<think>我们正在讨论Java对象字段校验注解Validation Annotations)。根据引用内容,我们可以使用JSR 380(Bean Validation 2.0)规范中的注解,以及如何自定义校验注解。 常用的内置校验注解包括: - `@NotNull`:验证字段值不为null - `@NotEmpty`:验证字符串、集合、数组等不为空(长度大于0) - `@NotBlank`:验证字符串不为空且至少包含一个非空白字符 - `@Size`:验证字符串、集合、数组等的长度在指定范围内 - `@Min`:验证数字值的最小值 - `@Max`:验证数字值的最大值 - `@Pattern`:验证字符串是否匹配正则表达式 - `@Email`:验证字符串是否为有效的电子邮件地址 - `@Positive` 和 `@PositiveOrZero`:验证数字为正数或正数或零 - `@Negative` 和 `@NegativeOrZero`:验证数字为负数或负数或零 - `@Past` 和 `@PastOrPresent`:验证日期是过去或过去及现在 - `@Future` 和 `@FutureOrPresent`:验证日期是将来或将来及现在 此外,我们还可以自定义校验注解,如引用[1]和引用[2]所示,通过定义注解和实现`ConstraintValidator`接口来创建自定义校验规则。 例如,我们有一个用户注册对象,需要校验用户名、密码、电子邮件和手机号码(自定义校验): ```java public class UserRegistration { @NotBlank(message = "用户名不能为空") @Size(min = 4, max = 20, message = "用户名长度必须在4到20个字符之间") private String username; @NotBlank(message = "密码不能为空") @Size(min = 6, max = 20, message = "密码长度必须在6到20个字符之间") private String password; @Email(message = "电子邮件格式不正确") private String email; // 假设我们自定义了一个手机号码校验注解 @MobilePhone(message = "手机号码格式不正确") private String mobile; // 省略getter和setter } ``` 自定义手机号码校验注解`@MobilePhone`的实现步骤(如引用[1]): 1. 定义注解`@MobilePhone`,并指定校验器为`MobilePhoneValidator`。 2. 实现`ConstraintValidator<MobilePhone, String>`接口,在`isValid`方法中编写校验逻辑。 另外,引用[3]提到了使用`groups`属性来分组校验,例如在新增时需要校验手机号,而更新时不需要。我们可以定义分组接口,然后在注解中指定组。 例如: ```java public interface CreateGroup {} public interface UpdateGroup {} public class UserVO { @NotBlank(groups = {CreateGroup.class, UpdateGroup.class}) private String name; @MobilePhone(groups = CreateGroup.class) // 只在创建时校验 private String mobile; } ``` 在Controller中使用`@Validated`注解指定分组: ```java @PostMapping("/users") public ResponseEntity<?> createUser(@Validated(CreateGroup.class) @RequestBody UserVO user) { // ... } @PutMapping("/users/{id}") public ResponseEntity<?> updateUser(@Validated(UpdateGroup.class) @RequestBody UserVO user) { // ... } ``` 引用[4]提供了一些常用的正则表达式,我们可以直接使用`@Pattern`注解进行校验,而不必自定义注解。例如,校验手机号码也可以这样写: ```java @Pattern(regexp = "^1[3|4|5|6|7|8|9][0-9]\\d{8}$", message = "手机号码格式错误") private String mobile; ``` 但是,自定义注解可以使代码更简洁,并且可以复用。 总结一下,Java对象字段校验可以通过内置注解自定义注解实现,并且可以利用分组来控制不同场景下的校验规则。 §§相关问题§§ 1. 如何在Spring Boot中启用校验? 2. 自定义校验注解时,如何实现校验逻辑? 3. 分组校验的使用场景有哪些? 4. 除了字段校验,还有哪些校验方式?</think>### Java 对象字段校验注解详解 在 Java 中,通过 **JSR 380 (Bean Validation 2.0)** 规范提供的注解可实现对象字段校验。以下是常用注解及用法: #### 一、内置校验注解 1. **基础校验** - `@NotNull`:字段值不能为 null - `@NotEmpty`:字符串/集合/数组非空(长度 > 0) - `@NotBlank`:字符串至少包含一个非空白字符 ```java public class User { @NotBlank(message = "用户名不能为空") private String username; } ``` 2. **范围校验** - `@Size(min, max)`:长度范围 - `@Min(value)`:最小值 - `@Max(value)`:最大值 ```java @Size(min = 6, max = 20, message = "密码长度需在6-20位") private String password; ``` 3. **格式校验** - `@Email`:邮箱格式 - `@Pattern(regexp)`:正则表达式匹配 ```java @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式错误") private String mobile; ``` #### 二、自定义校验注解(引用[1][2]) 1. **定义注解** ```java @Target({FIELD}) @Retention(RUNTIME) @Constraint(validatedBy = IdCardValidator.class) // 绑定校验器 public @interface IdCard { String message() default "身份证号格式错误"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; } ``` 2. **实现校验器** ```java public class IdCardValidator implements ConstraintValidator<IdCard, String> { @Override public boolean isValid(String value, ConstraintValidatorContext context) { // 实现身份证校验逻辑 return value.matches("^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[0-9Xx]$"); } } ``` 3. **使用自定义注解** ```java public class Employee { @IdCard private String idCard; } ``` #### 三、分组校验(引用[3]) 通过 `groups` 属性实现不同场景的校验: ```java public class UserDTO { // 创建时必填 @NotBlank(groups = CreateGroup.class) private String mobile; // 更新时必填 @NotBlank(groups = UpdateGroup.class) private String userId; } // 定义分组接口 public interface CreateGroup {} public interface UpdateGroup {} ``` Controller 中使用: ```java @PostMapping("/create") public void createUser(@Validated(CreateGroup.class) @RequestBody UserDTO dto) { // 仅校验CreateGroup分组字段 } ``` #### 四、正则表达式示例(引用[4]) - 手机号:`^1[3-9]\\d{9}$` - IP地址:`^((2[0-4]\\d|25[0-5]|[01]?\\d\\d?)\\.){3}(2[0-4]\\d|25[0-5]|[01]?\\d\\d?)$` - 端口号:`^([1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$` #### 五、启用校验 在 Spring Boot 中: 1. 添加依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> ``` 2. 在 Controller 中使用 `@Valid` 或 `@Validated`: ```java @PostMapping("/save") public ResponseEntity<?> save(@Valid @RequestBody User user) { // ... } ``` ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值