UniApp 项目开发中,网络请求是绕不开的模块。很多开发者直接在页面写
uni.request,业务代码到处散落接口逻辑:重复写 loading、重复处理 token、重复写错误提示,每个页面都要写超时、重试、401 登录失效逻辑。
项目迭代后出现大量问题:token 过期处理不一致、请求没有统一超时时间、没有防重复请求、小程序 / App/H5 端差异处理混乱,接口变更需要修改几十处页面代码。
如果纯手动改造,老项目全量修改请求工作量巨大。可以借助 Claude Code / Codex 完成请求层封装、老代码批量迁移、异常场景补全,用 AI 协助我们搭建一套规范、可维护的统一请求框架。
本文实现完整的请求封装:全局拦截器、token 自动携带、统一错误处理、loading 管控、请求防抖防重、超时重试、取消重复请求,同时兼容小程序 / H5/App 三端,附带多套可直接复制的 AI 提示词,实现老项目快速改造。
一、原生 uni.request 开发的常见痛点
- 每个页面重复书写 header、token、loading、toast 错误提示,代码大量冗余。
- token 失效 401,每个页面单独判断,没有统一跳转登录逻辑。
- 没有超时统一配置,部分接口超时直接静默失败,用户无感知。
- 短时间多次点击,触发重复请求(列表查询、提交表单),造成数据错乱。
- 没有请求取消,页面切换,上一页的请求还在执行,造成页面数据错乱、内存泄漏。
- 多端差异:H5 跨域、小程序合法域名校验、App 端证书校验,缺少统一处理。
- 老项目页面到处散落
uni.request,接口地址硬编码,后端改域名,所有页面逐个修改。 - 缺少日志,线上接口报错很难排查。
核心设计思路:把所有网络能力收拢到统一请求层,业务页面只关心入参与返回数据,不需要处理 http 底层逻辑;所有拦截、鉴权、报错、重试全部交给请求层处理。老项目借助 AI 批量替换零散的 uni.request 调用。
二、整体架构分层
- 配置层:统一域名、超时时间、请求头、环境区分(开发 / 测试 / 生产)
- 核心请求实例 :封装
request函数,基于 uni.request 做二次封装 - 请求拦截器:自动追加 token、处理请求头、参数格式化
- 响应拦截器:统一解析返回体、业务错误码处理、401 自动登出跳转、全局 toast 提示
- 高级能力层:loading 管理、防抖防重复请求、请求取消、超时重试
- API 接口管理层:所有接口集中管理,页面只调用函数,不写 url
- 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 多端兼容要点
- 微信小程序:需要配置小程序后台合法域名;开发阶段可以勾选「不校验合法域名」。
- H5 端:会遇到浏览器跨域问题,优先使用 uni 的 manifest 配置代理,不要把处理逻辑散落在业务页面。
- App 端:安卓 iOS 部分环境会出现 ssl 证书校验失败,可以配置忽略证书(仅测试环境)。
- 支付宝 / 百度小程序:错误 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 做代码扫描校验)
- 业务页面禁止直接写 uni.request,全部导入 api.js 内封装好的函数。
- url 地址全部写在 api 层,页面不能出现硬编码接口地址。
- token、header、loading、错误提示全部交给请求层,业务页面不重复处理。
- 表单提交类接口,关闭自动重试,防止重复提交。
- 页面
onUnload必须取消当前页面未完成请求,规避内存泄漏。 - 特殊场景可以关闭 loading、关闭自动错误 toast,传入
showLoading:false showError:false。
七、高频问题排查
- token 过期没有跳转登录:检查响应拦截器 401 分支逻辑,确认后端返回的业务码和代码中 CODE 配置一致。
- 重复点击多次请求:确认已经开启重复请求取消逻辑。
- 切换页面,旧请求返回报错:页面 onUnload 没有取消请求。
- H5 跨域报错:优先使用 manifest.json H5 代理配置,不要在业务层写特殊兼容。
- App 打包后请求失败:检查域名是否 https,证书是否可信。
八、总结
UniApp 项目网络请求如果放任散落在各个页面,后期维护成本会指数级上涨。统一请求层把鉴权、报错、重试、请求取消全部收拢,业务页面只关注业务数据。
老项目改造最麻烦的就是存量uni.request迁移,人工改极易漏改。借助 Claude Code、Codex 可以批量扫描替换,补齐边界场景,大幅降低重复工作量。
项目体量越大,这套封装收益越高;小项目也可以直接复用,避免后期技术债务。