Spring boot参数校验及全局异常处理

问题:不想在方法中写一堆的参数校验逻辑,搞得参数校验的逻辑比业务逻辑还长,比如:

    @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调一下试试效果了。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值