怎么处理 API 异常

在后端 MVC 思想中,一个很重要的概念是分层机制,模型层关注数据的处理,异常层来处理全局异常。这种服务端的思想逐渐在这个大前端不断被提及的今天被用在前端代码中。之前一直从事前端的同学一开始可能不太适应,但是慢慢地你会喜欢上它。

首先,我们要明确这里说的异常仅指 API 异常,是前后端已经约定过的,比如密码错误、无登录权限、token 令牌失效等,如果是业务代码异常或其他异常,这就需要你自己处理了,我们不可能将所有的异常处理掉。所有的 API 请求都是 HTTP 请求,所以我们可以抽象出所有 API 请求共同的一个基类,如果你开发过小程序,同样的可以把 wx.request 封装起来,那么我们这里的 http 请求用的是 axios异常机制 - 图1 ,通过查看官方文档,我们知道了可以通过请求拦截器来实现。

这种思想叫做面向切面编程(AOP),这种做法对原有代码毫无入侵性,将与主业务无关的事情放到别的地方去做。

代码实现

代码位置: src/lin/utils/http.js

我们来看下这个文件,首先创建一个拥有通用配置的 axios 实例,官网提供的请求配置有很多,可以根据你的具体需求进行配置。

  1. // 创建一个拥有通用配置的axios实例
  2. const http = axios.create({
  3. baseURL: Config.baseUrl, // api 的 base_url
  4. transformResponse: [data => JSON.parse(data)], // 对 data 进行任意转换处理
  5. timeout: 5000, // 请求超时
  6. // 定义可获得的http响应状态码
  7. // return true、设置为null或者undefined,promise将resolved,否则将rejected
  8. validateStatus(status) {
  9. return status >= 200 && status < 500
  10. },
  11. })

然后添加一个请求拦截器,将 Authorization 混入到 HTTP 的 header 中,这块是使用的 jwt 的机制。

接下来介绍一下 token在前端的获得及刷新流程

  • login.vue 中登陆获取 tokens,这里面会同时获取 access_token和refresh_token,将其存储到 cookie 中(每次登录颁发的都是新令牌),不进入 vuex 全局管理,refresh_token 的过期时间是 30 天,access_token 的过期时间是 2 小时。我们每个 API 请求都要携带 access_token 才能请求成功,access_token 包含了管理员的唯一身份标识。

  • 如果管理员在操作过程中,access_token 过期了,那么当次请求服务端会返回 10050 错误码,检测到这个错误码后,通过 refresh_token 来换取 access_token,并再次保存。

  1. // 添加一个请求拦截器
  2. http.interceptors.request.use(
  3. (requestConfig) => {
  4. if (requestConfig.url === 'cms/user/refresh') {
  5. const refreshToken = getToken('refresh_token')
  6. if (refreshToken) {
  7. // eslint-disable-next-line no-param-reassign
  8. requestConfig.headers.Authorization = refreshToken
  9. return requestConfig
  10. }
  11. } else {
  12. // 有access_token
  13. const accessToken = getToken('access_token')
  14. if (accessToken) {
  15. // eslint-disable-next-line no-param-reassign
  16. requestConfig.headers.Authorization = accessToken
  17. return requestConfig
  18. }
  19. }
  20. return requestConfig
  21. },
  22. error => Promise.reject(error),
  23. )

继续往下看,在 HTTP 的 response 响应中,如果接口出现异常,则判断其 http 状态码:

  1. import { handleException, handleError } from './exception'
  2. // 返回结果处理
  3. http.interceptors.response.use(
  4. async (res) => {
  5. if (res.status.toString().charAt(0) !== '2') {
  6. const result = await handleException(res)
  7. return result
  8. }
  9. return res.data
  10. },
  11. error => handleError(error),
  12. )

通过拿到的 HTTP 状态码和服务端 restful 接口返回的自定义异常数据,接下来在 exception.js 这个文件中,根据不同的 error_code 来进行不同的处理逻辑,将异常拦截在全局,业务逻辑只专注处理业务。

位置: src/lin/utils/exception.js 具体实现了 handleException 和 handleError。

error_code 要前后端商量确认,前端在 src/config/error-code.js 中定义:

error_code说明
0成功
1007未知错误
999服务器未知错误
10000认证失败
10020资源不存在
10030参数错误
10040令牌失效
10050令牌过期
10060字段重复

这里我们进行了全局异常拦截,但是如果你不想在全局处理异常,当然也是可以的,根据开发者自己的需求,在 api 获取过程中的任意阶段都可以进行异常捕获处理。

小结

本小节介绍了前端异常层的处理,这里主要是希望大家了解分层的思想,那么同样的,数据验证层、模型层、业务层等这些 MVC 分层的思想也可以逐渐应用到前端代码中。