SpringBoot JSR-303 + AOP 自定义注解校验参数 - 知乎 - 参考

SpringBoot OAuth2.0 使用短信验证码登录授权

江景:SpringBoot OAuth2.0 使用短信验证码登录授权​zhuanlan.zhihu.com图标

SpringBoot OAuth2.0 封装登录、刷新令牌接口

江景:SpringBoot OAuth2.0 封装登录、刷新令牌接口​zhuanlan.zhihu.com图标

保证校验接口传的参数是合法的,可以使用 JSR-303 、 AOP 的方式对接受的参数进行校验处理

  • 发送短信接口参数:
  • 输入的 phone 字段手机号码不能为空
  • type 字段短信类型不能为空且类型必须是在指定的范围
  • 登录接口参数:
  • loginType字段不能为空且类型必须是在指定的范围

加入 aop 依赖

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    
    <dependency>
      <groupId>org.hibernate.validator</groupId>
      <artifactId>hibernate-validator</artifactId>
    </dependency>

参数为空校验

  • 使用 JSR-303 注解即可
@Data
public class SmsCodeVO {
​
 @NotBlank(message = "手机号码不能为空")
 private String phone;
​
 @NotBlank(message = "短信类型不能为空")
 private String type;
}

发送短信接口

  • 使用 @Valid 注解,封装的 VO 后面必须加上 BindingResult result
  • 必须加上 BindingResult , 使用 AOP 获取错误结果
@RestController
@RequestMapping(value = "/api/v1/sms")
@Slf4j
public class SmsCodeController {
  @Resource
  private SmsCodeSenderService smsCodeSenderService;
​
  @PostMapping("/send")
  public R<?> sendSmsCode(@Valid SmsCodeVO smsCodeVO, BindingResult result) {
    return R.ok(smsCodeSenderService.sendSmsCode(smsCodeVO));
  }
}

使用 AOP 切面,对程序进行处理

  • @Aspect 标记这个类为一个切面
  • @Component 加入 Spring 容器
  • @Order(2) 顺序
@Aspect
@Component
@Order(2)
public class BindingResultAspect {
    // 切入点
    // 切入点表达式 对 com.moose.operator.web.controller 包名下的任意类任意方法都处理
  @Pointcut("execution(public * com.moose.operator.web.controller.*.*(..))")
  public void validateAnnotation() {
  }
​
    // 环绕通知处理切面结果 ProceedingJoinPoint
  @Around("validateAnnotation()")
  public Object doAround(ProceedingJoinPoint point) throws Throwable {
    Object[] args = point.getArgs();
    for (Object arg : args) {
      if (arg instanceof BindingResult) {
          // 可以拿到 接口定义 BindingResult,对 BindingResult 进行细粒度处理
        BindingResult result = (BindingResult) arg;
        if (result.hasErrors()) {
          FieldError fieldError = result.getFieldError();
          String message = ResultCode.PARAMS_VALIDATE_FAIL.getMessage();
          Integer code = ResultCode.PARAMS_VALIDATE_FAIL.getCode();
          if (ObjectUtils.isNotEmpty(fieldError)) {
            message = fieldError.getDefaultMessage();
          }
            // 抛给全局异常捕获处理
          throw new BusinessException(message, code);
        }
      }
    }
    return point.proceed();
  }
}

启动服务测试

localhost:7000/api/v1/sms/send?phone=&type=sms_login

  • 不输入手机号码

  • 输入手机号码

自定义注解

  • 对输入发送短信的类型进行限制,必须在指定的范围

创建 ValueIn 注解

/**
 * @author taohua
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD})
@Constraint(validatedBy = {ValueInValidator.class})
public @interface ValueIn {
​
  Class<?> value();
​
  String message() default "";
​
  Class<?>[] groups() default {};
​
  Class<? extends Payload>[] payload() default {};
}
​

自定义注解校验器 ValueInValidator

/**
 * @author taohua
 */
@Slf4j
public class ValueInValidator implements ConstraintValidator<ValueIn, Object>, Annotation {
  private final List<Object> values = new ArrayList<>();
​
  @Override
  public void initialize(ValueIn valueIn) {
    Class<?> clz = valueIn.value();
    Object[] objectArr = clz.getEnumConstants();
    try {
      Method method = clz.getMethod("getValue");
      Object value = null;
      for (Object obj : objectArr) {
        value = method.invoke(obj);
        values.add(value);
      }
    } catch (Exception e) {
      log.error("[处理枚举校验异常]", e);
    }
  }
​
  @Override
  public Class<? extends Annotation> annotationType() {
    return null;
  }
​
  @Override
  public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) {
    if (value instanceof String) {
      return values.contains(value);
    }
    return Boolean.FALSE;
  }
}

使用自定义注解

  • 修改 SmsCodeVO,创建 SmsCodeEnum 短信枚举类,定义短信的类型
  • 在 type 加上 ValueIn 注解
  @NotBlank(message = "短信类型不能为空")
  @ValueIn(value = SmsCodeEnum.class, message = "短信类型不正确")
  private String type;

SmsCodeEnum 枚举类

/**
 * @author taohua
 */
​
public enum SmsCodeEnum {
​
  /**
   * 注册
   */
  REGISTER(SmsTypeConstant.REGISTER),
​
  /**
   * sms login
   */
  SMS_LOGIN(SmsTypeConstant.SMS_LOGIN),
​
  /**
   * reset phone number
   */
  RESET_PHONE(SmsTypeConstant.RESET_PHONE),
​
  /**
   * 重置密码
   */
  RESET_PASSWORD(SmsTypeConstant.RESET_PASSWORD);
​
  private final String value;
​
  SmsCodeEnum(String value) {
    this.value = value;
  }
​
  public static boolean isExist(String value) {
    if (StringUtils.isEmpty(value)) {
      return Boolean.FALSE;
    }
    for (SmsCodeEnum smsCodeEnum : SmsCodeEnum.values()) {
      if (StringUtils.equals(smsCodeEnum.value, value)) {
        return Boolean.TRUE;
      }
    }
    return Boolean.FALSE;
  }
​
  public String getValue() {
    return value;
  }
}

发送短信类型

public interface SmsTypeConstant {

  String REGISTER = "register";

  String SMS_LOGIN = "sms_login";

  String RESET_PHONE = "reset_phone";

  String RESET_PASSWORD = "reset_password";
}

重新启动服务测试

  • 访问 localhost:7000/api/v1/sms/send?phone=15370315010&type=suibian
  • type 是随便输入的,不在指定的范围

  • 输入正确的 type

源码地址:江景/moose


原网址: 访问
创建于: 2021-07-12 14:10:35
目录: default
标签: 无

请先后发表评论
  • 最新评论
  • 总共0条评论