weblog/doc/4、IDEA 搭建 Spring Cloud Alibaba 微服务项目骨架/4.10 Spring Boot 添加全局异常捕获、接口参数校验.md
2025-02-17 10:05:44 +08:00

8.0 KiB
Raw Blame History

本小节中,我们继续完善项目的基础功能骨架 —— 为认证服务添加全局异常捕获、接口参数校验,定义统一的代码规范,以方便后续更高效率的进行业务开发。

Tip

: 由于这两块的内容在星球第一个项目 中,已经讲解过了,对于相关概念,细节不了解的小伙伴,可翻阅之前的内容,本节内容直接上手实操:

1. 添加全局异常捕获

编辑 xiaohashu-auth 认证服务,如下图所示,分别添加 :

  • /enums :枚举包, 统一放置相关枚举类;
  • /exception : 异常包,放置异常相关的功能;

考虑到这块的功能和业务本身关联性比较强,所以没有单独提取到 framework 基础框架层,直接放在了业务项目内部。

代码如下:

package com.quanxiaoha.xiaohashu.auth.enums;

import com.quanxiaoha.framework.common.exception.BaseExceptionInterface;
import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public enum ResponseCodeEnum implements BaseExceptionInterface {

    // ----------- 通用异常状态码 -----------
    SYSTEM_ERROR("AUTH-10000", "出错啦,后台小哥正在努力修复中..."),
    PARAM_NOT_VALID("AUTH-10001", "参数错误"),

    // ----------- 业务异常状态码 -----------
    ;

    // 异常码
    private final String errorCode;
    // 错误信息
    private final String errorMessage;

}

Tip

: 针对各个微服务,每个服务的异常状态码可以带上服务名(具有唯一性),比如 AUTH-10000 , 这样当某个接口报错时,能一样看出来异常是从哪个子服务抛出来的。

package com.quanxiaoha.xiaohashu.auth.exception;

import com.quanxiaoha.framework.common.exception.BizException;
import com.quanxiaoha.framework.common.response.Response;
import com.quanxiaoha.xiaohashu.auth.enums.ResponseCodeEnum;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.Optional;

@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    /**
     * 捕获自定义业务异常
     * @return
     */
    @ExceptionHandler({ BizException.class })
    @ResponseBody
    public Response<Object> handleBizException(HttpServletRequest request, BizException e) {
        log.warn("{} request fail, errorCode: {}, errorMessage: {}", request.getRequestURI(), e.getErrorCode(), e.getErrorMessage());
        return Response.fail(e);
    }

    /**
     * 捕获参数校验异常
     * @return
     */
    @ExceptionHandler({ MethodArgumentNotValidException.class })
    @ResponseBody
    public Response<Object> handleMethodArgumentNotValidException(HttpServletRequest request, MethodArgumentNotValidException e) {
        // 参数错误异常码
        String errorCode = ResponseCodeEnum.PARAM_NOT_VALID.getErrorCode();

        // 获取 BindingResult
        BindingResult bindingResult = e.getBindingResult();

        StringBuilder sb = new StringBuilder();

        // 获取校验不通过的字段,并组合错误信息,格式为: email 邮箱格式不正确, 当前值: '123124qq.com';
        Optional.ofNullable(bindingResult.getFieldErrors()).ifPresent(errors -> {
            errors.forEach(error ->
                    sb.append(error.getField())
                            .append(" ")
                            .append(error.getDefaultMessage())
                            .append(", 当前值: '")
                            .append(error.getRejectedValue())
                            .append("'; ")

            );
        });

        // 错误信息
        String errorMessage = sb.toString();

        log.warn("{} request error, errorCode: {}, errorMessage: {}", request.getRequestURI(), errorCode, errorMessage);

        return Response.fail(errorCode, errorMessage);
    }

    /**
     * 其他类型异常
     * @param request
     * @param e
     * @return
     */
    @ExceptionHandler({ Exception.class })
    @ResponseBody
    public Response<Object> handleOtherException(HttpServletRequest request, Exception e) {
        log.error("{} request error, ", request.getRequestURI(), e);
        return Response.fail(ResponseCodeEnum.SYSTEM_ERROR);
    }
}

1.1 自测一下

以上代码添加完毕后,编辑 /test2 接口,如下:

手动模拟一个运行时异常 —— 分母不能为 0 ,以测试全局异常捕获功能是否正常:

int i = 1 / 0

重启认证服务,通过 Apipost 调试一波 /test2 接口:

如上图所示,成功捕获到了异常,并返回了通用的异常状态码,以及友好的错误提示信息。

2. 添加接口参数校验

imgimg

2.1 添加依赖

由于参数校验的依赖,是比较通用的,可以添加到 xiaoha-common 公共模块中:

编辑其 pom.xml , 添加如下依赖:

        <!-- 入参校验 -->
        <dependency>
            <groupId>jakarta.validation</groupId>
            <artifactId>jakarta.validation-api</artifactId>
        </dependency>
        <dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId>
        </dependency>

2.2 自测一下

依赖添加完毕后,重新刷一下 maven 依赖,然后,我们来自测一下参数校验功能是否好使。编辑 xiaohashu-auth 认证服务中的 User 用户类,为昵称字段添加@NotBlank 校验注解,代码如下:

package com.quanxiaoha.xiaohashu.auth.controller;

import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class User {
    /**
     * 昵称
     */
    @NotBlank(message = "昵称不能为空")
    private String nickName;

    /**
     * 创建时间
     */
    private LocalDateTime createTime;
}

接着,为 /test2 接口的入参实体类,添加 @Validated 校验注解,代码如下:

package com.quanxiaoha.xiaohashu.auth.controller;

import com.quanxiaoha.framework.biz.operationlog.aspect.ApiOperationLog;
import com.quanxiaoha.framework.common.response.Response;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;

@RestController
public class TestController {

	// 省略...

    @PostMapping("/test2")
    @ApiOperationLog(description = "测试接口2")
    public Response<User> test2(@RequestBody @Validated User user) {
        int i = 1 / 0;
        return Response.success(user);
    }
}

重启项目,再次自测一波 /test2 接口,将 nickName 昵称字段值设置为空字符串:

可以看到,注解形式的参数校验功能也是没有问题的~

本小节源码下载

https://t.zsxq.com/PM4jv