第一种: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 为必传参数,当前端没有传递的时候,我需要报错提示前端,需要怎么做呢?
- 在QueryParam 里面的 url 字段上加上JDK的 NotBlank 注解
@Data
public class QueryParam {
@NotBlank(message = "url参数不能为空")
private String url;
private String App_key;
private String App_secret;
}
- 在接口的 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);
}
- 在全局异常控制器中获取到 @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();
}
}
- 在接口方法上使用校验注解
最后在接口方法的请求头参数上运用自定义的校验注解。
@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
}
这就是我想要的效果了,到此本教程结束。