oauth2.0 前端并发刷新问题解决方案。

原创
2019/06/04 16:18
阅读数 1K
/**
 * 测试的函数,故意让第一次请求失效,正式使用的时候 请去掉
 * @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

展开阅读全文
加载中

作者的其它热门文章

打赏
0
3 收藏
分享
打赏
0 评论
3 收藏
0
分享
返回顶部
顶部