# 跨平臺Axios服務封裝,支持后端多種微服務對接
## 前言
大家封裝axios是否都是通過攔截服務呢?處理時,大多從以下問題出發吧,具體代碼可[點擊](http://www.hmoore.net/vvmily_king/vvmily/2326097)前往。
1、統一處理請求頭headers信息,如口令(token、Authorization)信息
2、重復接口防止提交,如采用loading、防抖處理等
3、請求入參統一,post、put、facth和get等方式
4、對響應數據狀態處理,如登錄攔截
5、對響應數據信息(錯誤)處理
6、其他:對文件請求處理...
* 實現方式
```
// 添加請求攔截器
axios.interceptors.request.use(function(config){
// ... 在發送請求前,統一做點什么,如請求頭配置、token信息等處理
return config;
},function(error){
// ... 對請求錯誤統一做點什么
return Promise.reject(error)
})
// 添加響應攔截器
axios.interceptors.response(function(response){
// ... 對響應數據做點什么
return response;
},function(error){
// ... 對錯誤數據統一做點什么
return Promise.reject(error)
})
export default axios;
```
## 進入本文主題,如何實現跨平臺接口封裝,支持后端多種微服務對對接
封裝Api服務,解決以下問題
1、支持跨平臺,可直接復用于多平臺,如移動端、PC端、小程序;
2、支持多服務,后端基本不會只有一個服務,一般大點公司,都是 網關+N服務方式(核心);
3、集中配置、解耦、可移植,簡化開發流程,開發體驗也是杠杠的。
## 結構目錄
services
apis ---接口配置列表
base ---單一工具
axiosAjax.js ---ajax封裝
jsonpAjax.js ---jsonp封裝
config.js ---基本配置
couplingAjax.js ---ajax數據統一處理
index.js ---導出出口

## 代碼使用
* apis/BaseServer/index.js文件
```
export?default?function?BaseServer(ajax,?config)?{
??return?{
????//?根據城市名稱模糊搜索
????queryList:?opt?=>
??????ajax({
????????url:?"/sale/list",
????????method:?"get",
????????...opt
??????})
??};
}
```
* 在業務組件中引入`import Api from "./services";`,得到的一定是正確的數據
```
// 前提 import Api from "./services";
const opt = {
??????data:?{
????????// ...
????????pageNum:?1,
????????pageSize:?15
??????},
??????loading:?true
??????//?success:?true
}
Api.BaseServer.queryList(opt).then(res?=>?{
??????console.log("拿到一定是正確的數據",?res);
????});
```
*Ajax請求配置以及請求后數據統一處理配置,如上`queryList(opt)`方法傳入opt對象配置如下:
注:下邊的配置項,適當的把部分配置放`apis/BaseServer/index.js文件`配置,部分存放到業務組件中。
| 配置項 | 說明 | ?是否必填 | 類型 | 默認值 |
| --- | --- | --- | --- | --- |
|?url?|?請求Api?|?是?|?string?|? ?|
|?loading?|?加載攔截,全屏?|?否?|?boolean?|?false?|
|?baseURL?|?基礎路徑?|?否?|?string?|??|
|?data?|?請求發送數據?|?否?|?object?|??|
|?params?|?地址欄拼接數據,僅限于'put',?'post',?'patch'?|?否?|?object?|??|
|?timeout?|?超時時間?|?否?|?number?|?30?*?1000?|
|?method?|?請求方法:get、post、put、patch、jsonp??|?否?|?string?|?get?|
|?headers?|?請求頭?|?否?|?object?|?{?"Content-Type":?"application/json"?}?|
|?success?|?請求成功時,是否提示?|?否?|?boolean?|?false?|
|?error?|?請求失敗時,是否提示?|?否?|?boolean?|?true?|
|?jsonp?|?是否使用jsonp請求接口?|?否?|?boolean?|?false?|
|?jsonpOpt?|?jsonp庫的options參數,配合jsonp使用?|?否?|?object?|?false?|
|?file?|?是否為文件模式?|?否?|?boolean?|?false?|
|?mock?|?是否為mock模式?|?否?|?boolean?|?false?|?
|?responseType?|?數據格式?|?否?|?string?|?json?|
|?isResponse?|?是否簡化數據?|?否?|?boolean?|?false?|?
|?reLogin?|?是否校驗登錄?|?否?|?boolean?|?true?|
## 代碼開發
* 基本配置
```
//?接口和頁面初始化配置中心
//?在前置配置之前,需要搞清楚后端微服務前綴路由是什么,然后再配置到該文件下面
const?gateway?=?"";
let?service?=?{
??domainName:?"",?//?主域名
??gateway,?//?流量網關前綴,后面的才是微服務后端代碼前綴
??BaseServer:?gateway?+?"/order"?//?公共服務
};
console.log("當前環境",?process.env.VUE_APP_NODE_ENV);
switch?(process.env.VUE_APP_NODE_ENV)?{
??//?當走淘寶mock的情況
??case?"rapmock":?{
????service?=?{
??????...service
????};
????break;
??}
??//?開發,?本地開發走vue代理
??case?"development":?{
????service?=?{
??????...service,
??????domainName:?""
????};
????break;
??}
??//?測試環境
??case?"staging":?{
????service?=?{
??????...service,
??????domainName:?""
????};
????break;
??}
??//?生產
??case?"production":?{
????service?=?{
??????...service,
??????domainName:?""
????};
????break;
??}
}
export?default?service;
```
* services/index.js
```
import?{?BaseApi?}?from?"./couplingAjax";
import?config?from?"./config";
import?BaseServer?from?"./apis/BaseServer";
const?baseServer?=?opt?=>?BaseApi(opt,?{?prefix:?config.BaseServer?});
export?default?{
??BaseServer:?BaseServer(baseServer,?config)
};
```
* couplingAjax.js文件
請求頭信息、登錄校驗、響應數據都可在本文件中自行配置
```
import { ajax } from "./base/axiosAjax";
import config from "./config";
import Tips from "./base/tips";
// import { loginOut } from '@/services/tool/LoginSet'
// import vuex from "@/store/index";
import qs from "qs";
import { debounce } from "@/utils/antiShakingAndThrottling";
// 口令封裝處理
const handlerToken = (header = {}) => {
const token = "token-test"; // vuex.getters.token;
if (!token) return header;
header["Authorization"] = token;
return header;
};
// 401退出登錄
const signOut = debounce(() => {
// loginOut()
Tips.error({ msg: "用戶登錄失效,將重新登錄", title: "錯誤" });
}, 1000);
// 處理opt傳入參數
const handlerData = (opt, apiBase = {}) => {
const { prefix } = apiBase;
opt.baseURL = opt.baseURL ? opt.baseURL : config.domainName;
opt.url = prefix + opt.url;
opt.method = opt.method ?? "get";
opt.data = opt.data ?? {};
opt.headers = opt.headers ?? { "Content-Type": "application/json" }; // 設置默認headers
opt.headers = handlerToken(opt.headers);
opt.file = opt.file ?? false; // 是否為文件模式,文件下載模式為后端直接下載文件,不做處理判斷
opt.mock = opt.mock ?? process.env.VUE_APP_NODE_ENV === "rapmock"; // 是否為mock模式
// opt.responseType = opt.responseType ?? (opt.mock ? 'json' : 'text') // 細節需要加括號,上環境情況下后端返回的數據是base64字符串
opt.responseType = opt.responseType ?? "json";
opt.isResponse = opt.isResponse ?? false; // 是否直接獲取response數據,避免因為簡化data數據獲取導致無法獲取完整數據情況
opt.reLogin = opt.reLogin ?? true; // 是否判斷401狀態跳轉到登錄頁面
return opt;
};
// 錯誤信息
const handlerErrorMessage = (error, message, tipsCode) => {
error &&
Tips.error({
msg: error !== true ? error : message ?? "系統異常,請稍后重試!",
tipsCode
});
};
// 成功信息
const handlerSuccessMessage = (success, message, tipsCode = "") => {
success &&
Tips.success({
msg: success !== true ? success : message ?? "成功",
tipsCode
});
};
// 業務接口
async function BaseApi(
opt = {},
{
prefix = "",
codeField = "code",
// dataField = "data",
codeNum = 200,
msgField = "msg",
tipsCode = "code"
}
) {
opt = handlerData(opt, { prefix }); // 參數預處理
const error = opt.error ?? true; // 默認,提示錯誤信息
const success = opt.success ?? false; // 默認:不提示成功信息
// 特殊格式請求處理
const posts = ["put", "post", "patch"];
if (
posts.includes(opt.method) &&
opt.headers["Content-Type"] === "application/x-www-form-urlencoded"
) {
opt.data = qs.stringify(opt.data);
}
try {
const result = await ajax(opt); // 請求接口
if (result.headers["authorization"]) {
// vuex.commit("user/SET_TOKEN", result.headers["authorization"]);
}
// 是否已登錄
if (opt.reLogin && result.status === 401) {
signOut();
return Promise.reject(result);
}
switch (opt.file) {
case false: {
// 解密后端返回信息
/*const response = opt.mock ? result.data
: result.data
? JSON.parse(base64Decode(result.data))
: result.data;*/
const response = result.data;
const code = response[codeField];
// const data = response[dataField];
const message = response[msgField];
const errCode = response[tipsCode];
// 提前統一處理接口提示信息
if (code === codeNum) {
handlerSuccessMessage(success, message); // success===false:不提示信息
return Promise.resolve(response);
} else {
handlerErrorMessage(error, message, errCode); // error===false:不提示信息
return Promise.reject(response);
}
}
// 走文件模式下
case true: {
return Promise.resolve(result);
}
}
} catch (e) {
const response = e.response;
if (opt.reLogin && response?.status === 401) signOut();
else {
const resData = response?.data ?? {};
const message = resData[msgField];
const errCode = resData[tipsCode];
handlerErrorMessage(error, message, errCode);
}
return Promise.reject(e);
}
}
export { BaseApi };
```
* axiosAjax.js文件
```
import axios from "axios";
import jsonpAjax from "./jsonpAjax";
import { Loading } from "element-ui";
// axios函數封裝
const ajax = async ({
url = "",
loading = false, // 加載攔截
baseURL = "",
data = {},
params = {}, // 地址欄拼接數據,僅限于'put', 'post', 'patch'
headers = { "Content-Type": "application/json;charset=UTF-8" }, // 頭部信息處理
method = "get",
timeout = 30 * 1000,
responseType = "json", // 表示服務器響應的數據類型,可以是 'arraybuffer', 'blob', 'document', 'json', 'text', 'stream'
jsonp = false, //是否使用jsonp請求接口
jsonpOpt = {} // jsonp庫的options參數
}) => {
// 接口全局加載提示
let loadingInstance = "";
if (loading !== false) {
loadingInstance = Loading.service({
lock: true,
text: loading !== true ? loading : "加載中……",
spinner: "el-icon-loading",
background: "rgba(0, 0, 0, 0.5)"
});
}
try {
const posts = ["put", "post", "patch"]; // 使用data作為發送數據主體
let response = null;
if (jsonp) {
response = await jsonpAjax({
url,
baseURL,
data,
timeout,
jsonpOpt
});
} else {
response = await axios({
url: url,
baseURL: baseURL,
headers: headers,
method: method,
params,
[posts.includes(method.toLowerCase()) ? "data" : "params"]: data,
timeout: timeout,
responseType
});
}
loadingInstance && loadingInstance.close();
return Promise.resolve(response);
} catch (e) {
loadingInstance && loadingInstance.close();
return Promise.reject(e);
}
};
export { ajax };
```
* jsonpAjax.js文件
```
// 原始文檔https://github.com/webmodules/jsonp
import jsonp from "jsonp";
function connectUrl(data) {
let url = "";
for (let k in data) {
let value = data[k] !== undefined ? data[k] : "";
url += `&${k}=${encodeURIComponent(value)}`; //使用的es6的模板字符串的用法 ${}
}
return url ? url.substring(1) : ""; //這里主要判斷data是否為空
}
const handlerOpt = ({
url = "",
baseURL = "", // 將會拼接到url前面
data = {}, // 傳入的參數,注意是對象
timeout = 60 * 1000,
jsonpOpt = {}
}) => {
url = baseURL + url; // 拼接基礎路徑
//拼接字符串(根路徑 + 參數),看根路徑是否包含 ‘?’
url = url + (url.indexOf("?") < 0 ? "?" : "&") + connectUrl(data);
jsonpOpt = {
// param 用于指定回調的查詢字符串參數的名稱(默認為callback)
// prefix 處理 jsonp 響應的全局回調函數的前綴(默認為__jp)
// name 處理 jsonp 響應的全局回調函數的名稱(默認為prefix+ 遞增計數器)
timeout, //發出超時錯誤后多長時間。0禁用(默認為60000)
...jsonpOpt
};
return {
url,
baseURL,
data,
timeout,
jsonpOpt
};
};
//封裝一個jsonp的函數
export default function jsonpAjax(opt = {}) {
let { url, jsonpOpt } = handlerOpt(opt);
return new Promise((resolve, reject) => {
jsonp(url, jsonpOpt, (err, data) => {
if (!err) {
resolve(data);
} else {
reject(err);
}
});
});
}
```
* tips.js
```
import { Message } from "element-ui";
// import { checkAnswer } from '@/utils/jumpToSolution'
// 統一message
const customMessage = async ({ msg = "", type = "success", tipsCode = "" }) => {
if (!msg) return null;
if (!tipsCode) {
Message({
message: msg,
type,
showClose: true
});
}
if (tipsCode) {
Message({
dangerouslyUseHTMLString: true,
message: `${msg} <a href="/nbs-pc/#/wiki-search?name=${tipsCode}" style="color: #409EFF;" target="_blank">更多幫助</a>`,
type,
showClose: true
});
// let showTip = await checkAnswer(tipsCode)
// if (showTip) {
// Message({
// dangerouslyUseHTMLString: true,
// message: `${msg} <a href="/nbs-pc/#/wiki-search?name=${tipsCode}" style="color: #409EFF;" target="_blank">更多幫助</a>`,
// type,
// showClose: true,
// })
// } else {
// Message({
// message: msg,
// type,
// showClose: true,
// })
// }
}
};
// 消息提示
const Tips = {
success(opt = {}) {
customMessage({ type: "success", ...opt });
},
error(opt = {}) {
customMessage({ type: "error", ...opt });
}
};
export default Tips;
```
* upload.js文件
```
// 適用于按鈕點擊上傳場景
import Tips from "./tips";
// 對uri地址進行數據拼接
const new_url = obj => {
if (obj) {
let fields = "";
for (let key in obj) {
fields = fields + `&${key}=${obj[key]}`;
}
return "?" + fields.substring(1, fields.length);
} else {
return "";
}
};
const paramsHandle = options => {
options.baseURL = ""; //個人處理,需要兼容之前的elementui等插件的上傳
options.fdata = options.fdata || ""; //文件上傳的url拼接地址
options.success = options.success || "文件上傳成功";
options.url = options.url + new_url(options.fdata);
options.loading = options.loading || "文件上傳中";
options.headers = options.headers || {};
options.headers["Content-Type"] = "multipart/form-data";
options.method = "post";
options.multiple = options.multiple || false; //是否多文件,默認false
//文件類型驗證,注意傳入數組,默認["image/jpeg", "image/png"]
options.type = options.type || ["image/jpeg", "image/png"];
options.size = options.size || 5; //文件大小限制,默認5M大小
options.max = options.max || 5; //最多上傳幾個文件
return options;
};
// 文件上傳
const upload = (ajaxCallback, params) => {
const options = paramsHandle(params);
//文件驗證處理
let input = document.createElement("input");
input.type = "file";
options.multiple ? (input.multiple = "multiple") : "";
input.click();
return new Promise((suc, err) => {
let type = options.type;
input.addEventListener("input", watchUpload, false);
function watchUpload(event) {
//console.log(event);
//移除監聽
let remove = () => {
input.removeEventListener("input", watchUpload, false);
input = null;
};
const file = event.path[0].files;
const len = file.length;
// 文件數量限制
if (len > options.max) {
remove();
Tips.error({ msg: "文件個數超過" + options.max });
err(file);
return false;
}
let formData = new FormData();
for (let i = 0; i < len; i++) {
// 文件大小限制
if (options.size !== 0 && file[i].size / 1024 / 1024 > options.size) {
remove();
Tips.error({ msg: file[i].name + "文件超過" + options.size + "M" });
err(file[i]);
return false;
}
// 文件類型限制
if (type.length > 0 && !type.includes(file[i].type)) {
remove();
Tips.error({ msg: file[i].name + "文件類型為" + file[i].type });
err(file);
return false;
}
formData.append("dhtUpload", file[i], file[i].name);
}
options.data = formData;
// 最終進行文件上傳
ajaxCallback(options)
.then(e => {
suc(e);
})
.catch(e => {
err(e);
});
// 沒有問題下,清空監聽。
remove();
}
});
};
export default upload;
```
## 其他
防抖節流antiShakingAndThrottling.js,請[點擊](http://www.hmoore.net/vvmily_king/vvmily/2331774)前往。
倉庫地址:https://github.com/wwmingly/axios-services
- 首頁
- 2021年
- 基礎知識
- 同源策略
- 跨域
- css
- less
- scss
- reset
- 超出文本顯示省略號
- 默認滾動條
- 清除浮動
- line-height與vertical-align
- box-sizing
- 動畫
- 布局
- JavaScript
- 設計模式
- 深淺拷貝
- 排序
- canvas
- 防抖節流
- 獲取屏幕/可視區域寬高
- 正則
- 重繪重排
- rem換算
- 手寫算法
- apply、call和bind原理與實現
- this的理解-普通函數、箭頭函數
- node
- nodejs
- express
- koa
- egg
- 基于nodeJS的全棧項目
- 小程序
- 常見問題
- ec-canvas之橫豎屏切換重繪
- 公眾號后臺基本配置
- 小程序發布協議更新
- 小程序引入iconfont字體
- Uni-app
- 環境搭建
- 項目搭建
- 數據庫
- MySQL數據庫安裝
- 數據庫圖形化界面常用命令行
- cmd命令行操作數據庫
- Redis安裝
- APP
- 控制縮放meta
- GIT
- 常用命令
- vsCode
- 常用插件
- Ajax
- axios-services
- 文章
- 如何讓代碼更加優雅
- 虛擬滾動
- 網站收藏
- 防抖節流之定時器清除問題
- 號稱破解全網會員的腳本
- 資料筆記
- 資料筆記2
- 公司面試題
- 服務器相關
- 前端自動化部署-jenkins
- nginx.conf配置
- https添加證書
- shell基本命令
- 微型ssh-deploy前端部署插件
- webpack
- 深入理解loader
- 深入理解plugin
- webpack注意事項
- vite和webpack區別
- React
- react+antd搭建
- Vue
- vue-cli
- vue.config.js
- 面板分割左右拖動
- vvmily-admin-template
- v-if與v-for那個優先級高?
- 下載excel
- 導入excel
- Echart-China-Map
- vue-xlsx(解析excel)
- 給elementUI的el-table添加骨架
- cdn引入配置
- Vue2.x之defineProperty應用
- 徹底弄懂diff算法的key作用
- 復制模板內容
- 表格操作按鈕太多
- element常用組件二次封裝
- Vue3.x
- Vue3快速上手(第一天)
- Vue3.x快速上手(第二天)
- Vue3.x快速上手(第三天)
- vue3+element-plus搭建項目
- vue3
- 腳手架
- vvmily-cli
- TS
- ts筆記
- common
- Date
- utils
- axios封裝
- 2022年
- HTML
- CSS基礎
- JavaScript 基礎
- 前端框架Vue
- 計算機網絡
- 瀏覽器相關
- 性能優化
- js手寫代碼
- 前端安全
- 前端算法
- 前端構建與編譯
- 操作系統
- Node.js
- 一些開放問題、智力題