現在,在項目中,廣泛採用Axios庫進行Http接口請求。它是基於promise的http庫,可運行在瀏覽器端和node.js中。此外還有攔截請求和響應、轉換JSON數據、客戶端防護XSRF等優秀的特性。axios源碼javascript
「連接-axios中文文檔」vue
考慮到各個項目實際使用時寫法混亂,不統一。對Axios進行一下通用化的封裝,目的是幫助簡化代碼和利於後期的更新維護,儘可能通用化。java
項目代碼自己依附於@vue/CLI3構建,固然經過剝離vue代碼,該Axios封裝也能夠直接應用到其餘項目中。node
封裝要達成的目標:webpack
npm install axios;
複製代碼
經過建立實例,操做實例的方式進行接口請求。ios
//request.js
import axios from 'axios'; // 引入axios
import Qs from 'qs'; // 引入qs模塊,用來序列化post類型的數據
import { autoMatch, checkStatus } from './utils'; // 處理函數
import { Toast } from 'mint-ui'; //提示框
// 建立axios實例
const instance = axios.create({
// baseURL: process.env.BASE_URL,
timeout: 30000, // 請求超時時間
// `transformRequest` 容許在向服務器發送前,修改請求數據
transformRequest: [function (data) {
// 對 data 進行任意轉換處理
return data;
}],
// `transformResponse` 在傳遞給 then/catch 前,容許修改響應數據
transformResponse: [function (data) {
// 對 data 進行任意轉換處理
return JSON.parse(data);
}]
})
複製代碼
給實例添加請求和響應攔截器,根據實際數據格式進行相應的處理。
好比:
請求時,應後端要求根據Content-type設置data傳參格式;
響應時,統一處理登陸超時的狀況和請求失敗的錯誤提示處理等。git
//request.js
// 實例添加請求攔截器
instance.interceptors.request.use(function (config) {
// 在發送請求以前作處理...
config.headers = Object.assign(config.method === 'get' ? {
'Accept': 'application/json',
'Content-Type': 'application/json; charset=UTF-8'
} : {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
}, config.headers);
if (config.method === 'post') {
const contentType = config.headers['Content-Type'];
// 根據Content-Type轉換data格式
if (contentType) {
if (contentType.includes('multipart')) { // 類型 'multipart/form-data;'
// config.data = data;
} else if (contentType.includes('json')) { // 類型 'application/json;'
// 服務器收到的raw body(原始數據) "{name:"nowThen",age:"18"}"(普通字符串)
config.data = JSON.stringify(config.data);
} else { // 類型 'application/x-www-form-urlencoded;'
// 服務器收到的raw body(原始數據) name=nowThen&age=18
config.data = Qs.stringify(config.data);
}
}
}
return Promise.resolve(config);
}, function (error) {
// 對請求錯誤作處理...
return Promise.reject(error);
});
// 實例添加響應攔截器
instance.interceptors.response.use(function (response) {
// 對響應數據作處理,如下根據實際數據結構改動!!...
// const { reason_code } = response.data || {};
// if (reason_code === '001') { // 請求超時,跳轉登陸頁
// const instance = Toast('請求超時,即將跳轉到登陸頁面...');
// setTimeout(() => {
// instance.close();
// window.location.href = '/login';
// }, 3000)
// }
return Promise.resolve(checkStatus(response));
}, function (error) {
// 對響應錯誤作處理...
return Promise.reject(checkStatus(error.response));
});
複製代碼
處理錯誤狀態:github
//utils.js
export function checkStatus (response) {
const status = response.status || -1000; // -1000 本身定義,鏈接錯誤的status
if ((status >= 200 && status < 300) || status === 304) {
// 若是http狀態碼正常,則直接返回數據
return response.data;
} else {
let errorInfo = '';
switch (status) {
case -1:
errorInfo = '遠程服務響應失敗,請稍後重試';
break;
case 400:
errorInfo = '400:錯誤請求';
break;
case 401:
errorInfo = '401:訪問令牌無效或已過時';
break;
case 403:
errorInfo = '403:拒絕訪問';
break;
case 404:
errorInfo = '404:資源不存在';
break;
case 405:
errorInfo = '405:請求方法未容許'
break;
case 408:
errorInfo = '408:請求超時'
break;
case 500:
errorInfo = '500:訪問服務失敗';
break;
case 501:
errorInfo = '501:未實現';
break;
case 502:
errorInfo = '502:無效網關';
break;
case 503:
errorInfo = '503:服務不可用'
break;
default:
errorInfo = `鏈接錯誤`
}
return {
status,
msg: errorInfo
}
}
}
複製代碼
實例封裝到async\await異步函數中。web
//request.js
const request = async function (opt) {
try {
const options = Object.assign({
method: 'get',
ifHandleError: true // 是否統一處理接口失敗(提示)
}, opt);
// 匹配接口前綴 開發環境則經過proxy配置轉發請求; 生產環境根據實際配置
options.baseURL = autoMatch(options.prefix);
const res = await instance(options);
// console.log(res);
if (!opt.ifHandleError) { // 自定義參數,是否容許全局提示錯誤信息
Toast(res.error || '請求處理失敗!')
}
return res;
} catch (err) {
if (!opt.ifHandleError) { // 自定義參數,是否容許全局提示錯誤信息
Toast(err.msg || '請求處理失敗!')
}
return err;
}
}
複製代碼
項目中統一處理報錯提示。但有時對於接口請求比較多的狀況,不少時候並不但願某些接口被全局報錯提示。這時這種接口能夠設置自定義的ifHandleError參數處理,不進行全局錯誤提示,而是到業務代碼中再作處理。
同理,其餘的一些特殊業務狀況也能夠經過自定義參數處理。shell
對於項目中請求多個後端的接口時,每一個接口在請求封裝時附帶prefix參數,根據prefix的值autoMatch函數
統一處理,自動匹配接口前綴。
autoMath方法:
// utils.js
const isDev = process.env.NODE_ENV === 'development'; // 開發 or 生產
// 匹配接口前綴
export function autoMatch (prefix) {
let baseUrl = '';
if (isDev) {
// 開發環境 經過proxy配置轉發請求;
baseUrl = `/${prefix || 'default'}`;
} else {
// 生產環境 根據實際配置 根據 prefix 匹配url;
// 配置來源 根據實際應用場景更改配置。(1.從全局讀取;2.線上配置中心讀取)
switch (prefix) {
case 'baidu':
baseUrl = window.LOCAL_CONFIG.baidu;
break;
case 'alipay':
baseUrl = window.LOCAL_CONFIG.alipay;
break;
default:
baseUrl = window.LOCAL_CONFIG.default;
}
}
return baseUrl;
}
複製代碼
webpack配置,好比:@vue/CLI3在vue.config.js
中配置:
devServer: {
proxy: {
'/baidu': {
target: 'http://10.59.81.31:8088',
changeOrigin: true,
pathRewrite: { '^/baidu': '' }
},
'/default': {
target: 'http://10.59.81.31:8088',
changeOrigin: true,
pathRewrite: {'^/default' : ''}
},
}
},
複製代碼
在一個新文件apiUrl.js
中統一管理項目所有的接口,方便維護。
//apiUrl.js
export const apiUrl = {
login: '/api/login',
loginOut: '/api/loginOut',
qryPageConfig: '/test/qryPageConfig',
setPageConfig: '/test/setPageConfig',
//...
}
複製代碼
在index.js
中:
import Vue from 'vue';
import request from './request';
import { apiUrl } from './apiUrl';
let services = {};
Object.entries(apiUrl).forEach((item) => {
services[item[0]] = function (options = {}) {
return request(Object.assign({
url: item[1]
}, options))
}
})
// 將services掛載到vue的原型上
// 業務中引用的方法:this.$services.接口名(小駝峯)
Object.defineProperty(Vue.prototype, '$services', {
value: services
});
export default services;
複製代碼
在上述代碼中,經過讀取配置的接口信息(名稱和請求路徑),生成所有接口。 並掛載到vue的原型上,這樣在業務中就能夠經過this.$services.接口名
直接調用對應的接口。
固然,這是本項目使用的方法。 因具體項目而異,也能夠不掛載到Vue原型上,使用services模塊;或直接調用request.js
封裝的請求函數。
如下爲使用事例:
// index.vue methods中
methods: {
// 獲取頁面配置信息接口 get Promise
$_qryPageConfig() {
this.$services.qryPageConfig({
method: 'get', //默認
params: {
page: 'login'
},
}).then((res) => {
this.pageConfig = res.data;
}).finally(() => {
...
this.close();
});
},
// 設置頁面配置接口 post async/await
$_order: async function () {
this.loading = true;
const res = await this.$services.setPageConfig({
prefix: 'baidu', //匹配url前綴
ifHandleError: true, //不對該接口進行全局錯誤提示。
method: 'post',
headers: {
'Content-Type': 'application/json; charset=UTF-8',
},
data: {
userId: this.idCard,
userName: this.realName,
color: 'red',
}
})
this.loading = false;
if (res.data) {
Total('success');
}
},
}
複製代碼
項目代碼自己依附於@vue/CLI3構建,是CLI3構建移動H5端應用的一部分。固然經過剝離vue代碼,該Axios封裝也能夠直接應用到其餘項目中。
項目源碼:
@vue/CLI3構建移動H5端應用:juejin.im/post/5d674d…;
Github連接:github.com/now1then/vu…;