weblog/doc/5、整合 SaToken 实现 JWT 登录功能/5.7 自定义 @PhoneNumber 手机号校验注解.md
2025-02-17 10:05:44 +08:00

8.0 KiB
Raw Blame History

在后端开发中数据校验是确保数据正确性非常重要的一个环节。Jakarta Validation以前是Bean ValidationJSR 380提供了一套丰富的标准注解来校验数据比如 @NotNull@NotBlank等,想必小伙伴们已经不陌生了。

比如,前面小节开发的获取手机验证码接口中,就对入参的 phone 字段添加了 @NotBlank 字符串非空校验注解,如下图所示。然而,单单校验字符串非空是不够的,如果用户提交的手机号格式有问题呢,比如提交了非数字、或者数字不满 11 位等等,这类的校验是没有现成的校验注解以供使用的,这个时候,就需要自定义校验注解了。

1. 自定义校验规则

接下来,我们就来亲手实现一个手机号校验注解。编辑 xiaoha-common 公共模块,添加 /validator 包,用于统一放置校验注解相关代码。首先,创建 PhoneNumberValidator 自定义校验类:

package com.quanxiaoha.framework.common.validator;

import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;

/**
 * @author: 犬小哈
 * @date: 2024/4/15 22:23
 * @version: v1.0.0
 * @description: TODO
 **/
public class PhoneNumberValidator implements ConstraintValidator<PhoneNumber, String> {

    @Override
    public void initialize(PhoneNumber constraintAnnotation) {
        // 这里进行一些初始化操作
    }

    @Override
    public boolean isValid(String phoneNumber, ConstraintValidatorContext context) {
        // 校验逻辑:正则表达式判断手机号是否为 11 位数字
        return phoneNumber != null && phoneNumber.matches("\\d{11}");
    }
}

解释一下:

PhoneNumberValidator 是一个用于自定义校验注解 @PhoneNumber 的验证器类。它实现了 ConstraintValidator 接口,用于验证一个字符串是否符合特定的手机号格式。

1. 实现 ConstraintValidator 接口

public class PhoneNumberValidator implements ConstraintValidator<PhoneNumber, String>

这行代码表明 PhoneNumberValidator 类实现了 ConstraintValidator<PhoneNumber, String> 接口。ConstraintValidator 接口有两个泛型参数:

  • PhoneNumber:自定义注解类型。
  • String:被校验的属性类型。

2. initialize 方法

@Override
public void initialize(PhoneNumber constraintAnnotation) {
}

initialize 方法是用来执行初始化操作的。这个方法在校验器实例化后会被调用,通常用来读取注解中的参数来设置校验器的初始状态。在这里,我们没有任何初始化操作,所以方法体是空的。

3. isValid 方法

@Override
public boolean isValid(String phoneNumber, ConstraintValidatorContext context) {
    return phoneNumber != null && phoneNumber.matches("\\d{11}");
}

isValid 方法包含了实际的校验逻辑。它有两个参数:

  • phoneNumber:需要验证的字符串,即被注解的属性值。
  • context:提供了一些校验的上下文信息,通常用来设置错误消息等。

校验逻辑的详细解释如下:

  • phoneNumber != null:首先检查 phoneNumber 是否为 null。如果为 null,则返回 false,表示无效。这里也可以选择返回 true,具体取决于业务需求是否允许空值。
  • phoneNumber.matches("\\d{11}"):如果 phoneNumber 不为 null,接着使用正则表达式 \\d{11} 验证字符串是否为 11 位的数字。\\d 表示匹配一个数字字符,{11} 表示匹配前面的模式正好 11 次。因此,这个正则表达式确保字符串是一个长度为 11 的纯数字字符串。

2. 自定义注解

接着,创建自定义注解 @PhoneNumber

如何创建自定义注解 @interface 类型的类?

上个项目中,就有很多小伙伴提问,如何通过 IDEA 创建 @interface 类型的?在高版本的 IDEA 中,有 Annotation 类型可供选择,如下图所示。低版本中如果没有,也可以先创建一个 class 类,再手动将 class 关键字改成 @interface 即可。

代码如下:

package com.quanxiaoha.framework.common.validator;

import jakarta.validation.Constraint;
import jakarta.validation.Payload;

import java.lang.annotation.*;

/**
 * @author: 犬小哈
 * @date: 2024/4/15 22:22
 * @version: v1.0.0
 * @description: 自定义手机号校验注解
 **/
@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PhoneNumberValidator.class)
public @interface PhoneNumber {

    String message() default "手机号格式不正确, 需为 11 位数字";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

解释一下注解的各个部分的作用:

1. @Target

@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.PARAMETER })

@Target 注解用于指定自定义注解可以应用的 Java 元素类型。在 @PhoneNumber 中,@Target 的参数包括以下几个元素类型:

  • ElementType.METHOD:可以应用于方法。
  • ElementType.FIELD:可以应用于字段。
  • ElementType.ANNOTATION_TYPE:可以应用于其他注解。
  • ElementType.PARAMETER:可以应用于方法参数。

这种组合使得 @PhoneNumber 注解可以被广泛使用在方法、字段、注解和参数上。

2. @Retention

@Retention(RetentionPolicy.RUNTIME)

@Retention 注解用于指定自定义注解的保留策略。RetentionPolicy.RUNTIME 表示该注解在运行时仍然可用(可以通过反射机制访问)。这对于校验注解非常重要,因为校验框架需要在运行时读取注解并执行相应的校验逻辑。

3. @Constraint

@Constraint(validatedBy = PhoneNumberValidator.class)

@Constraint 注解用于指定关联的验证器类。在 @PhoneNumber 中,validatedBy 属性指向 PhoneNumberValidator.class,即自定义注解 @PhoneNumber 使用 PhoneNumberValidator 类进行校验。

4. message

String message() default "手机号格式不正确, 需为 11 位数字";

message 元素用于定义验证失败时的错误消息。在使用注解时可以覆盖默认消息。default 关键字用于提供该元素的默认值。

3. 使用 @PhoneNumber 注解

自定义注解开发完毕后,我们为获取手机验证码接口的入参实体类中的 phone 字段,添加上此注解,代码如下:

package com.quanxiaoha.xiaohashu.auth.model.vo.verificationcode;

import com.quanxiaoha.framework.common.validator.PhoneNumber;
import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class SendVerificationCodeReqVO {

    @NotBlank(message = "手机号不能为空")
    @PhoneNumber
    private String phone;

}

3. 自测一波

最后,重启项目,来自测一波功能好不好使。重新请求接口,同时故意将 phone 手机号少写一位,点击发送

可以看到,自定义的 @PhoneNumer 校验注解工作正常,服务端成功返回了手机号格式不正确,需为 11 位数字的默认提示~

本小节源码下载

https://t.zsxq.com/RMxmf