7.9 KiB
7.9 KiB
本小节中,我们将为 Gateway 网关服务添加全局异常处理,统一一波接口出参格式。
问题复现
首先,我们来看看目前网关服务存在的问题,编辑 SaTokenConfigure 配置类,对登出接口添加权限校验,校验登录的用户是否拥有 admin 角色:
代码如下:
SaRouter.match("/auth/user/logout", r -> StpUtil.checkRole("admin"));
重启网关服务,请求一波登出接口,由于目前已注册的用户,只有一个普通用户角色,于是,网关会提示你:无此角色:admin:
*发现问题了没有?*返参的格式是下面这样的:
{
"code": 500,
"msg": "无此角色:admin",
"data": null
}
和我们已经定好的接口出参格式,是不一致的:
{
"success": true,
"message": null,
"errorCode": null,
"data": null
}
接下来,我们就将为网关服务添加全局异常捕获器,统一一下 JSON 出参格式。
删除 SaToken 默认的异常处理
添加全局异常捕获之前,首先编辑 SaTokenConfigure 配置类,将 .setError() 方法删除掉,防止等会自定义的全局异常处理失效:
代码如下:
package com.quanxiaoha.xiaohashu.gateway.auth;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.reactor.filter.SaReactorFilter;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SaTokenConfigure {
// 注册 Sa-Token全局过滤器
@Bean
public SaReactorFilter getSaReactorFilter() {
return new SaReactorFilter()
// 拦截地址
.addInclude("/**") /* 拦截全部path */
// 鉴权方法:每次访问进入
.setAuth(obj -> {
// 登录校验
SaRouter.match("/**") // 拦截所有路由
.notMatch("/auth/user/login") // 排除登录接口
.notMatch("/auth/verification/code/send") // 排除验证码发送接口
.check(r -> StpUtil.checkLogin()) // 校验是否登录
;
// 权限认证 -- 不同模块, 校验不同权限
SaRouter.match("/auth/user/logout", r -> StpUtil.checkRole("admin"));
// 更多匹配 ... */
})
;
}
}
添加错误响应枚举类
接着,在网关服务中新建 /enums 枚举包,并创建 ResponseCodeEnum 异常枚举类,代码如下:
package com.quanxiaoha.xiaohashu.gateway.enums;
import com.quanxiaoha.framework.common.exception.BaseExceptionInterface;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum ResponseCodeEnum implements BaseExceptionInterface {
// ----------- 通用异常状态码 -----------
SYSTEM_ERROR("500", "系统繁忙,请稍后再试"),
UNAUTHORIZED("401", "权限不足"),
// ----------- 业务异常状态码 -----------
;
// 异常码
private final String errorCode;
// 错误信息
private final String errorMessage;
}
在异常响应枚举类中,先定义两个现阶段需要用到的错误码枚举值:
SYSTEM_ERROR:系统繁忙错误;UNAUTHORIZED: 权限不足,若 SaToken 权限校验失败,则统一返回此错误码;
网关全局异常捕获
接着,在网关服务中创建一个 /exception 异常包,用于统一放置异常相关的代码。然后,创建 GlobalExceptionHandler 全局异常处理器,代码如下:
package com.quanxiaoha.xiaohashu.gateway.exception;
import cn.dev33.satoken.exception.SaTokenException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.quanxiaoha.framework.common.response.Response;
import com.quanxiaoha.xiaohashu.gateway.enums.ResponseCodeEnum;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* @author: 犬小哈
* @date: 2024/4/5 19:18
* @version: v1.0.0
* @description: 全局异常处理
**/
@Component
@Slf4j
public class GlobalExceptionHandler implements ErrorWebExceptionHandler {
@Resource
private ObjectMapper objectMapper;
@Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
// 获取响应对象
ServerHttpResponse response = exchange.getResponse();
log.error("==> 全局异常捕获: ", ex);
// 响参
Response<?> result;
// 根据捕获的异常类型,设置不同的响应状态码和响应消息
if (ex instanceof SaTokenException) { // Sa-Token 异常
// 权限认证失败时,设置 401 状态码
response.setStatusCode(HttpStatus.UNAUTHORIZED);
// 构建响应结果
result = Response.fail(ResponseCodeEnum.UNAUTHORIZED.getErrorCode(), ResponseCodeEnum.UNAUTHORIZED.getErrorMessage());
} else { // 其他异常,则统一提示 “系统繁忙” 错误
result = Response.fail(ResponseCodeEnum.SYSTEM_ERROR);
}
// 设置响应头的内容类型为 application/json;charset=UTF-8,表示响应体为 JSON 格式
response.getHeaders().setContentType(MediaType.APPLICATION_JSON_UTF8);
// 设置 body 响应体
return response.writeWith(Mono.fromSupplier(() -> { // 使用 Mono.fromSupplier 创建响应体
DataBufferFactory bufferFactory = response.bufferFactory();
try {
// 使用 ObjectMapper 将 result 对象转换为 JSON 字节数组
return bufferFactory.wrap(objectMapper.writeValueAsBytes(result));
} catch (Exception e) {
// 如果转换过程中出现异常,则返回空字节数组
return bufferFactory.wrap(new byte[0]);
}
}));
}
}
解释一波异常处理器的代码:
- 和 Spring Boot 中使用
@ControllerAdvice注解,来定义全局异常捕获器不同。在网关中,你需要创建ErrorWebExceptionHandler的实现类,并将其注入到 Spring 容器中;- 在
handle()异常处理方法中,对方法的入参ex异常进行类型判断,从而设置不同的响应状态码和响应消息:
- 如果异常类型为
SaTokenException,则设置 401 状态码,并构建响应结果;- 其他异常,则统一提示 “系统繁忙” 错误;
- 设置响应头的内容类型为
application/json;charset=UTF-8,表示响应体为 JSON 格式;- 设置
body响应体,并返回响参;
自测一波
全局异常捕获器添加完毕后,重启网关服务,再次测试一波登出接口,看看出参效果:
如上图所示,响参的 JSON 格式已经被统一了,错误提示信息为权限不足,并且状态码为 401。