UniApp 请求层终极封装:统一拦截、错误处理、多端兼容,结合 Claude Code 高效落地

UniApp 项目开发中,网络请求是绕不开的模块。很多开发者直接在页面写uni.request,业务代码到处散落接口逻辑:重复写 loading、重复处理 token、重复写错误提示,每个页面都要写超时、重试、401 登录失效逻辑。
项目迭代后出现大量问题:token 过期处理不一致、请求没有统一超时时间、没有防重复请求、小程序 / App/H5 端差异处理混乱,接口变更需要修改几十处页面代码。
如果纯手动改造,老项目全量修改请求工作量巨大。可以借助 Claude Code / Codex 完成请求层封装、老代码批量迁移、异常场景补全,用 AI 协助我们搭建一套规范、可维护的统一请求框架。
本文实现完整的请求封装:全局拦截器、token 自动携带、统一错误处理、loading 管控、请求防抖防重、超时重试、取消重复请求,同时兼容小程序 / H5/App 三端,附带多套可直接复制的 AI 提示词,实现老项目快速改造。

一、原生 uni.request 开发的常见痛点

  1. 每个页面重复书写 header、token、loading、toast 错误提示,代码大量冗余。
  2. token 失效 401,每个页面单独判断,没有统一跳转登录逻辑。
  3. 没有超时统一配置,部分接口超时直接静默失败,用户无感知。
  4. 短时间多次点击,触发重复请求(列表查询、提交表单),造成数据错乱。
  5. 没有请求取消,页面切换,上一页的请求还在执行,造成页面数据错乱、内存泄漏。
  6. 多端差异:H5 跨域、小程序合法域名校验、App 端证书校验,缺少统一处理。
  7. 老项目页面到处散落uni.request,接口地址硬编码,后端改域名,所有页面逐个修改。
  8. 缺少日志,线上接口报错很难排查。

核心设计思路:把所有网络能力收拢到统一请求层,业务页面只关心入参与返回数据,不需要处理 http 底层逻辑;所有拦截、鉴权、报错、重试全部交给请求层处理。老项目借助 AI 批量替换零散的 uni.request 调用。

二、整体架构分层

  1. 配置层:统一域名、超时时间、请求头、环境区分(开发 / 测试 / 生产)
  2. 核心请求实例 :封装request函数,基于 uni.request 做二次封装
  3. 请求拦截器:自动追加 token、处理请求头、参数格式化
  4. 响应拦截器:统一解析返回体、业务错误码处理、401 自动登出跳转、全局 toast 提示
  5. 高级能力层:loading 管理、防抖防重复请求、请求取消、超时重试
  6. API 接口管理层:所有接口集中管理,页面只调用函数,不写 url
  7. AI 辅助层:AI 批量扫描老项目,把页面零散 uni.request 替换成封装后的 api 函数,补全边界异常。

三、完整代码实现

3.1 请求配置文件 /common/http/config.js

bash 复制代码
// /common/http/config.js
const env = process.env.NODE_ENV

const baseConfig = {
  // 根据环境切换域名
  baseUrl: env === "development" ? "https://dev-api.xxx.com" : "https://api.xxx.com",
  timeout: 15000, // 全局超时15秒
  header: {
    "Content-Type": "application/json"
  }
}

// 业务错误码定义
export const CODE = {
  SUCCESS: 200,
  TOKEN_EXPIRE: 401,
  NO_PERMISSION: 403,
  SERVER_ERROR: 500
}

export default baseConfig

3.2 请求核心封装 /common/http/request.js

bash 复制代码
import baseConfig, { CODE } from "./config"
import uniStorage from "@/common/storage.js"

// 存储正在进行中的请求,用于取消重复请求
const pendingRequest = new Map()

// 生成请求唯一key
function generateReqKey(config) {
  const { url, method, data } = config
  return `${method}&${url}&${JSON.stringify(data || {})}`
}

// 取消重复请求
function removePending(config) {
  const reqKey = generateReqKey(config)
  if (pendingRequest.has(reqKey)) {
    const abort = pendingRequest.get(reqKey)
    abort()
    pendingRequest.delete(reqKey)
  }
}

/**
 * 统一请求封装
 * @param {Object} options {url,method,data,showLoading,showError}
 */
export default function request(options) {
  return new Promise((resolve, reject) => {
    const { url, method = "GET", data = {}, showLoading = true, showError = true } = options

    // 取消相同未完成请求
    removePending({ url, method, data })

    if (showLoading) {
      uni.showLoading({ title: "加载中...", mask: true })
    }

    const reqKey = generateReqKey({ url, method, data })

    const task = uni.request({
      url: baseConfig.baseUrl + url,
      method,
      data,
      timeout: baseConfig.timeout,
      header: {
        ...baseConfig.header,
        Authorization: `Bearer ${uniStorage.get("token") || ""}`
      },
      success: (res) => {
        removePending({ url, method, data })
        const { statusCode, data: responseBody } = res
        if (showLoading) uni.hideLoading()

        // http状态码判断
        if (statusCode !== 200) {
          if (showError) uni.showToast({ title: `网络异常${statusCode}`, icon: "none" })
          return reject(new Error(`http ${statusCode}`))
        }

        // 业务码判断
        if (responseBody.code === CODE.SUCCESS) {
          resolve(responseBody.data)
        } else if (responseBody.code === CODE.TOKEN_EXPIRE) {
          // token过期,清除本地存储,跳转登录
          uniStorage.remove("token")
          uni.showToast({ title: "登录已失效,请重新登录", icon: "none" })
          setTimeout(() => {
            uni.reLaunch({ url: "/pages/login/login" })
          }, 800)
          reject(new Error("token expire"))
        } else {
          // 业务报错
          if (showError) uni.showToast({ title: responseBody.msg || "请求失败", icon: "none" })
          reject(responseBody)
        }
      },
      fail: (err) => {
        removePending({ url, method, data })
        if (showLoading) uni.hideLoading()
        // 区分超时、断网
        let errMsg = "网络请求失败,请检查网络"
        if (err.errMsg.includes("timeout")) {
          errMsg = "请求超时,请稍后重试"
        }
        if (showError) uni.showToast({ title: errMsg, icon: "none" })
        reject(err)
      }
    })
    // 将abort任务存入map,页面卸载可调用task.abort()取消请求
    pendingRequest.set(reqKey, task.abort)
  })
}

3.3 api 集中管理 /common/http/api.js

所有接口统一在此维护,页面不写任何 url 字符串

bash 复制代码
import request from "./request"

// 用户模块
export const apiUserLogin = (params) => request({
  url: "/user/login",
  method: "POST",
  data: params
})

export const apiUserInfo = () => request({
  url: "/user/info",
  method: "GET"
})

// 列表模块
export const apiGetList = (params) => request({
  url: "/article/list",
  method: "GET",
  data: params
})

3.4 页面中使用示例

bash 复制代码
<script>
import { apiGetList } from "@/common/http/api.js"
export default {
  data() {
    return { list: [] }
  },
  onLoad() {
    this.fetchData()
  },
  methods: {
    async fetchData() {
      try {
        const res = await apiGetList({ page: 1, size: 10 })
        this.list = res.records
      } catch (e) {
        console.error("获取列表失败", e)
      }
    }
  }
}
</script>

四、进阶能力实现

4.1 页面销毁,取消正在执行请求

页面卸载的时候,把页面还没返回的请求取消,防止页面销毁后还执行 setData,避免报错和数据错乱。

bash 复制代码
// 页面onUnload生命周期
onUnload() {
  // 把当前页面所有pending请求全部取消,AI可以自动给所有页面补充该逻辑
}

4.2 多端兼容要点

  1. 微信小程序:需要配置小程序后台合法域名;开发阶段可以勾选「不校验合法域名」。
  2. H5 端:会遇到浏览器跨域问题,优先使用 uni 的 manifest 配置代理,不要把处理逻辑散落在业务页面。
  3. App 端:安卓 iOS 部分环境会出现 ssl 证书校验失败,可以配置忽略证书(仅测试环境)。
  4. 支付宝 / 百度小程序:错误 errMsg 字段存在差异,统一在 request 层做兼容映射。

五、AI 辅助开发:Claude Code / Codex 提示词

直接复制给 AI,用于新建请求层、老项目迁移、漏洞补全。

提示词 1:生成完整 UniApp 请求封装

bash 复制代码
你是uniapp资深开发,帮我搭建一套完整统一网络请求层。
约束:
1. 基于uni.request二次封装,区分开发、测试、生产环境域名。
2. 实现请求拦截自动携带token,响应拦截处理401token过期,自动跳转登录页面。
3. 支持全局loading开关、错误toast可配置关闭。
4. 实现重复请求取消,页面卸载可以终止未完成网络请求,避免页面销毁后回调执行。
5. 统一处理超时、断网、http错误码、业务错误码。
6. 所有接口抽离独立api.js,页面只导入调用函数,禁止页面写url。
7. 兼容微信小程序、H5、App‑iOS、App‑Android,输出完整可运行代码,附带页面调用示例。
8. 输出多端踩坑注意事项。

提示词 2:老项目批量迁移,替换散落的 uni.request

bash 复制代码
遍历当前uniapp项目全部vue页面组件:
1. 找到所有页面直接写的 uni.request,全部替换为我们已经封装好的api函数;
2. 提取url、method、data参数,把接口定义追加到api.js;
3. 页面代码改为async await方式,保留原有业务逻辑不变;
4. 处理异常捕获,不要删除原有业务逻辑;
5. 识别页面onUnload,补充取消页面未完成请求逻辑;
6. 输出修改清单,输出修改后的代码片段,规避小程序、App端坑,禁止生成会报错的代码。

提示词 3:给请求层新增能力:重试、日志打印

bash 复制代码
基于现有的request.js请求封装,新增两个能力:
1. 请求失败自动重试机制,配置最大重试次数,只对GET请求生效,POST表单提交不重试;
2. 增加请求日志,控制台打印请求url、入参、返回结果,生产环境关闭日志输出。
只修改核心request.js,不要改动api.js业务接口,输出完整修改后的代码。

六、团队开发强制规范(交给 AI 做代码扫描校验)

  1. 业务页面禁止直接写 uni.request,全部导入 api.js 内封装好的函数。
  2. url 地址全部写在 api 层,页面不能出现硬编码接口地址。
  3. token、header、loading、错误提示全部交给请求层,业务页面不重复处理。
  4. 表单提交类接口,关闭自动重试,防止重复提交。
  5. 页面onUnload必须取消当前页面未完成请求,规避内存泄漏。
  6. 特殊场景可以关闭 loading、关闭自动错误 toast,传入showLoading:false showError:false

七、高频问题排查

  1. token 过期没有跳转登录:检查响应拦截器 401 分支逻辑,确认后端返回的业务码和代码中 CODE 配置一致。
  2. 重复点击多次请求:确认已经开启重复请求取消逻辑。
  3. 切换页面,旧请求返回报错:页面 onUnload 没有取消请求。
  4. H5 跨域报错:优先使用 manifest.json H5 代理配置,不要在业务层写特殊兼容。
  5. App 打包后请求失败:检查域名是否 https,证书是否可信。

八、总结

UniApp 项目网络请求如果放任散落在各个页面,后期维护成本会指数级上涨。统一请求层把鉴权、报错、重试、请求取消全部收拢,业务页面只关注业务数据。

老项目改造最麻烦的就是存量uni.request迁移,人工改极易漏改。借助 Claude Code、Codex 可以批量扫描替换,补齐边界场景,大幅降低重复工作量。

项目体量越大,这套封装收益越高;小项目也可以直接复用,避免后期技术债务。

相关推荐
LuDvei2 小时前
怎么把数据上传到云端
运维·服务器
奇特認2 小时前
数据库MySQL 1.安装环境部署
linux·运维·服务器
小小龙学IT2 小时前
Day 26-27 项目实战:从零构建一个高并发聊天室(epoll + 线程池)
linux·服务器·c语言·开发语言·网络
光源【时光寸寸又逢君】3 小时前
回放时间定位问题
运维·服务器
雨声不在3 小时前
vm.mmap_rnd_bits 引发 ASAN CPU 100% 问题解析
linux·服务器·网络
Fnetlink13 小时前
Fnet 云网安 260807
服务器·网络·人工智能·安全·网络安全
AI 小老六4 小时前
Agent Runtime 如何用 Session、Memory、User Profile 和 Skill 实现外部学习
服务器·人工智能·学习·ai·架构·自动化
The Chosen One9854 小时前
【操作系统】操作系统引导、虚拟机、进程通信、信号
linux·运维·服务器·操作系统·虚拟机·信号
宵时待雨4 小时前
linux笔记归纳14:Socket编程TCP
linux·服务器·网络·笔记·tcp/ip