會話過期後token重新整理,重新請求介面(訂閱釋出模式)
需求
在一個頁面內,當請求失敗並且返回 302 後,判斷是介面過期還是登入過期,如果是介面過期,則去請求新的token,然後拿新的token去再次發起請求.
思路
- 當初,想了一個黑科技(為了偷懶),就是拿到新的token後,直接強制重新整理頁面,這樣一個頁面內的介面就自動重新整理啦~(方便是方便,使用者體驗卻不好)
- 目前,想到了重新請求介面時,可以配合訂閱釋出模式來提高使用者體驗
響應攔截
首先我們發起一個請求 axios({url:'/test',data:xxx}).then(res=>{})
攔截到302後,我們進入到重新整理token邏輯
響應攔截程式碼
axios.interceptors.response.use(
function (response) {
if (response.status == 200) {
return response;
}
},
(err) => {
//重新整理token
let res = err.response || {};
if (res.data.meta?.statusCode == 302) {
return refeshToken(res);
} else {
return err;
}
}
);
複製程式碼
我們後臺的資料格式是根據statusCode來判斷過期(你們可以根據自己的實際情況判斷),接著進入refrshToken
方法~
重新整理token方法
//避免其他介面同時請求(只請求一次token介面)
let isRefreshToken = false;
const refeshToken = (response) => {
if (!isRefreshToken) {
isRefreshToken = true;
axios({
//獲取新token介面
url: `/api/refreshToken`,
})
.then((res) => {
const { data = '', meta = {} } = res.data;
if (meta.statusCode === 200) {
isRefreshToken = false;
//釋出 訊息
retryOldRequest.trigger(data);
} else {
history.push('/user/login');
}
})
.catch((err) => {
history.push('/user/login');
});
}
//收集訂閱者 並把成功後的資料返回原介面
return retryOldRequest.listen(response);
};
複製程式碼
看到這,有的小夥伴就有點奇怪retryOldRequest
這個又是什麼?沒錯,這就是我們男二 訂閱釋出模式佇列。
訂閱釋出模式
把失敗的介面當訂閱者,成功拿到新的token後再發布(重新請求介面)。
以下便是訂閱釋出模式程式碼
const retryOldRequest = {
//維護失敗請求的response
requestQuery: [],
//新增訂閱者
listen(response) {
return new Promise((resolve) => {
this.requestQuery.push((newToken) => {
let config = response.config || {};
//Authorization是傳給後臺的身份令牌
config.headers['Authorization'] = newToken;
resolve(axios(config));
});
});
},
//釋出訊息
trigger(newToken) {
this.requestQuery.forEach((fn) => {
fn(newToken);
});
this.requestQuery = [];
},
};
複製程式碼
大家可以先不用關注訂閱者的邏輯,只需要知道訂閱者是每次請求失敗後的介面(reponse)就好了。
每次進入refeshToken
方法,我們失敗的介面都會觸發retryOldRequest.listen
去訂閱,而我們的requestQuery
則是儲存這些訂閱者的佇列。
注意
:我們訂閱者佇列requestQuery
是儲存待發布的方法。而在成功獲取新token後,retryOldRequest.trigger
就會去釋出這些訊息(新token)給訂閱者(觸發訂閱佇列的方法)。
而訂閱者(response
)裡面有config配置,我們拿到新的token後(釋出後),修改config裡面的請求頭Autorzation.而藉助Promise我們可以更好的拿到新token請求回來的介面資料,一旦請求到資料,我們可以原封不動的返回給原來的介面/test
了(因為我們在響應攔截那裡返回的是refreshToken
,而refreshToken
又返回的是訂閱者retryOldRequest.listen
返回的資料,而Listiner又返回Promise的資料,Promise又在成功請求後resolve出去)。
看到這,小夥伴們是不是覺得有點繞了~
而在真實開發中,我們的邏輯還含有登入過期(與請求過期區分開來)。我們是根據 當前時間 - 過去時間 < expiresTime
(epiresTime:登入後返回的有效時間)來判斷是請求過期還是登入過期的。 以下是完整邏輯
以下是完整程式碼
const retryOldRequest = {
//維護失敗請求的response
requestQuery: [],
//新增訂閱者
listen(response) {
return new Promise((resolve) => {
this.requestQuery.push((newToken) => {
let config = response.config || {};
config.headers['Authorization'] = newToken;
resolve(axios(config));
});
});
},
//釋出訊息
trigger(newToken) {
this.requestQuery.forEach((fn) => {
fn(newToken);
});
this.requestQuery = [];
},
};
/**
* sessionExpiredTips
* 會話過期:
* 重新整理token失敗,得重新登入
* 使用者未授權,頁面跳轉到登入頁面
* 介面過期 => 重新整理token
* 登入過期 => 重新登入
* expiresTime => 在本業務中返回18000ms == 5h
* ****/
//避免其他介面同時請求
let isRefreshToken = false;
let timer = null;
const refeshToken = (response) => {
//登入後拿到的有效期
let userExpir = localStorage.getItem('expiresTime');
//當前時間
let nowTime = Math.floor(new Date().getTime() / 1000);
//最後請求的時間
let lastResTime = localStorage.getItem('lastResponseTime') || nowTime;
//登入後儲存到本地的token
let token = localStorage.getItem('token');
if (token && nowTime - lastResTime < userExpir) {
if (!isRefreshToken) {
isRefreshToken = true;
axios({
url: `/api/refreshToken`,
})
.then((res) => {
const { data = '', meta = {} } = res.data;
isRefreshToken = false;
if (meta.statusCode === 200) {
localStorage.setItem('token', data);
localStorage.setItem('lastResponseTime', Math.floor(new Date().getTime() / 1000)
);
//釋出 訊息
retryOldRequest.trigger(data);
} else {
//去登入
}
})
.catch((err) => {
isRefreshToken = false;
//去登入
});
}
//收集訂閱者 並把成功後的資料返回原介面
return retryOldRequest.listen(response);
} else {
//節流:避免重複執行
//去登入
}
};
// http response 響應攔截
axios.interceptors.response.use(
function (response) {
if (response.status == 200) {
//記錄最後操作時間
localStorage.setItem('lastResponseTime', Math.floor(new Date().getTime() / 1000));
return response;
}
},
(err) => {
let res = err.response || {};
if (res.data.meta?.statusCode == 302) {
return refeshToken(res);
} else {
// 非302 報的錯誤;
return err;
}
}
);
複製程式碼
以上便是我們這邊的業務,如果寫的不好請大佬多擔待~~
最後
如果你覺得此文對你有一丁點幫助,點個贊。或者可以加入我的開發交流群:1025263163相互學習,我們會有專業的技術答疑解惑
如果你覺得這篇文章對你有點用的話,麻煩請給我們的開源專案點點star:http://github.crmeb.net/u/defu不勝感激 !
- Netty核心概念之ChannelHandler&Pipeline&ChannelHandlerContext
- 理解python非同步程式設計與簡單實現asyncio
- Mycat 作為代理服務端的小知識點
- 一文吃透 React Expiration Time
- 前端模組化詳解
- Java必備主流技術流程圖
- 【建議使用】告別if,Java超好用引數校驗工具類
- MySQL模糊查詢再也不用like %了
- Java 8 的Stream流那麼強大,你知道它的原理嗎
- Vue SEO的四種方案
- 會話過期後token重新整理,重新請求介面(訂閱釋出模式)
- 常用的前端JavaScript方法封裝
- 完整過一遍axios,再也不怕寫請求
- Vue.cli專案封裝全域性axios,封裝請求,封裝公共的api和呼叫請求的全過程
- MySQL一會兒快,一會兒慢的,我該怎麼辦?
- 【前端必知】Webpack效能優化
- 忘記 ajax,擁抱前後端一體化
- 你需要知道的 25 個 Vue 技巧
- Java記憶體分配原理精講
- Innodb到底是怎麼加鎖的