Vue項目蹬挤,異步隊列解決雙令牌請求

使用雙令牌的初衷

為了方便分布式部署伴奥,平臺所有接口都允許跨域訪問,但為了防止惡意請求撒强,需要設(shè)置請求頭禽捆、令牌等;
有一部分頁面不需要登錄就可以查看飘哨,接口請求帶一個靜態(tài)令牌胚想,有一部分需要有登錄權(quán)限才能查看,就需要動態(tài)令牌杖玲。
靜態(tài)令牌:利用RSA加密與后臺約定一個publicKey生成一個加密串顿仇,請求接口換取靜態(tài)令牌;
動態(tài)令牌:利用靜態(tài)令牌請求接口換取動態(tài)令牌

踩坑點

1.請求時怎么區(qū)分該接口是需要帶靜態(tài)令牌摆马,還是需要帶動態(tài)令牌臼闻?
2.axios的get請求帶參數(shù)是params:{},post請求是data: {},token怎么攜帶更優(yōu)雅囤采?
3.'/coin/users/[uid]/coins/[coin]'述呐,接口使用的restful接口,該怎么優(yōu)雅處理url的參數(shù)蕉毯?
4.token是有過期時間的乓搬,靜態(tài)token思犁、動態(tài)token過期該怎么處理?
5.兩個token過期后臺返回的錯誤碼都是一樣的进肯,該怎么處理激蹲?
6.如果用戶執(zhí)行一個刪除操作,token過期當前請求不能通過江掩,刷新token后怎么自動再執(zhí)行這個刪除請求学辱,否則用戶會有點擊無效或者卡頓的感受

開始填坑

1.動態(tài)token、靜態(tài)token何時使用环形?
其實在發(fā)送請求的時候只會帶一個令牌過去策泣,也就是只會帶靜態(tài)或者只帶動態(tài),放在請求的header中發(fā)送給后臺抬吟。  
在用戶登錄之后返回一個userid并存起來萨咕,以此來區(qū)分動靜令牌,開始的請求都用靜態(tài)token火本,如果有userid就用動態(tài)token危队。  
動態(tài)令牌的權(quán)限高于靜態(tài)令牌,所有接口都可以用動態(tài)token发侵。
2.axios的get請求帶參數(shù)是params:{}交掏,post請求是data: {},token怎么攜帶更優(yōu)雅妆偏?
如果每次請求都去手動將token帶入params或者data很難受刃鳄,因為這是一個高度重復性的事情;   
索性就放在請求的header中钱骂,這樣后臺獲取token也方便叔锐,前端也一勞永逸。
import axios from 'axios'
import store from '@/store'

//請求攔截器
axios.interceptors.request.use(config => {
    config.headers['JWT'] = store.getters.JWT;
    config.headers['UID'] = store.getters.uid;

    return config;
}, error => {
    return Promise.reject(error)
})
3.'/coin/users/[uid]/coins/[coin]'见秽,接口使用的restful接口膨蛮,該怎么優(yōu)雅處理url的參數(shù)析孽?
請求傳參時,將url上需要的參數(shù)一起傳入,然后寫一個函數(shù)來統(tǒng)一處理
//請求攔截器
axios.interceptors.request.use(config => {
    config.headers['JWT'] = store.getters.JWT;
    config.headers['UID'] = store.getters.uid;
    config.url = replaceUrl(config.url, config.method == 'get' ? config.params : config.data)
    return config;
}, error => {
    return Promise.reject(error)
})
/**
 * url特殊變量替換
 * @param {string} url -要請求的url
 * @param {object} params -需要替換進url的值
 */
function replaceUrl(url, params) {
    /**
    * url: '/coin/[articleId]/coins/[coin]'
    * params: {articleId: 198, coin: 'ytx', inviteCount:0, realName: '杰克'}
    * 替換之后 /coin/198/coins/yxt
    **/
    const reg = /\[[a-zA-Z]+\]/g;
    let flag = true;
    let n = 10; //防止無限循環(huán)

    while(flag && n > 0) {
        let result = reg.exec(url); //匹配url是否有[***]這種特殊變量
        let item = result ? result[0] : null;
        if(item == '[uid]') { continue; } //如果有[uid]特殊變量放案,跳過,[uid]會在request的時候處理
        if(item !== null) {
            let key = item.replace('[','').replace(']','');
            let val = params[key]; //有特殊變量既荚,還需要特殊的值去替換
            params[key] = null;
            if(val) {
                url = url.replace(item, val);
            } else {
                console.warn(item + '沒有傳入對應的值')
            }
        } else {
            flag = false;
        }
        n--;
    }
    return url;
}
4.后面的坑填起來是費時費神遭铺,幾經(jīng)周折,最后引入異步隊列來解決振乏;所有請求放入數(shù)組中排隊蔗包,上一個請求完成進行下一個請求,如果進行到某一個請求時令牌過期慧邮,可以暫停隊列等待拿到新的令牌调限,然后繼續(xù)執(zhí)行隊列
//創(chuàng)建request.js
import { response } from './response.js'
import axios from 'axios'
import store from '@/store'

const service = axios.create({
    baseURL: process.env.VUE_APP_BASE_URL, // api的base_url  在config中分別設(shè)置開發(fā)環(huán)境和生產(chǎn)環(huán)境
    timeout: 20000, // request timeout
    headers: {
        'Content-Type': 'application/json; charset=UTF-8',
    }
});
service.interceptors.request.use(config => {
    config.headers['JWT'] = store.getters.JWT;
    config.headers['Content-Type'] = "application/json; charset=UTF-8";
    config.headers['UID'] = store.getters.uid;

    let uid = Number(store.getters.uid) || null;
    config.url = config.url.replace('[uid]', uid || '');

    // 這幾個請求的參數(shù)需要在body中傳參
    if (config.method == 'post' || config.method == 'put') {
        config.data = config.data || config.params;
    }
    return config;
}, error => {
    alert(error)
    return Promise.reject(error)
})
//返回攔截器
service.interceptors.response.use(res => {
    var respones = res.data;
    return response(res, res.config)
}, error => {
    if (error.toString().indexOf('Network Error') != -1) {
        alert('網(wǎng)絡(luò)異常舟陆,請檢查您的網(wǎng)絡(luò)!')
    }
    return Promise.reject(error)
})


export default service;
// 創(chuàng)建intercept.js耻矮,接口請求從intercept走
import store from '../store/index.js'
import { staticToken, superToken, } from './token'
import request from '@/http/request.js'

//隊列 
const quee = []; 
//標識隊列是否正在進行中秦躯,false請求進入隊列立即執(zhí)行,true請求進入隊列排隊
let wait = false; 

//請求入口
export async function intercept(config) {
    let status = await checkStaticToken()
    if (status) {
        return new Promise(resolve => {
            resolve(add(config))
        })
    }

}

//拓展請求方式
['get','post','put','patch','delete','head','options'].forEach(el => {
    intercept[el] = function(url, data, conf = {}) {
        const options = {
            url,
            method: el,
            ...conf
        }
        if(el == 'post' || el == 'put') {
            options.data = data;
        } else {
            options.params = data;
        }
        options.url = replaceUrl(options.url, data);
        return intercept(options)
    }
})


/**
 * url特殊變量替換
 * @param {string} url -要請求的url
 * @param {object} params -需要替換進url的值
 */
function replaceUrl(url, params) {
    const reg = /\[[a-zA-Z]+\]/g;
    let flag = true;
    let n = 10; //防止無限循環(huán)

    while(flag && n > 0) {
        let result = reg.exec(url); //匹配url是否有[***]這種特殊變量
        let item = result ? result[0] : null;
        if(item == '[uid]') { continue; } //如果有[uid]特殊變量裆装,跳過宦赠,[uid]會在request的時候處理
        if(item !== null) {
            let key = item.replace('[','').replace(']','');
            let val = params[key]; //有特殊變量,還需要特殊的值去替換
            params[key]=null;
            if(val) {
                url = url.replace(item, val);
            } else {
                console.warn(item + '沒有傳入對應的值')
            }
        } else {
            flag = false;
        }
        n--;
    }
    return url;
}

//往隊列添加
function add(config) {
    return new Promise((resolve, reject) => {
        quee.push({
            resolve,
            config,
            reject,
            count: 0 //防止無限請求
        })
        if(!wait) {
            wait = true;
            run();
        }
    })
    
}

/**
 * 執(zhí)行隊列請求
 * 規(guī)則:
 * 所有請求都在隊列中排序米母,run的時候永遠只執(zhí)行隊列第一項勾扭,
 * 當請求完成(返回錯誤或返回正確,都算請求完成)铁瞒,讓第一項出隊列妙色,繼續(xù)run,這樣就能按順序執(zhí)行所有請求慧耍,
 * 有一個wait參數(shù)來標識隊列是否正在執(zhí)行中身辨,如果true就讓后面進來的請求排隊,false就立即執(zhí)行當前請求
 */
async function run() {
    let item = quee[0];
    // 如果url上有[uid]標識芍碧,但又不存在UID煌珊,拒絕請求;或者另外一種方式:跳轉(zhuǎn)登錄頁
    if(item.config.url.indexOf('[uid]') > -1 && !store.getters.uid) {
        console.warn('url上有[uid]標識泌豆,但又不存在UID定庵,拒絕請求');
        item.resolve({code: '500', msg: '沒有UID,不發(fā)起請求'});
        checkQuee();
        return false;
    }
    //如果重復請求超過三次踪危,防止無限請求蔬浙,需要停止
    if(item.count > 3) {
        checkQuee();
        return false;
    }
    request(item.config).then(res => {
        if (res && res.code == 'needToken') {
            item.count++;
            //如果有UID,是動態(tài)令牌過期贞远,否則就是只需要靜態(tài)令牌
            if(store.getters.uid) {
                checkSuperToken().then(status => {
                    if(status) {//如果獲取動態(tài)令牌成功畴博,繼續(xù)跑隊列
                        run()
                    } else {//否則獲取到靜態(tài)->動態(tài)令牌之后繼續(xù)跑隊列,如果獲取token失敗,出隊列繼續(xù)往下走
                        store.dispatch("token", "");
                        getAllToken().then(token => { 
                            token ? run() : checkQuee();
                        })
                    }
                })
            } else {
                store.dispatch("token", "");
                checkStaticToken().then(status => { //獲取靜態(tài)令牌成功繼續(xù)跑隊列蓝仲,失敗什么都不能請求俱病,索性清空隊列,
                    if(status) {
                        run()
                    } else {
                        quee.splice(0, quee.length);
                        wait = false;
                        item.resolve({code: '5104',data:'',msg:'獲取靜態(tài)令牌失敗'});
                    }
                })
            }
            
            return false;
        }
        checkQuee();
        item.resolve(res)
    }).catch(err => { //如果當前請求失敗袱结,則繼續(xù)執(zhí)行后面的請求
        quee.splice(0, 1);
        quee.length ? run() : wait = false;
        item.reject(err);
    })
}

//檢查隊列
function checkQuee() {
    quee.splice(0, 1) //請求成功后將該項移除隊列
    quee.length ? run() : wait = false; //如果隊列有數(shù)據(jù)則繼續(xù)跑亮隙,否則通知隊列已經(jīng)執(zhí)行完畢
}

//請求靜態(tài)令牌
async function checkStaticToken() {
    if (!store.getters.JWT) {
        let res = await staticToken()
        if (res.code == '0000') {
            store.dispatch("token", res.data);
            return true;
        } else {
            return false;
        }
    }
    return true;
}

//請求動態(tài)令牌
async function checkSuperToken() {
    let res = await superToken()
    if (res.code == '0000') {
        store.dispatch("token", res.data);
        return true;
    } else {
        store.dispatch("token", '');
        return false;
    }
}

//依次獲取靜態(tài)->動態(tài)令牌
async function getAllToken() {
    let staticT = await staticToken();
    if(staticT.code != '0000') {
        return false;
    }
    store.dispatch("token", staticT.data);

    let dynamic = await superToken();
    if(dynamic.code != '0000') {
        return false;
    }
    store.dispatch("token", dynamic.data);
    return true;
}

6.使用
//main.js中掛載
import { intercept } from './http/intercept'

Vue.prototype.$intercept = intercept
Vue.prototype.$get = intercept.get
Vue.prototype.$post = intercept.post
Vue.prototype.$put = intercept.put
Vue.prototype.$delete = intercept.delete

//在頁面中
this.$get('/a/b/c')  //發(fā)起get請求
this.$post('/a/b/c',{a,b,c}) //發(fā)起post請求
7.其他補充

在請求中不免有些敏感參數(shù),不方便明文傳輸擎勘,可以使用RSA進行加密咱揍,點擊查看jsencrypt使用方法
在GitHub中查看完整demo https://github.com/yellowSTA/doubleToken

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末棚饵,一起剝皮案震驚了整個濱河市煤裙,隨后出現(xiàn)的幾起案子掩完,更是在濱河造成了極大的恐慌,老刑警劉巖硼砰,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件且蓬,死亡現(xiàn)場離奇詭異,居然都是意外死亡题翰,警方通過查閱死者的電腦和手機恶阴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來豹障,“玉大人冯事,你說我怎么就攤上這事⊙” “怎么了昵仅?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長累魔。 經(jīng)常有香客問我摔笤,道長,這世上最難降的妖魔是什么垦写? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任吕世,我火速辦了婚禮,結(jié)果婚禮上梯投,老公的妹妹穿的比我還像新娘命辖。我一直安慰自己,他們只是感情好晚伙,可當我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布吮龄。 她就那樣靜靜地躺著,像睡著了一般咆疗。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上母债,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天午磁,我揣著相機與錄音,去河邊找鬼毡们。 笑死迅皇,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的衙熔。 我是一名探鬼主播登颓,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼红氯!你這毒婦竟也來了框咙?” 一聲冷哼從身側(cè)響起咕痛,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎喇嘱,沒想到半個月后茉贡,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡者铜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年腔丧,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片作烟。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡愉粤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出拿撩,到底是詐尸還是另有隱情科汗,我是刑警寧澤,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布绷雏,位于F島的核電站头滔,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏涎显。R本人自食惡果不足惜坤检,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望期吓。 院中可真熱鬧早歇,春花似錦、人聲如沸讨勤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽潭千。三九已至谱姓,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間刨晴,已是汗流浹背屉来。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留狈癞,地道東北人茄靠。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像蝶桶,于是被迫代替她去往敵國和親慨绳。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,781評論 2 354

推薦閱讀更多精彩內(nèi)容

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,101評論 1 32
  • # Python 資源大全中文版 我想很多程序員應該記得 GitHub 上有一個 Awesome - XXX 系列...
    小邁克閱讀 2,985評論 1 3
  • 筆記 RESTful架構(gòu)風格概述 RESTful架構(gòu)風格 RESTful架構(gòu)風格最初由Roy T. Fieldin...
    plutoese閱讀 12,623評論 3 58
  • 以此記錄配置office365登錄認證時遇到的那些坑。 需求: 想在自己APP中獲取到用戶office365賬號的...
    斷片記憶閱讀 1,910評論 0 2
  • 聊聊高并發(fā)系統(tǒng)限流特技-1來自開濤的博客 在開發(fā)高并發(fā)系統(tǒng)時有三把利器用來保護系統(tǒng):緩存脐雪、降級和限流厌小。緩存的目的是...
    meng_philip123閱讀 6,640評論 1 20