后端提供的服务,都是需要统一格式的,比如都需要返回错误码,错误信息,全局流水等等。那么在后端系统中,如果抛了异常不处理的话,这些格式就没法控制,今天讲讲springCloudGateway和springboot服务怎么在抛异常的时候,统一格式。
网关分为限流异常和业务异常
网关有限流的功能,那么在限流的时候,需要对返回做处理(这里用的sentinel做的限流)
首先我们有个配置类
@Configuration
public class SentinelException {
private final List viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public SentinelException(ObjectProvider> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
return new SentinelGatewayExceptionHandler(viewResolvers, serverCodecConfigurer);
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public GlobalFilter sentinelGatewayFilter() {
return new SentinelGatewayFilter();
}
}
然后新建一个SentinelGatewayExceptionHandler类继承SentinelGatewayBlockExceptionHandler,重写handle方法
@Slf4j
public class SentinelGatewayExceptionHandler extends SentinelGatewayBlockExceptionHandler {
private List viewResolvers;
private List> messageWriters;
public SentinelGatewayExceptionHandler(List viewResolvers,
ServerCodecConfigurer serverCodecConfigurer) {
super(viewResolvers, serverCodecConfigurer);
this.viewResolvers = viewResolvers;
this.messageWriters = serverCodecConfigurer.getWriters();
}
@Override
public Mono handle(ServerWebExchange serverWebExchange, Throwable throwable) {
if (serverWebExchange.getResponse().isCommitted()) {
return Mono.error(throwable);
}
if (!BlockException.isBlockException(throwable)) {
return Mono.error(throwable);
}
return handleBlockedRequest(serverWebExchange, throwable)
.flatMap(response -> writeResponse(response, serverWebExchange));
}
private Mono handleBlockedRequest(ServerWebExchange exchange,
Throwable throwable) {
return GatewayCallbackManager.getBlockHandler().handleRequest(exchange, throwable);
}
private final Supplier contextSupplier = () -> new ServerResponse.Context() {
@Override
public List> messageWriters() {
return SentinelGatewayExceptionHandler.this.messageWriters;
}
@Override
public List viewResolvers() {
return SentinelGatewayExceptionHandler.this.viewResolvers;
}
};
private Mono writeResponse(ServerResponse response, ServerWebExchange exchange) {
MDC.clear();
log.error("交易【{}】在【{}】时间被限制,请检查是否有大流量进入",
exchange.getRequest().getPath(),
DateUtil.format(new Date(), CakeConstants.FORMAT_TIME));
ServerHttpResponse resp = exchange.getResponse();
resp.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
Map map = new HashMap();
String json = JSON.toJSONString(map, CakeConstants.FORMAT_TIME);
DataBuffer buffer = resp.bufferFactory().wrap(json.getBytes(StandardCharsets.UTF_8));
return resp.writeWith(Mono.just(buffer));
}
Map map = new HashMap();这里的map就是定义的返回格式,随便自己定义,返回的json数据。
网关可能发生业务异常,那么我们也要做处理。新建一个GlobalErrorWebExceptionHandler类,实现ErrorWebExceptionHandler接口,重写handle方法,这里我就不贴所有代码了。
@Slf4j
@Order(-1)
@Configuration
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class GlobalErrorWebExceptionHandler implements ErrorWebExceptionHandler {
@SneakyThrows
@Override
public Mono handle(ServerWebExchange exchange, Throwable ex) {
ServerHttpResponse response = exchange.getResponse();
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
return response.writeWith(Mono.fromSupplier(() -> {
DataBufferFactory bufferFactory = response.bufferFactory();
try {
return bufferFactory.wrap(mapper.writeValueAsBytes("返回数据,自定义"));
} catch (Exception e) {
log.info("返回失败");
return bufferFactory.wrap(JSON.toJSONBytes("返回数据,自定义"));
}
}));
}
springboot分为流控异常,业务异常,还有404这些。
新建一个SentinelBootException类,实现BlockExceptionHandler,重写handle方法
@Slf4j
@Component
public class SentinelBootException implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
BlockException e) throws Exception {
MDC.clear();
CenterQueryResponse responseData = new CenterQueryResponse();
httpServletResponse.setStatus(200);
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter out = httpServletResponse.getWriter();
out.print(JSON.toJSONString(responseData, CakeConstants.FORMAT_TIME));
out.flush();
out.close();
}
}
responseData对象就是自定义返回的数据,大家按照自己项目的定义就行。
新建TransferNotFindController类,实现ErrorController接口,对/error路径进行处理。
@RestController
public class TransferNotFindController implements ErrorController {
private static final Logger log = LoggerFactory.getLogger(TransferNotFindController.class);
/**
* 默认错误
*/
private static final String path_default = "/error";
@RequestMapping(value = path_default, produces = {MediaType.APPLICATION_JSON_VALUE})
public CenterQueryResponse handleError(HttpServletRequest request) {
CenterQueryResponse responseData = new CenterQueryResponse();
log.info("交易不存在[{}]", JSON.toJSONString(responseData, CakeConstants.FORMAT_TIME));
return responseData;
}
}
CenterQueryResponse对象就是返回的数据。大家根据项目自行定义。
这部分比较麻烦,我是有对controller成做AOP切面处理的,我用了try catch做了一些异常处理,这部分就不写了,大家可以自己去处理,在catch里面对异常处理。还有一部分是全局异常处理,利用的是@ControllerAdvice注解和@ExceptionHandler注解。先定义一个异常类PgException继承RuntimeException。
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PgException extends RuntimeException {
private String code;
private String msg;
public PgException(String code, String msg) {
this.code = code;
this.msg = msg;
}
@Override
public String getMessage() {
return this.msg;
}
@Override
public String toString() {
return "Exception(code=" + this.getCode() + ", message=" + this.getMsg() + ")";
}
}
这里面有个地方注意一下,重写了getMessage,为什么要重写,因为在日志里你不重写的话,那么异常信息打印出来都是null,重写了,你打印的异常信息就是你的错误信息。
然后新建PgExceptionHandler类,这里的代码没完整贴出来。
@Slf4j
@ControllerAdvice
public class PgExceptionHandler {
@ExceptionHandler(PgException.class)
@ResponseBody
public CenterCommandResponse handlePgException(PgException ex) {
CenterCommandResponse centerCommandResponse = new CenterCommandResponse();
RequestContext.remove();
return centerCommandResponse;
}
@ExceptionHandler(Exception.class)
@ResponseBody
public CenterCommandResponse handleException(Exception ex) {
CenterCommandResponse centerCommandResponse = new CenterCommandResponse();
。。。
return centerCommandResponse;
}
}
CenterCommandResponse就是要返回的数据,大家可以根据项目自行定义。
网关和服务应用的异常处理就到这了,基本包含了当前所有的异常场景了,后续还有别的再加吧。再贴个我的异常返回图。
页面更新:2024-04-12
本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828
© CopyRight 2008-2024 All Rights Reserved. Powered By bs178.com 闽ICP备11008920号-3
闽公网安备35020302034844号