/**
* 测试的函数,故意让第一次请求失效,正式使用的时候 请去掉
* @type {number}
*/
let i = 0;
class RequestUtils {
constructor() {
this.instance = null;
this.isRefreshing = false;
this.subscribers = [];
this.token = null;
this.LOGIN_URL = "http://101.200.151.183:8080/oauth/token";
}
/**
* 单例构造方法,构造一个广为人知的接口,供用户对该类进行实例化
* @returns {RequestUtils}
*/
static getInstance() {
if (!this.instance) {
this.instance = new RequestUtils();
}
return this.instance;
}
/**
* 带token的请求
* @param url 请求路径
* @param options 请求参数
* @returns {Promise<Response | never>}
*/
request(url, options) {
let self = this;
//让第一请求失效
if (i === 0) {
options.set('access_token', "错误的参数");
} else {
options.set('access_token', self.token.access_token);
}
i++;
return fetch(url, {
method: 'POST',
model: 'cros', //跨域
headers: {
Accept: 'application/json'
},
body: options
})
.then(response => {
return self.checkStatus(response, url, options);
});
}
/**
* 检查token 是否失效,如果失效,刷新token
* @param response 拦截的请求 response
* @param url 请求路径
* @param options 请求参数
* @returns {Promise<any>|*}
*/
checkStatus(response, url, options) {
// eslint-disable-next-line no-console
console.log("checkStatus", response.status);
let self = this;
if (response && response.status === 401) {
// eslint-disable-next-line no-console
console.log("response.status", response.status);
// 这个Promise函数很关键
let p = new Promise((resolve) => {
self.addSubscriber(() => {
resolve(self.request(url, options))
});
});
// eslint-disable-next-line no-console
console.log("isRefreshing", self.isRefreshing);
// 刷新token的函数,这需要添加一个开关,防止重复请求
if (!self.isRefreshing) {
self.isRefreshing = true;
self.flushToken()
.catch(error => {
// eslint-disable-next-line no-console
console.log(error);
});
}
return p;
} else {
return response;
}
}
/**
* 重新执行token 失效的函数
*/
onAccessTokenFetched() {
// eslint-disable-next-line no-console
console.log("subscribers", this.subscribers);
this.subscribers.forEach((callback) => {
callback();
});
this.subscribers = [];
}
/**
* 把请求的token 失效的函数放到 subscribers
* @param callback 请求的token 失效的函数
*/
addSubscriber(callback) {
// eslint-disable-next-line no-console
console.log("addSubscriber", callback);
this.subscribers.push(callback);
// eslint-disable-next-line no-console
console.log("this.subscribers", this.subscribers);
}
/**
* 用户登录的方法
* @param username 用户名
* @param password 密码
* @returns {Promise<Response>} 登录状态
*/
login(username, password) {
let self = this;
let param = new FormData();
param.set('client_id', 'v-client');
param.set('client_secret', 'v-client-ppp');
param.set('grant_type', 'password');
param.set('scope', 'select');
param.set('username', username.trim());
param.set('password', password.trim());
return fetch(self.LOGIN_URL, {
method: 'POST',
model: 'cros', //跨域
headers: {
Accept: 'application/json'
},
body: param
})
.then(response => {
if (response.status === 200) {
return response.json();
} else if (response.status === 401) {
return new Promise((resolve, reject) => {
reject("用户名密码错误");
});
} else {
return new Promise((resolve, reject) => {
reject("服务器错误");
});
}
}).then(json => {
self.token = json;
self.isRefreshing = false;
return new Promise((resolve) => {
resolve(json);
})
})
}
/**
* 刷新token
* @returns {Promise<Response | never>}
*/
flushToken() {
let self = this;
let param = new FormData();
param.set('client_id', 'v-client');
param.set('client_secret', 'v-client-ppp');
param.set('grant_type', 'refresh_token');
param.set('scope', 'select');
param.set('refresh_token', self.token.refresh_token);
return fetch(self.LOGIN_URL, {
method: 'POST',
model: 'cros', //跨域
headers: {
Accept: 'application/json'
},
body: param
})
.then(response => {
// eslint-disable-next-line no-console
console.log("刷新token response.status", response.status);
if (response.status === 200) {
return response.json();
} else if (response.status === 401) {
return new Promise((resolve, reject) => {
reject("刷新token错误:请退出重新登录");
});
} else {
return new Promise((resolve, reject) => {
reject("刷新token错误:服务器错误");
});
}
}).then(json => {
self.token = json;
self.onAccessTokenFetched();
self.isRefreshing = true;
})
}
}
export default RequestUtils;
测试代码,远程地址可以调用
RequestUtils .getInstance() .login("zhangsan", "123456") .then(json => { // eslint-disable-next-line no-console console.log(json); let param1 = new FormData(); return RequestUtils .getInstance() .request("http://101.200.151.183:8080/api/user/init", param1) }) .then(response => { // eslint-disable-next-line no-console console.log(response.status); // eslint-disable-next-line no-console return response.json(); }).then(json => { // eslint-disable-next-line no-console console.log(json); });
解决思路,通过拦截 request 方法的返回值,判断是否token 失效,
如果失效,执行刷新(加锁 isRefreshing ),然后把失效的方法push 到 subscribers 中,
刷新完成后 callback subscribers 的失效方法。
测试可以通过,如果有考虑不周,请指教。
主要是聊天工具中会用到此机制,但是多终端还是有问题。 https://gitee.com/lele-666/V-IM
参考 : https://segmentfault.com/a/1190000016946316?utm_source=tag-newest