网关和中心统一异常

背景

后端提供的服务,都是需要统一格式的,比如都需要返回错误码,错误信息,全局流水等等。那么在后端系统中,如果抛了异常不处理的话,这些格式就没法控制,今天讲讲springCloudGatewayspringboot服务怎么在抛异常的时候,统一格式。

springCloudGateway网关

网关分为限流异常和业务异常

网关有限流的功能,那么在限流的时候,需要对返回做处理(这里用的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服务

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

标签:网关   异常   重写   注解   定义   格式   业务   方法   项目   数据   中心

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2008-2024 All Rights Reserved. Powered By bs178.com 闽ICP备11008920号-3
闽公网安备35020302034844号

Top