请求拦截、响应拦截、业务错误统一处理

在 Ant Design Pro 后台项目中,plugin‑request 是框架内置对 Axios 的高层封装,帮助我们统一处理请求头、业务错误、HTTP 状态码异常、消息提示,避免每个接口写重复的 try‑catch 和弹框逻辑。

很多同学直接复制默认配置,却不理解内部执行流程,遇到 token 失效、后端返回格式变更、401 未登录、业务自定义弹窗等问题无从下手。本文完整解析这份官方模板代码,讲清楚每一段代码的作用、执行顺序、常见踩坑点与扩展思路。完整代码就是项目 src/app.tsrequest 配置,也就是我们贴出的源码。

整体架构总览

RequestConfig 分为三大模块:

  1. errorConfig :业务错误处理器,包含 errorThrower(抛出业务异常)、errorHandler(捕获并处理异常),专门处理后端业务码(不是 HTTP 状态码)。
  2. requestInterceptors 请求拦截器:请求发出之前执行,统一追加 token、userId 等公共请求头。
  3. responseInterceptors 响应拦截器:接口拿到后端返回 response 之后执行,在交给业务页面之前做统一处理。

重要区分:

  • HTTP 错误:401/403/404/500,Axios 层面抛出错误。
  • 业务错误:HTTP 200,但是 success:false,属于业务逻辑失败,由 plugin‑requesterrorConfig 接管。

一、类型与枚举定义解析

复制代码
import type { RequestOptions } from '@@/plugin-request/request';
import type { RequestConfig } from '@umijs/max';
import { message, notification } from 'antd';

// 错误处理方案: 错误类型
enum ErrorShowType {
  SILENT = 0,          // 静默,不提示任何消息
  WARN_MESSAGE = 1,   // warning 警告提示
  ERROR_MESSAGE = 2,  // error 错误提示
  NOTIFICATION = 3,   // notification通知框
  REDIRECT = 9,       // 需要跳转(例如登录失效跳转登录页)
}

// 和后端约定返回的数据格式
interface ResponseStructure {
  success: boolean;
  data: any;
  errorCode?: number;
  errorMessage?: string;
  showType?: ErrorShowType;
}

解读

  1. ErrorShowType后端控制前端提示行为 。后端返回 showType,前端根据这个枚举决定用什么组件展示错误。

好处:后端接口可以精细化控制报错表现。比如有些错误不需要弹窗,静默记录日志;有些重要错误用通知栏。

  1. ResponseStructure:前后端契约。约定接口 HTTP 状态码永远 200,业务成功失败由 success布尔字段控制。
    • success:true:业务正常,取 data;
    • success:false:业务失败,读取 errorCode、errorMessage、showType

⚠️坑点:如果你们后端不返回这套字段,整套业务错误处理会失效,需要修改字段映射。

二、errorConfig 业务错误处理核心

复制代码
errorConfig: {
    //错误抛出
    errorThrower: (res) => {
      const { success, data, errorCode, errorMessage, showType } =
        res as unknown as ResponseStructure;
      if (!success) {
        const error: any = new Error(errorMessage);
        error.name = 'BizError';
        error.info = { errorCode, errorMessage, showType, data };
        throw error; // 抛出自制的业务错误
      }
    },
    //错误接收及处理
    errorHandler: (error: any, opts: any) => {
      if (opts?.skipErrorHandler) throw error;
      //我们 errorThrower抛出的业务错误
      if (error.name === 'BizError') {
        const errorInfo: ResponseStructure | undefined = error.info;
        if (errorInfo) {
          const { errorMessage, errorCode } = errorInfo;
          switch (errorInfo.showType) {
            case ErrorShowType.SILENT:
              // do nothing
              break;
            case ErrorShowType.WARN_MESSAGE:
              message.warning(errorMessage);
              break;
            case ErrorShowType.ERROR_MESSAGE:
              message.error(errorMessage);
              break;
            case ErrorShowType.NOTIFICATION:
              notification.open({
                description: errorMessage,
                message: errorCode,
              });
              break;
            case ErrorShowType.REDIRECT:
              // TODO: redirect
              break;
            default:
              message.error(errorMessage);
          }
        }
      } else if (error.response) {
        //Axios HTTP错误:服务器返回非2xx状态码,404 500 401
        message.error(`Response status:${error.response.status}`);
      } else if (error.request) {
        //请求发出去,但是完全没有收到后端响应,断网、跨域、后端服务挂掉
        message.error('None response! Please retry.');
      } else {
        // 创建请求阶段出错,配置错误
        message.error('Request error, please retry.');
      }
    },
  },

2.1 errorThrower 做了什么?

执行时机:接口 HTTP 200 拿到响应后

  1. 判断 success === false,业务失败;
  2. 创建 JS Error 对象,自定义 name='BizError',挂载 info 属性存放后端完整业务信息;
  3. 手动 throw error

关键点:虽然 HTTP 请求成功,但是业务失败,主动抛出异常,业务代码中 await request () 会进入 catch,不会走 then 。 如果不抛异常,即使 success=false,业务代码依然会走到 .then,每个接口都要手动判断 success,非常麻烦。

2.2 errorHandler 错误捕获分发

errorThrower抛出异常后,会进入 errorHandler,统一做消息提示。分三大错误分支:

  1. error.name === 'BizError':业务层错误 来自上面 errorThrower 抛出,读取后端返回的 showType,按枚举展示不同 antd 提示组件。 opts?.skipErrorHandler:调用接口时手动配置 {skipErrorHandler:true},可以跳过全局错误提示,业务自己处理错误。
  2. error.response:Axios HTTP 错误 请求发送成功,服务器返回状态码,但是不在 2xx 区间,如 401、403、500。
  3. error.request:无响应错误 请求已经发出,但是没有收到任何返回。典型场景:断网、后端服务宕机、跨域拦截。
  4. else 其他错误:请求构建阶段异常。

遗留 TODO:REDIRECT 枚举,官方模板没有实现,一般用来处理 token 过期,跳转登录页。

📌常见坑点

  1. 后端返回 success:false,但是页面没有弹报错:大概率后端字段名不是 success / errorMessage
  2. 想某个接口不使用全局提示:调用时传入 {skipErrorHandler:true},自己在 catch 处理。
  3. error.info 是自定义挂载属性,TS 不会识别,源码用 any绕过类型。

三、requestInterceptors 请求拦截器

复制代码
requestInterceptors: [
    (config: RequestOptions) => {
      // 从本地存储获取认证信息
      const token = localStorage.getItem('token');
      const userId = localStorage.getItem('userId');

      // 构建公共请求头
      const authHeaders: Record<string, string> = {};
      if (token) {
        authHeaders['token'] = token;
      }
      if (userId) {
        authHeaders['id'] = userId;
      }

      return {
        ...config,
        headers: {
          ...config.headers,
          ...authHeaders,
        },
      };
    },
  ],

执行时机:请求发送到服务器之前

  1. localStorage 读取 token、userId;
  2. 合并原有接口配置的 headers,追加自定义鉴权头;
  3. 返回新的 config 对象,plugin‑request 拿着这个配置发起网络请求。

⚠️坑点:

  1. 注意合并顺序:...config.headers写前面,业务接口的 header 优先级高于全局拦截器,业务接口可以覆盖全局 token。
  2. 不要直接修改原 config,必须返回新对象。
  3. 很多项目 token 放 Authorization 请求头,而这份代码使用自定义 token 请求头,需要和后端对齐。
  4. localStorage 在 SSR 环境会报错,Umi Max 默认客户端渲染后台项目无问题。

四、responseInterceptors 响应拦截器

复制代码
responseInterceptors: [
    (response) => {
      // 拦截响应数据,进行个性化处理
      const { data } = response as unknown as ResponseStructure;

      if (data?.success === false) {
        message.error('请求失败!');
      }
      return response;
    },
  ],

执行时机:拿到后端 response 之后,在 errorThrower 之前执行

重大注意点(很多人踩坑)

这里有一段冗余逻辑:if(data?.success === false) message.error('请求失败!') 因为后面 errorThrower 已经捕获 success=false 并且弹窗;如果后端返回业务错误,这里会先弹一次 "请求失败!",之后 errorHandler 又弹一次后端的 errorMessage,造成双重提示!

✅优化建议:可以直接删除这段判断,否则出现重复报错弹窗。

复制代码
responseInterceptors: [
  (response) => {
    return response;
  }
]

responseInterceptors 的用途:可以在这里做 response.data 的预处理,例如统一剥壳、打印日志。不要在这里做业务错误弹窗,交给 errorConfig 处理。

五、完整执行时序图(非常重要)

复制代码
1.业务代码调用 request('/api/xxx')
↓
2.执行 requestInterceptors 请求拦截器 → 追加token头
↓
3.Axios发起http请求
↓
4.后端返回response
↓
5.执行 responseInterceptors 响应拦截器
↓
6.进入 errorThrower:判断 success,false则抛出BizError
↓
7.抛出异常,进入 errorHandler,根据错误类型统一弹消息
↓
👉 如果没有异常:业务代码进入 then;有异常业务代码进入catch

重点:responseInterceptorserrorThrower 前面执行

六、高频业务扩展改造方案

改造 1:401 未登录,跳转到登录页(完善 REDIRECT 枚举)

errorHandler 中,HTTP 错误分支捕获 error.response.status === 401,清除 token,跳转登录。

复制代码
else if (error.response) {
  if(error.response.status ===401){
    localStorage.removeItem('token');
    localStorage.removeItem('userId');
    // umi 跳转
    history.push('/login');
    message.error('登录已过期,请重新登录');
  }else{
    message.error(`Response status:${error.response.status}`);
  }
}

改造 2:后端字段不匹配,比如后端叫code代替success

修改 errorThrower 内部判断逻辑,把 success 改为 code === 200

改造 3:某个接口跳过全局错误提示

复制代码
//业务调用示例
const res = await request('/api/demo',{skipErrorHandler:true}).catch(err=>{
  //自己单独处理错误
})

改造 4:移除 responseInterceptors 重复弹窗代码

上文提到,默认模板存在重复弹窗 bug,生产环境建议删除。

七、总结

  1. plugin‑request 把错误分为业务错误HTTP 网络错误 两套体系;errorThrower把业务失败主动抛出异常,让业务代码统一走 catch。
  2. 请求拦截器统一注入 token,注意 headers 合并顺序,业务接口 header 优先级更高。
  3. 响应拦截器执行时机早于业务错误处理,默认模板存在重复弹窗 bug,需要手动修复。
  4. ErrorShowType实现后端驱动前端报错 UI,是这套配置的设计亮点。
  5. skipErrorHandler 提供局部关闭全局提示的能力,适合需要自定义错误逻辑的接口。
相关推荐
码林鼠4 小时前
2026前端面试题(二)
前端
宿6746 小时前
tsconfig.node.json
前端·javascript·vue.js
小杨互联网6 小时前
Cursor 前端 Dist 自动化逆向框架:/fr 流水线 · 4 脚本 · Vite/Webpack
前端·webpack·自动化·前端逆向框架·ai前端逆向框架
瑞码空间6 小时前
Web应用的多端部署之道:浏览器 · Electron · Docker
前端·docker·electron·浏览器·web
徐奥雯XUAOWEN6 小时前
Codex官网前端可抄吗?从技术视角深度解析与借鉴指南
前端
文艺理科生6 小时前
3 年,8 种方案,1 次重构:LangChain 记忆方案如何从混乱走向清晰
前端·后端·架构
huabuyu7 小时前
SourceMap:从「看不懂线上报错」到「让 AI 定位真根因」
前端·javascript
এ慕ོ冬℘゜7 小时前
前端树形二级列表渲染 + 页面传参完整实战解析(附原生jQuery源码)
前端·javascript·jquery
weixin_469273817 小时前
.md文件是什么?.md如何打开?
前端·编辑器