前言
在uniapp開發中,網絡請求是每個應用都必不可少的功能模塊。一個優秀的網絡請求封裝不僅能提高開發效率,還能增強代碼的可維護性和可擴展性。本文將基于實際項目經驗,詳細介紹如何封裝一個高效、可維護的Uniapp網絡請求框架,并結合必應圖片API的調用示例,展示其完整使用流程。
一、網絡請求封裝的核心目標
- 統一管理:集中管理請求配置、攔截器等
- 復用性:減少重復代碼,提高開發效率
- 可擴展性:方便添加新功能如日志記錄、錯誤處理等
- 可維護性:清晰的代碼結構,便于團隊協作
二、封裝架構設計
1.項目結構規劃
項目根目錄下新建文件夾common,在其目錄下新建 api 文件夾以及 vmeitime-http 文件夾 ,如下
common/ ├── api/? ? ? ? ? ? ? ?# API接口管理 │? ?└── index.js? ? ? ?# 接口統一出口 └── vmeitime-http/? ? ?# HTTP核心封裝├── http.js? ? ? ? # 請求核心實現└── interface.js? ?# 基礎請求方法
然后再項目根目錄下的main.js下引用
//api
import api from '@/common/api/index.js'
Vue.prototype.$api = api
2.核心組件解析
(1) 基礎請求層 (interface.js)
這是整個框架的基石,實現了:
- 基礎請求方法(GET/POST/PUT/DELETE)
- 請求超時處理
- 請求ID生成(便于日志追蹤)
- 基礎配置管理
/*** 通用uni-app網絡請求* 基于 Promise 對象實現更簡單的 request 使用方式,支持請求和響應攔截*/
export default {config: {baseUrl: "",header: {'Content-Type': 'application/x-www-form-urlencoded'},data: {},method: "POST",dataType: "json",/* 如設為json,會對返回的數據做一次 JSON.parse */responseType: "text",timeout: 15000, // 全局超時時間 15 秒fail() {},complete() {}},interceptor: {request: null,response: null},request(options) {if (!options) {options = {}}// 1. 生成唯一請求ID并掛載到optionsconst requestId = generateRequestId();options.requestId = requestId; // 給請求配置添加IDoptions.baseUrl = options.baseUrl || this.config.baseUrloptions.dataType = options.dataType || this.config.dataTypeoptions.url = options.baseUrl + options.urloptions.data = options.data || {}options.method = options.method || this.config.methodoptions.timeout = options.timeout || this.config.timeout; // 使用配置的超時時間return new Promise((resolve, reject) => {let _config = nulllet timeoutHandle = null;// 超時處理if (options.timeout) {timeoutHandle = setTimeout(() => {reject({errMsg: "request:fail timeout",timeout: true,requestId: requestId // 超時錯誤也攜帶ID,方便定位});}, options.timeout);}options.complete = (response) => {// 無論成功失敗,都清除超時計時器clearTimeout(timeoutHandle);_config = Object.assign({}, this.config, options);response.config = _config; // 給響應掛載配置,供攔截器使用// 執行響應攔截器if (this.interceptor.response) {const interceptedResponse = this.interceptor.response(response);if (interceptedResponse !== undefined) {response = interceptedResponse;}}// 統一的響應日志記錄_reslog(response)if (response.statusCode === 200) { //成功resolve(response);} else {reject(response)}}// 失敗回調(網絡錯誤等)options.fail = (error) => {clearTimeout(timeoutHandle);error.requestId = requestId; // 網絡錯誤攜帶IDconsole.error(`【網絡請求失敗】ID: ${requestId}`, error);uni.showToast({title: error.timeout ? "請求超時" : "網絡連接失敗,請檢查網絡",icon: "none"});reject(error);};// 執行請求攔截器_config = Object.assign({}, this.config, options);if (this.interceptor.request) {const interceptedConfig = this.interceptor.request(_config);if (interceptedConfig !== undefined) {_config = interceptedConfig;}}// 統一的請求日志記錄_reqlog(_config)uni.request(_config);});},get(url, data, options) {if (!options) {options = {}}options.url = urloptions.data = dataoptions.method = 'GET'return this.request(options)},post(url, data, options) {if (!options) {options = {}}options.url = urloptions.data = dataoptions.method = 'POST'return this.request(options)},put(url, data, options) {if (!options) {options = {}}options.url = urloptions.data = dataoptions.method = 'PUT'return this.request(options)},delete(url, data, options) {if (!options) {options = {}}options.url = urloptions.data = dataoptions.method = 'DELETE'return this.request(options)}
}/*** 請求接口日志記錄*/function _reqlog(req) {//開發環境日志打印if (process.env.NODE_ENV === 'development') {const reqId = req.requestId || '未知ID'; // 讀取請求IDconst reqUrl = req.url || '未知URL'; // 讀取完整URLconsole.log(`【${reqId}】 地址:${reqUrl}`);if (req.data) {console.log("【" + (req.requestId || '未知ID') + "】 請求參數:", req.data);}}
}/*** 響應接口日志記錄*/
function _reslog(res) {if (!res) {console.error("【日志錯誤】響應對象為空");return;}const requestId = res.config?.requestId || '未知請求ID';const url = res.config?.url || '未知URL';const statusCode = res.statusCode || '未知狀態碼';console.log(`【${requestId}】 接口: ${url} | 業務狀態碼: ${statusCode}`);// 打印響應數據if (res.data) {console.log(`【${requestId}】 響應數據:`, res.data);}// 錯誤處理邏輯switch (statusCode) {case "401":console.error(`【${requestId}】 未授權錯誤`);break;case "404":console.error(`【${requestId}】 接口不存在`);break;case "500":console.error(`【${requestId}】 服務器錯誤`);break;default:}
}
/*** 生成唯一請求ID(時間戳+隨機數,避免重復)*/
function generateRequestId() {const timestamp = Date.now().toString(36); // 時間戳轉36進制(縮短長度)const randomStr = Math.random().toString(36).slice(2, 8); // 6位隨機字符串return `${timestamp}-${randomStr}`; // 格式:時間戳-隨機串(如:1h8z2x-9k7a3b)
}
(2) 請求增強層 (http.js)
在基礎層之上添加:
- 全局攔截器(請求/響應)
- 公共參數處理
- 錯誤統一處理
- 默認配置初始化
import uniRequest from './interface'uniRequest.config.baseUrl = "https://cn.bing.com"
uniRequest.config.header = {'Content-Type': 'application/x-www-form-urlencoded'
}// 公共參數(所有請求都會攜帶)
const commonParams = {};//設置請求前攔截器
uniRequest.interceptor.request = (config) => {//添加通用參數let token = uni.getStorageSync('token');if (token) {config.header["X-Token"] = token;}// 合并公共參數和業務參數const mergedData = {...commonParams, // 公共參數...config.data // 業務參數(會覆蓋公共參數)};config.data = mergedData;return config;
}
//設置請求結束后攔截器
uniRequest.interceptor.response = (response) => {return response;
}export default uniRequest
(3) API接口層 (api/index.js)
import http from '@/common/vmeitime-http/http.js'const $api = {//查看必應圖片byImageList(data) {return http.request({url: '/HPImageArchive.aspx?format=js&idx=0&n=1',method: 'GET',data,})}
}
export default $api
三、使用示例:調用Bing圖片API
<template><view><!-- 顯示加載狀態 --><view v-if="loading" class="loading">{{ tooltips.loading }}</view><!-- 顯示Bing圖片 --><image v-else :src="fullImageUrl" mode="widthFix" class="bing-image"></image></view>
</template><script>export default {data() {return {loading: true, // 加載狀態fullImageUrl: "", // 拼接后的完整圖片URL};},onLoad() {uni.showLoading({title: '請稍后',mask: true // 防止用戶重復操作});this.fetchBingImage().finally(() => {this.loading = false; // 確保 loading 被關閉uni.hideLoading(); // 無論成功失敗都關閉加載框});},methods: {fetchBingImage() {return this.$api.byImageList().then(res => {console.log("請求成功:", res);console.log('images 數組:', res.data.images);// 2. 提取 images 數組的第一項const imageData = res.data.images[0];console.log('images圖片:', imageData.url);// 3. 拼接完整URL(http://cn.bing.com + url)this.fullImageUrl = `https://cn.bing.com${imageData.url}`;console.log('images圖片地址:', this.fullImageUrl);}).catch(err => {// 此時的err只可能是:HTTP錯誤、解析異常、業務失敗(result≠0000)console.error("請求失敗:", err);uni.showToast({title: err.error || err.timeout ? '請求超時' : '請求失敗',icon: 'none',duration: 2000});return Promise.reject(err); // 繼續拋出,供上層處理});},},};
</script><style>.loading {font-size: 16px;color: #999;text-align: center;margin-top: 50px;}.bing-image {width: 100%;height: auto;}
</style>
四、高級功能實現
1. 日志系統
// 請求日志
function _reqlog(req) {if (process.env.NODE_ENV === 'development') {console.log(`【${req.requestId}】 地址:${req.url}`);if (req.data) {console.log("請求參數:", req.data);}}
}// 響應日志
function _reslog(res) {const requestId = res.config?.requestId || '未知請求ID';console.log(`【${requestId}】 接口: ${res.config?.url} | 狀態碼: ${res.statusCode}`);// 錯誤狀態碼處理switch (res.statusCode) {case 401: console.error(`未授權錯誤`); break;case 404: console.error(`接口不存在`); break;case 500: console.error(`服務器錯誤`); break;}
}
2. 請求ID生成算法
function generateRequestId() {const timestamp = Date.now().toString(36); // 時間戳轉36進制const randomStr = Math.random().toString(36).slice(2, 8); // 6位隨機字符串return `${timestamp}-${randomStr}`; // 格式:時間戳-隨機串
}