问题:不想在方法中写一堆的参数校验逻辑,搞得参数校验的逻辑比业务逻辑还长,比如:
@PostMapping("hello1")
public String hello1(@RequestBody HelloRequestDTO helloRequestDTO) {
String name = helloRequestDTO.getName();
String position = helloRequestDTO.getPosition();
if (null == name || 0 == name.trim().length()) {
return "name不能为空";
}
if (name.length() < 2 || name.length() > 80) {
return "name长度需要在2和80之间";
}
if (null == position || 0 == position.trim().length()) {
return "position不能为空";
}
if (position.length() < 2 || position.length() > 80) {
return "position长度需要在2和80之间";
}
return "Hello " + position + " " + name;
}
解决:
1. 给DTO的字段加上@NotBlank、@Length等标签
@Data
public class HelloRequestDTO {
@NotBlank
@Length(min = 2, max = 80)
private String name;
@NotBlank
@Length(min = 2, max = 80)
private String position;
}
2. 在方法的参数前加上@Valid标签
@PostMapping("hello2")
public String hello2(@Valid @RequestBody HelloRequestDTO helloRequestDTO) {
String name = helloRequestDTO.getName();
String position = helloRequestDTO.getPosition();
return "Hello " + position + " " + name;
}
这样,即可实现参数的自动校验,代码也清爽了好多。
但是,如果参数传得不正确,客户端得到的响应是:
{
"timestamp": "2021-11-26 23:14:22",
"status": 400,
"error": "Bad Request",
"errors": [
{
"codes": [
"Length.helloRequestDTO.name",
"Length.name",
"Length.java.lang.String",
"Length"
],
"arguments": [
{
"codes": [
"helloRequestDTO.name",
"name"
],
"arguments": null,
"defaultMessage": "name",
"code": "name"
},
80,
2
],
"defaultMessage": "长度需要在2和80之间",
"objectName": "helloRequestDTO",
"field": "name",
"rejectedValue": "",
"bindingFailure": false,
"code": "Length"
},
{
"codes": [
"NotBlank.helloRequestDTO.name",
"NotBlank.name",
"NotBlank.java.lang.String",
"NotBlank"
],
"arguments": [
{
"codes": [
"helloRequestDTO.name",
"name"
],
"arguments": null,
"defaultMessage": "name",
"code": "name"
}
],
"defaultMessage": "不能为空",
"objectName": "helloRequestDTO",
"field": "name",
"rejectedValue": "",
"bindingFailure": false,
"code": "NotBlank"
}
],
"message": "Validation failed for object='helloRequestDTO'. Error count: 2",
"path": "/demo/my/hello2"
}
这个字符串对前端来说有点复杂了。返回这一长串的字符,其实是因为Spring boot自动处理了参数校验异常。
我们希望由我们自己来处理参数校验异常,并且返回到前端的响应是如下格式的:
{
"code": "PE0001",
"message": "参数校验错误: position不能为空;name长度需要在2和80之间",
"data": null
}
3. 创建ResultCodeEnum枚举,统一管理所有返回前端的响应码
@Getter
@AllArgsConstructor
public enum ResultCodeEnum {
SUCCESS("000000", "成功"),
FX_PARAM_VALID_ERROR("FX0001", "参数校验错误: %s"),
SYSTEM_ERROR("ZZZZZZ", "系统繁忙,请稍后再试"),
;
private String code;
private String message;
}
4. 创建Result类作为对前端统一的响应对象
@Data
public class Result<T> {
private String code;
private String message;
private T data;
private Result() {}
public static <T> Result<T> success() {
Result<T> result = new Result<T>();
result.setCode(ResultCodeEnum.SUCCESS.getCode());
result.setMessage(ResultCodeEnum.SUCCESS.getMessage());
return result;
}
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>();
result.setCode(ResultCodeEnum.SUCCESS.getCode());
result.setMessage(ResultCodeEnum.SUCCESS.getMessage());
result.setData(data);
return result;
}
public static <T> Result<T> fail(ResultCodeEnum resultCodeEnum, Object... args) {
Result<T> result = (Result<T>) new Result<>();
result.setCode(resultCodeEnum.getCode());
String message = String.format(resultCodeEnum.getMessage(), args);
result.setMessage(message);
return result;
}
public static <T> Result<T> fail(T data, ResultCodeEnum resultCodeEnum, Object... args) {
Result<T> result = (Result<T>) new Result<>();
result.setCode(resultCodeEnum.getCode());
String message = String.format(resultCodeEnum.getMessage(), args);
result.setMessage(message);
result.setData(data);
return result;
}
}
5. 创建统一异常处理类
@Slf4j
@RestControllerAdvice(annotations = {RestController.class})
public class ControllerExceptionHandleAdvice {
/**
* 方法参数校验
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
log.error("MethodArgumentNotValidException:", e);
List<FieldError> allErrors = e.getBindingResult().getFieldErrors();
String message = allErrors.stream().map(s -> s.getField() + s.getDefaultMessage()).collect(Collectors.joining(";"));
return Result.fail(ResultCodeEnum.FX_PARAM_VALID_ERROR, message);
}
/***
* 未知异常托底
* @param e
* @return
*/
@ExceptionHandler(Exception.class)
public Result handleUnknownException(Exception e) {
log.error("Exception:", e);
return Result.fail(ResultCodeEnum.SYSTEM_ERROR);
}
}
6. 修改hello方法,使其也返回Result类
@PostMapping("hello3")
public Result hello3(@Valid @RequestBody HelloRequestDTO helloRequestDTO) {
String name = helloRequestDTO.getName();
String position = helloRequestDTO.getPosition();
String strHello = "Hello " + position + " " + name;
return Result.success(strHello);
}
大功告成,可以用postman调一下试试效果了。