uni-app x企业级HTTP请求库封装实战 1. 项目概述从零构建企业级HTTP请求库在uni-app x开发中网络请求是每个项目都无法绕开的核心功能。虽然官方提供了uni.request()基础API但直接使用原始API会导致代码重复率高、错误处理分散、缺乏统一拦截机制等问题。我在最近参与的电商APP项目中就遇到了这样的困境——随着业务模块增加网络请求代码变得难以维护。以一个典型的用户登录场景为例原始实现需要重复编写baseURL、手动添加token、单独处理loading状态等。更棘手的是当后端接口规范变更时比如从Bearer token改为Cookie验证需要修改几十个分散的请求点。这促使我决定封装一个企业级的HTTP请求库本文将完整还原这个实战过程。2. 架构设计与技术选型2.1 核心需求分析通过分析多个实际项目我总结出企业级HTTP库必须具备的六大能力全局配置统一管理baseURL、超时时间等基础参数拦截机制实现请求/响应全链路拦截处理身份认证自动注入token等鉴权信息状态管理智能控制loading显示/隐藏错误处理统一捕获并转换异常信息类型支持完善的TypeScript类型定义2.2 技术方案对比方案优点缺点适用场景原生uni.request无需依赖直接可用功能简陋重复代码多简单demo项目axios功能强大生态完善需要适配uni环境复杂Web应用uview-plus http开箱即用UI统一定制能力有限使用uview的常规项目自主封装完全可控深度定制开发成本较高中大型企业项目基于项目长期维护考虑我们选择在uview-plus http模块基础上进行二次封装既保留其UI一致性又能实现深度定制。3. 实现细节与核心代码3.1 基础封装结构在项目根目录创建utils/request.ts必须使用.ts后缀以获得类型支持import { http } from uview-plus // 全局配置 http.setConfig((config) ({ baseURL: https://api.yourdomain.com, timeout: 8000, loadingText: 加载中..., loading: true, ...config })) // 请求拦截器 http.interceptors.request.use((config) { const token uni.getStorageSync(token) if (token) { config.header { ...config.header, Authorization: Bearer ${token} } } return config }) // 响应拦截器 http.interceptors.response.use( (response) response.data, (error) { const message error.message || 网络异常 uni.showToast({ title: message, icon: none }) return Promise.reject(error) } ) export default http3.2 全局挂载配置在main.uts中进行初始化import { createSSRApp } from vue import uviewPlus from uview-plus import http from /utils/request export function createApp() { const app createSSRApp(App) // 挂载到全局属性 app.config.globalProperties.$http http app.use(uviewPlus) return { app } }4. 高级功能实现4.1 智能Loading管理通过拦截器实现自动化的Loading控制// 请求计数器 let requestCount 0 http.interceptors.request.use((config) { if (config.loading ! false) { requestCount if (requestCount 1) { uni.showLoading({ title: config.loadingText || 加载中 }) } } return config }) http.interceptors.response.use( (response) { if (response.config.loading ! false) { requestCount-- if (requestCount 0) { uni.hideLoading() } } return response }, (error) { if (error.config?.loading ! false) { requestCount-- if (requestCount 0) { uni.hideLoading() } } return Promise.reject(error) } )4.2 多环境配置通过Vite环境变量实现多环境切换const envMap { dev: https://dev.api.com, test: https://test.api.com, prod: https://api.com } http.setConfig((config) ({ baseURL: envMap[import.meta.env.VITE_ENV], // 其他配置... }))5. 业务层最佳实践5.1 API服务模块化创建src/api目录组织业务接口// api/auth.ts import http from /utils/request export const login (data: { username: string; password: string }) http.post(/auth/login, data) export const getUserInfo () http.get(/auth/info) // api/product.ts export const getProductList (params: PaginationParams) http.get(/products, { params })5.2 类型安全增强定义统一的响应结构interface ApiResponseT { code: number data: T message: string } // 增强http实例类型 declare module uview-plus { interface HttpInstance { getT(url: string, params?: any): PromiseApiResponseT postT(url: string, data?: any): PromiseApiResponseT // 其他方法... } }6. 性能优化方案6.1 请求缓存策略实现GET请求缓存机制const cacheMap new Map() http.interceptors.request.use((config) { if (config.method GET config.cache) { const cacheKey JSON.stringify({ url: config.url, params: config.params }) if (cacheMap.has(cacheKey)) { return Promise.resolve(cacheMap.get(cacheKey)) } } return config }) http.interceptors.response.use((response) { if (response.config.method GET response.config.cache) { const cacheKey JSON.stringify({ url: response.config.url, params: response.config.params }) cacheMap.set(cacheKey, response.data) } return response })6.2 并发请求控制限制最大并发数const MAX_CONCURRENT 5 let currentConcurrent 0 const requestQueue: Function[] [] const processQueue () { while (currentConcurrent MAX_CONCURRENT requestQueue.length) { currentConcurrent const next requestQueue.shift() next?.().finally(() { currentConcurrent-- processQueue() }) } } http.interceptors.request.use((config) { return new Promise((resolve) { requestQueue.push(() resolve(config)) processQueue() }) })7. 安全防护措施7.1 CSRF防护自动注入CSRF Tokenhttp.interceptors.request.use((config) { const csrfToken getCSRFToken() // 从cookie或storage获取 if (csrfToken [POST, PUT, DELETE].includes(config.method.toUpperCase())) { config.header[X-CSRF-TOKEN] csrfToken } return config })7.2 请求重试机制对特定错误实现自动重试const RETRY_CODES [502, 503, 504] const MAX_RETRY 2 http.interceptors.response.use(null, (error) { const config error.config if (!config || !config.retry || !RETRY_CODES.includes(error.status)) { return Promise.reject(error) } config.__retryCount config.__retryCount || 0 if (config.__retryCount MAX_RETRY) { return Promise.reject(error) } config.__retryCount return new Promise(resolve { setTimeout(() resolve(http(config)), 1000 * config.__retryCount) }) })8. 实战问题排查8.1 典型问题记录iOS真机请求失败现象开发工具正常iOS真机网络错误原因未配置HTTPS或域名未备案解决使用合法备案域名并启用HTTPSContent-Type自动变更现象POST请求被转为application/json原因uview-plus默认行为解决显式设置headerContent-Type: application/x-www-form-urlencoded拦截器死循环现象登录接口无限递归原因在拦截器内触发新请求未做标记解决添加特殊header标识拦截器来源8.2 调试技巧使用Charles抓包分析// 在请求拦截器中添加调试标记 config.header[X-Debug-Id] Date.now()开启详细日志http.interceptors.request.use((config) { console.log([Request], config.method, config.url) return config })模拟慢网络测试loadinghttp.interceptors.response.use(async (response) { await new Promise(r setTimeout(r, 2000)) // 2秒延迟 return response })9. 扩展与演进9.1 文件上传优化实现分片上传和进度监控export const uploadFile (filePath: string, onProgress?: (percent: number) void) { return new Promise((resolve, reject) { const uploadTask uni.uploadFile({ url: http.defaults.baseURL /upload, filePath, name: file, success: resolve, fail: reject }) uploadTask.onProgressUpdate((e) { onProgress?.(e.progress) }) }) }9.2 WebSocket集成封装统一的Socket管理class SocketManager { private socket: WebSocket | null null connect(url: string) { this.socket uni.connectSocket({ url: http.defaults.baseURL.replace(http, ws) url }) this.socket.onMessage((res) { console.log(收到消息:, res.data) }) } send(data: any) { this.socket?.send({ data: JSON.stringify(data) }) } } export const socket new SocketManager()在项目迭代过程中这个请求库逐渐发展出了20个特性支撑了日均50万的API调用。最让我自豪的是在后端从REST迁移到GraphQL时我们只需要修改request.ts中的几十行代码就完成了平滑迁移业务层几乎零改动。这充分验证了良好封装的价值——它不仅能提升开发效率更能为系统演进提供坚实保障。