接口异常状态统一处理方案:优先业务端处理,再按需统一处理。

本文介绍了后端接口响应处理方案,通过优先让业务端处理异常,再根据业务决定是否执行统一异常处理。文章讨论了历史解决方案的问题,并提出了一种基于Promise状态的优雅设计,实现了在axios响应拦截器中延迟执行统一处理,同时允许业务端在catch中决定是否继续处理。

原文地址:monine.github.io/#/article/2…

最近工作贼忙,这篇文章按说应该两个月之前就产出,可是每天的精力基本都用在工作上,一写文章就犯迷糊,断断续续的每次要重新屡逻辑,以后再也不这样了。这篇文章是我司后台项目中遇到的一个基础需求,自己设计了一个实现方案,感觉还不错。

需求

后端接口响应,根据与后端约定的状态码(非 http 状态码)判定接口是否异常,我司的约定是 status !== 0 则表示接口异常。一旦接口处于异常状态,先让业务端(调用者)处理异常,再由业务端决定是否执行接口异常统一处理(目前我司的统一处理内容就是弹出个 element-ui message 提示消息 ?)。

这个流程有一个难点,当接口响应后处于异常状态,先交由业务端处理,再由业务端决定是否执行统一处理?

API 层我司使用的是第三方库 axios,接口响应后会先走响应拦截器,再走业务端代码。 正常的接口异常统一处理流程,是在响应拦截器内判定,与后端约定的响应状态码是否为异常状态码。如果是,则先执行统一处理逻辑,再交由到业务端处理。那现在的需求是将接口异常处理的流程逆转,接口响应状态异常之后,先交由业务端执行异常处理,再由业务端决定是否执行接口异常状态统一处理。

如上所说,如果接口处于异常状态,需要判定是否要执行统一处理,分两种情况:

  1. 业务端没有处理异常,必然要执行统一处理。
  2. 业务端已经处理异常,并且主动声明是否继续执行统一处理。(主动声明该如何设计?

问题来了,接口异常统一处理的代码应该写在哪里?如何保证它在状态异常情况下,先交由业务端处理,再根据业务端的声明判定是否执行统一处理。

历史解决方案之 mixin

我司之前已经有过处理方案,不过是只针对 vue 框架下的处理,通过 mixin 将 methods 内所有方法进行覆写,补丁函数内对源函数执行完成之后获取的返回结果进行判定,如果返回结果为 Promise 类型,则继续进行相关异常处理操作。这样确实能够达到实现效果,但总觉得很不优雅:

  1. API 层的处理与框架深度绑定,这本身就不合理。
  2. methods 内函数全部被覆写,大量无用开销;如果进行函数名称约定,加入覆写筛选,这又增加约定成本。
  3. 只能应用于 vue 框架,无法再其它项目下直接使用,很局限。

当时我了解到上述的处理方案后第一反应是 API 层的任何操作都不应该与框架本身进行任何关联绑定,如同当年 vue 从全家桶中移除 vue-resource 一样。

我的解决方案

经过一些思考,大致确定了一个思路:利用 Promise 状态的稳定,以接口名称作为唯一标识,表示当前接口是否还需要执行统一处理。

我是这样设计的,接口调用时使用 url 作为唯一标识,以状态的形式保存在数组内 const unhandleAPI = []

接口返回后进入响应拦截器,在此对接口响应状态进行判断,如果属于异常状态,则使用 setTimeout 将接口异常统一处理函数设置为 macro task,作为一个异步任务推迟到下一轮 Event Loop 再执行,并返回 Promise.reject。然后会进入业务端接口调用代码的 catch 回调函数内,执行完业务异常处理后,如果没有返回值,则表示无需再执行统一处理。相反,返回非 undefined 值,则表示还需要执行统一处理。

执行接口异常统一处理之前,先判定 url 标识是否存在于 unhandleAPI 内,如存在,则执行统一处理。

以上是一个大致的设计思路,具体到实现还需要解决一些实际问题:

  1. 如何确定接口的唯一性?因为同一个接口可能毫秒内被多次调用。
  2. 接口的异常状态以什么样的形式进行保存?
  3. 如何在适当的时候移除接口异常状态?比如业务端处理了异常不想再执行统一处理。

具体实现

  • 接口异常处理状态存储

    使用一个数组对象 const unhandleAPI = [] 保存所有已经调用但暂未响应的接口唯一标识,接口异常统一处理函以此判定判定是否需要执行。另外对外暴露一些操作 unhandleAPI</

### OpenFeign 的异常处理机制及实现方法 #### 1. 默认异常处理行为 当使用 OpenFeign 进行远程调用时,默认情况下,如果目标服务不可用或者发生错误,OpenFeign 将抛出 `FeignException` 或其子类。这些异常通常是对 HTTP 响应状态码的封装,例如 404 表示资源未找到,500 表示服务器内部错误等[^1]。 #### 2. 自定义解码器(ErrorDecoder) 为了更灵活地控制异常处理逻辑,可以通过自定义 `ErrorDecoder` 来解析特定的 HTTP 错误响应并将其转换为业务异常。以下是实现方式: ```java @Component public class CustomErrorDecoder implements ErrorDecoder { @Override public Exception decode(String methodKey, Response response) { switch (response.status()) { case 404: return new ResourceNotFoundException("Resource not found"); case 500: return new ServerInternalException("Server internal error occurred"); default: return new FeignException(response.status() + ": " + response.reason()); } } } ``` 在此基础上,在配置类中注册该解码器即可生效: ```java @Configuration public class FeignConfig { @Bean public ErrorDecoder errorDecoder() { return new CustomErrorDecoder(); } } ``` 此部分功能允许开发者针对不同的 HTTP 状态码返回具体的业务异常对象[^3]。 #### 3. 使用 Fallback 和 FallbackFactory 实现熔断降级 对于分布式系统的高可用求,可以借助 Hystrix 提供的熔断能力来增强稳定性。通过实现 `FallbackFactory<T>` 接口,可以在服务调用失败时提供默认的行为或数据作为替代方案。 下面是一个简单的例子展示如何设置 fallback 工厂: ```java @FeignClient(name = "example-service", fallbackFactory = ExampleServiceFallbackFactory.class) public interface ExampleServiceClient { @GetMapping("/api/example") String getExampleData(); } @Component public class ExampleServiceFallbackFactory implements FallbackFactory<ExampleServiceClient> { @Override public ExampleServiceClient create(Throwable cause) { return new ExampleServiceClient() { @Override public String getExampleData() { // 返回备用数据或记录日志 System.out.println("Fallback triggered due to exception: " + cause.getMessage()); return "Default data from fallback"; } }; } } ``` 要注意的是,上述提到的服务异常被捕获后可能导致客户无法感知实际发生的错误情况[^2]。因此建议在设计微服务体系架构时充分考虑这一点,并确保双方能够有效传递必要的上下文信息以便于排查问题。 #### 4. 配合全局异常处理器注意事项 尽管引入了统一的全局异常捕获机制有助于简化开发流程,但在某些场景下它可能会干扰正常的熔断逻辑执行过程。比如当存在多个层面的拦截器时容易造成冲突现象——即原本期望触发 hystrix 超时保护却因为提前被捕获而失效。所以在项目实践中谨慎评估是否有必要同时启用两者以及它们之间的优先顺序关系等问题。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值