背景
最近在和友商合作开发一个企微的应用,可能需要用到两种接口
- 内部接口:需要token 和 authorization 以及其他请求头字段等。
- 外部接口:直接调用即可
思路回顾
主要是利用了ES6的 class 关键字定义一个请求类,在类上定义一个静态方法(static),静态方法实例无法调用,类可以直接调用;类上还定义了各种各样的请求(post、postJson、get、put、putJson、delete)等,针对每一个请求单独设置其配置,最终通过axios.create([config]).then((result)=>{}),取到数据等。
文件目录层级
base.js
Request.js
LSRequest.js
实现
base.js
//抽离出来方便修改
export OPEN_URL = "xxx.xxx.xxx";//外部base
export INNER_URL = "xxx.xxx.xxx";//内部base
Request.js(外部接口调用)
import axios from "axios";
// Axios 插件 重试失败的请求
import axiosRetry from "axios-retry";
// qs 是一个增加了一些安全性的查询字符串解析和序列化字符串的库。
import qs from "qs";
// 提示窗口应用vant的Toast组件
import {Toast} from 'vant';
axiosRetry(axios, {
retries: 3,
retryCondition: () => true,
retryDelay: () => 1000
});
let toastCount = 0;
//请求拦截器
axios.interceptors.request.use(
config => {
if (!config._asyn) {
Toast.loading({
message: '加载中...',
forbidClick: true,
duration: 0,
});
toastCount++;
}
return config;
},
err => {
console.log(err);
return Promise.reject(err);
}
);
// 响应拦截器
axios.interceptors.response.use(
response => {
if (!response.config._asyn) {
toastCount--;
if (toastCount <= 0) {
Toast.clear();
}
}
return response;
},
err => {
toastCount--;
if (toastCount <= 0) {
Toast.clear();
}
return Promise.reject(err);
}
);
//通过class关键字定义一个Request类
export default class Request {
//构造方法,而this关键字则代表实例对象。
constructor(url) {
this.requestConfig = {url: url, headers: {}};
}
//类相当于实例的原型,所有在类中定义的方法,都会被实例继承。
如果在一个方法前,加上 `static`关键字,就表示该方法不会被实例继承,
而是直接通过类来调用,这就称为“静态方法”。
static create(url) {
return new Request(url);
}
asyn() {
this.requestConfig._asyn = true;
return this;
}
header(key, value) {
this.requestConfig.headers[key] = value;
return this;
}
post(param) {
this.requestConfig.method = 'post';
this.requestConfig.data = qs.stringify(param, {
skipNulls: true,//skipNulls选项可以忽略Null值的解析:
encodeValuesOnly: true,//通过设置 encodeValuesOnly 为 true,可以禁用对 key 进行URI 编码:
indices: false,//indices 为 false 不显示索引:
});
this.header("Content-Type", "application/x-www-form-urlencoded");
return this.execute();
}
postJson(param) {
this.requestConfig.method = 'post';
this.requestConfig.data = param;
this.header("Content-Type", "application/json");
return this.execute();
}
get(param) {
this.requestConfig.method = 'get';
this.requestConfig.params = param;
this.requestConfig.paramsSerializer = params =>
qs.stringify(params, {
skipNulls: true,
encodeValuesOnly: true,
indices: false,
});
this.header("Content-Type", "application/x-www-form-urlencoded");
return this.execute();
}
/**
* put调用 application/x-www-form-urlencoded
* @param param 参数
* @returns {Promise<unknown>}
*/
put(param) {
this.requestConfig.method = 'put';
this.requestConfig.data = qs.stringify(param, {
skipNulls: true,
encodeValuesOnly: true,
indices: false
});
this.header("Content-Type", "application/x-www-form-urlencoded");
return this.execute();
}
/**
* put调用 application/json
* @param param 参数
* @returns {Promise<unknown>}
*/
putJson(param) {
this.requestConfig.method = 'put';
this.requestConfig.data = param;
this.header("Content-Type", "application/json");
return this.execute();
}
/**
* delete调用 application/x-www-form-urlencoded
* @param param 参数
* @returns {Promise<unknown>}
*/
delete(param) {
this.requestConfig.method = 'delete';
this.requestConfig.params = param;
this.requestConfig.paramsSerializer = params =>
qs.stringify(params, {
skipNulls: true,
encodeValuesOnly: true,
indices: false,
});
this.header("Content-Type", "application/x-www-form-urlencoded");
return this.execute();
}
execute() {
//使用自定义配置新建一个 axios 实例
return axios.request(this.requestConfig).then(response => {
return Promise.resolve(response.data);
});
}
}
LSRequest.js(内部调用)
import Request from "./Request";
import {Toast} from 'vant';
import { INNER_URL } from "./base";//定义在base.js 里面的请求路径
//是否正在刷新标记
let isRefresh = false;
// 重试队列,每一项将是一个待执行的函数形式
let requests = [];
function flushToken(action) {
// 将resolve放进队列,用一个函数形式来保存,等token刷新后直接执行
let p = new Promise(resolve => {
requests.push(() => {
resolve(action());
});
});
if (!isRefresh) {
isRefresh = true;
LsRequest.create("userauth/token/refresh").post({
authorization: sessionStorage.getItem("authorization"),
refresh_token: sessionStorage.getItem("refreshToken")
}).then(token => {
sessionStorage.setItem("authorization", token.principal);
sessionStorage.setItem("token", token.token);
sessionStorage.setItem("refreshToken", token.refreshToken);
requests.forEach(callback => callback());
requests = [];
});
}
return p;
}
const baseUrl = INNER_URL;
export default class LsRequest extends Request {
constructor(url) {
if (!url.startsWith("/")) {
url = "/" + url;
}
//super键字只能够以指定的形式出现在以下地点,用在子类的constructor函数中;
关键字用于访问父对象上的函数,
super(baseUrl + url);
}
static create(url) {
return new LsRequest(url);
}
execute() {
//设置一些请求头
this.header("authorization", sessionStorage.getItem("authorization"));
this.header("token", sessionStorage.getItem("token"));
//父类方法是可以从`super`对象上调用的
return super.execute().then(response => {
let data = response.data;
let code = response.code;
let message = response.message;
switch (code) {
case 200:
return Promise.resolve(data);
//4012 标明token过期,重新刷新token
case 4012:
return flushToken(this.execute);
case 406:
Toast.fail(message);
break;
case 4011:
//清除缓存数据,重定向到首页
sessionStorage.clear();
router.replace({
path: "/"
});
break;
case 400:
Toast.fail("参数错误:" + message);
break;
case 401:
Toast.fail("权限错误");
break;
case 404:
Toast.fail("04 NOT FOUND");
break;
case 500:
Toast.fail("网络开小差了");
break;
default:
break;
}
return Promise.reject({code: code, message: message});
});
}
}
在mian.js 中将上述导出的方法挂载到全局
Vue.prototype.nativeRequest = Request.create;
Vue.prototype.request = LsRequest.create;
在需要的地方调用即可:
//内部调用
getNoticeList() {
this.request("case-field/message/userMessage")
.get({})
.then(result => {...});
},
// 外部调用
getAudioList(){
this.nativeRequest( this.ML_URL+'/leSoft/audio/' + id)
.get({ })
.then((r) => {...});
},
ps :上班的时候偷偷写的,有点慌