使用JDK的数据校验和Spring的自定义注解校验前端传递参数的两种方法

第一种:JDK的数据校验注解

    @PostMapping("/test")
    public String test(QueryParam param, 
                       @RequestHeader(value = "App_key") String App_key,
                       @RequestHeader(value = "App_secret") String App_secret) throws IOException {
        param.setApp_key(App_key);
        param.setApp_secret(App_secret);
        return service.test(param);
    }
@Data
public class QueryParam {
    private String url;
    private String App_key;
    private String App_secret;
}

QueryParam 里面的 url 为必传参数,当前端没有传递的时候,我需要报错提示前端,需要怎么做呢?

  1. 在QueryParam 里面的 url 字段上加上JDK的 NotBlank 注解
@Data
public class QueryParam {
    @NotBlank(message = "url参数不能为空")
    private String url;
    private String App_key;
    private String App_secret;
}
  1. 在接口的 QueryParam 参数前面加上 @Valid 注解
    @PostMapping("/test")
    public String test(@Valid QueryParam param, 
                       @RequestHeader(value = "App_key") String App_key,
                       @RequestHeader(value = "App_secret") String App_secret) throws IOException {
        param.setApp_key(App_key);
        param.setApp_secret(App_secret);
        return service.test(param);
    }
  1. 在全局异常控制器中获取到 @NotBlank 注解上的描述信息,并返回给前端。
package com.tyler.web.exception;

/**
 * 全局异常处理器
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
    /**
     * 方法参数校验失败
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<Object> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        e.printStackTrace();
        String errorMessage = e.getBindingResult()
                .getFieldErrors()
                .stream()
                .map(FieldError::getDefaultMessage) // 获取注解中定义的 message
                .findFirst()
                .orElse("参数校验错误");

        return Result.fail(400, errorMessage, e.getMessage());
    }
}

这样写完之后就可以开启校验了。返回给前端的效果如下:

{
    "code": 400,
    "flag": false,
    "desc": "url参数不能为空",
    "cause": "Validation failed for argument [0] in public java.lang.String com.yxai.web.controller.transfer.TransferController.entityExtraction(com.yxai.common.params.QueryParam,byte[],java.lang.String,java.lang.String) throws java.io.IOException: [Field error in object 'queryParam' on field 'url': rejected value [null]; codes [NotBlank.queryParam.url,NotBlank.url,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [queryParam.url,url]; arguments []; default message [url]]; default message [url参数不能为空]] ",
    "traceId": "3806dae5-d8bc-48b0-8164-b947483c83e1",
    "data": null
}

第二种:使用Spring的自定义注解校验

现在我想对接口上的 App_key 和 App_secret 做校验,这两个 header 也是必传字段,我需要怎么做呢?

在 Spring Boot 里可以借助@Validated注解和自定义校验注解达成对请求头参数的校验。当用户未传递App_key和App_secret时,就会给出相应的报错提示。

下面是具体的实现步骤与代码示例:

1. 自定义校验注解

先创建自定义的校验注解,用来校验请求头参数是否为空。

import com.tyler.web.utils.validator.NotEmptyValidator;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.*;

// 自定义注解,用于校验字符串是否为空
@Target({METHOD,FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = NotEmptyValidator.class)
public @interface NotEmpty {
    String message() default "该参数为必传参数";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

2. 实现校验逻辑

接着实现校验注解对应的校验器。

import com.tyler.web.annotation.NotEmpty;
import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;

// 自定义注解的校验器
public class NotEmptyValidator implements ConstraintValidator<NotEmpty, String> {

    @Override
    public void initialize(NotEmpty constraintAnnotation) {
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        return value != null && !value.isEmpty();
    }
}
  1. 在接口方法上使用校验注解

最后在接口方法的请求头参数上运用自定义的校验注解。

    @PostMapping("/test")
    public String test(@Valid QueryParam param, 
                       @RequestHeader(value = "App_key") @NotEmpty(message = "App_key为必传参数") String App_key,
                       @RequestHeader(value = "App_secret") @NotEmpty(message = "App_secret为必传参数") String App_secret) throws IOException {
        param.setApp_key(App_key);
        param.setApp_secret(App_secret);
        return service.test(param);
    }

然后在前端试一下不传递 App_key 或 App_secret, 你会发现返回结果如下:

{
    "code": 200000,
    "flag": false,
    "desc": "系统异常",
    "cause": "Required request header 'App_secret' for method parameter type String is not present",
    "traceId": "0fb0ccb5-c2cb-4dfd-b4bf-030a0c18bc89",
    "data": null
}

似乎我们写的代码没有生效。

这个问题在于 Spring 框架在执行自定义校验逻辑之前,就因为请求头缺失而抛出了异常。@RequestHeader注解默认要求参数必须存在,若缺失就会直接抛出异常,不会触发后续的自定义校验。

我们可以把@RequestHeader的required属性设为false,允许请求头缺失,之后再通过自定义校验注解进行检查。以下是修改后的代码:

    @PostMapping("/test")
    public String test(@Valid QueryParam param, 
                       @RequestHeader(value = "App_key", required = false) @NotEmpty(message = "App_key为必传参数") String App_key,
                       @RequestHeader(value = "App_secret", required = false) @NotEmpty(message = "App_secret为必传参数") String App_secret) throws IOException {
        param.setApp_key(App_key);
        param.setApp_secret(App_secret);
        return service.test(param);
    }

修改点说明:

把@RequestHeader的required属性设置成false,这样即便请求头缺失,Spring 也不会立即抛出异常,而是会继续执行后续的自定义校验逻辑。当App_key或App_secret为空时,@NotEmpty注解会发挥作用,抛出相应的校验异常。

然后前端试一下不传递 App_secret, 会返回如下结果:

{
    "code": 200001,
    "flag": false,
    "desc": "未知异常",
    "cause": "receiptCropAndRecogMulti.App_secret: App_secret为必传参数",
    "traceId": "d6d83dfd-fd05-456e-a3de-ed5f180e3b07",
    "data": null
}

但是我觉得这个返回结果不好看,我想要 desc 返回我定义的描述信息,怎么做呢?

当 App_secret 或 App_key 缺失的时候,代码会抛出 ConstraintViolationException 异常,所以我们需要在全局异常控制器里面捕捉该异常,然后对其进行处理即可。

package com.tyler.web.exception;

/**
 * 全局异常处理器
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
    /**
     * 方法参数校验失败
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<Object> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
        e.printStackTrace();
        String errorMessage = e.getBindingResult()
                .getFieldErrors()
                .stream()
                .map(FieldError::getDefaultMessage) // 获取注解中定义的 message
                .findFirst()
                .orElse("参数校验错误");

        return Result.fail(400, errorMessage, e.getMessage());
    }

    /**
     * 自定义注解参数错误
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public Result<Object> handleBindException(ConstraintViolationException e) {
        e.printStackTrace();
        ConstraintViolation<?> violation = e.getConstraintViolations().iterator().next();
        return Result.fail(400, violation.getMessage(), e.getMessage());
    }
}

然后再请求一次,返回结果如下:

{
    "code": 400,
    "flag": false,
    "desc": "App_key为必传参数",
    "cause": "entityExtraction.App_key: App_key为必传参数",
    "traceId": "a4537203-3820-4442-9c35-e3a9f653c489",
    "data": null
}

这就是我想要的效果了,到此本教程结束。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值