# 跨平台Axios服务封装,支持后端多种微服务对接
## 前言
大家封装axios是否都是通过拦截服务呢?处理时,大多从以下问题出发吧,具体代码可[点击](https://www.kancloud.cn/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 ---导出出口
![](https://img.kancloud.cn/b2/70/b2706bac803d081e1c9b7b74b0cea645_435x323.png)
## 代码使用
* 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,请[点击](https://www.kancloud.cn/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
- 一些开放问题、智力题