會話過期後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不勝感激 !
- 遵循Promises/A 規範,深入分析Promise實現細節 | 通過872測試樣例
- 80 行代碼實現簡易 RxJS
- 前後端分離項目,如何解決跨域問題?
- springboot中攔截並替換token來簡化身份驗證
- 15 行代碼在 wangEditor v5 使用數學公式
- Java線程池必知必會
- EdgeDB 架構簡析
- TS 類型體操:圖解一個複雜高級類型
- 基於babel的埋點工具簡單實現及思考
- 使用craco對cra項目進行構建優化
- Netty核心概念之ChannelHandler&Pipeline&ChannelHandlerContext
- 理解python異步編程與簡單實現asyncio
- Mycat 作為代理服務端的小知識點
- 一文吃透 React Expiration Time
- 前端模塊化詳解
- Java必備主流技術流程圖
- 【建議使用】吿別if,Java超好用參數校驗工具類
- MySQL模糊查詢再也不用like %了
- Java 8 的Stream流那麼強大,你知道它的原理嗎
- Vue SEO的四種方案