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

在 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 提供局部关闭全局提示的能力,适合需要自定义错误逻辑的接口。
相关推荐
Eiceblue1 小时前
React 项目实战:用 JavaScript 合并多个本地 Excel 文件
前端·javascript·react.js·excel
水上冰石2 小时前
【MuJoCo从入门到精通】第2章 第一个 MuJoCo 仿真
前端·人工智能·算法
码事漫谈3 小时前
比尔·盖茨这次谈的不是模型,是账单
前端·后端
星栈3 小时前
AI 生成页面全是紫粉渐变?我用 ui-ux-pro-max-skill 重构了整个产品页
前端·weui
avi91114 小时前
【】js不同颜色(Vue 框架)今时今日2026年学编程入门(10月1日)
前端·vue.js·vue·vue框架·前端入门·vue入门·html上传
Fluxart.ai4 小时前
商品多角度图怎么做?Flux Art 从白底图到规格图、包装图的 10 步教程
开发语言·前端·javascript
前端繁华如梦4 小时前
React + Three.js 造了一个"乙烯基娃娃"3D 角色编辑器:配方驱动、程序化生成、还能跳舞
前端
繁华若梦7594 小时前
全项目 Skills 适配:把「会写代码的 Agent」变成「会按你们规范干活的同事」
前端
江华森4 小时前
云原生从0到1:Kubernetes 工作负载实战——Deployment/Service/滚动更新/弹性伸缩
前端·后端