背景介紹
在我們做 web 項目時,經常會遇到一個問題就是,需要 通知業務人員(系統用戶)刷新瀏覽器或者清空瀏覽器 cookie 緩存
的情況。 而對于用戶而言,很多人一方面不懂如何操作,另一方面由于執行力問題,很少有人進行這樣的操作。進而遇到常見的緩存問題而上報運維問題。 耽誤用戶的同時,也增加了我們的運維工作量。 甚至有時研發人員還經常吐槽(都告訴他刷新了,怎么就不刷!)
今天,咱們通過簡單的幾行代碼,來實現靈活控制,是否強制提醒并提供便利的操作讓業務人員自動執行此操作!
實現原理
- 案例前端代碼為 vue 工程代碼(其他前端架構原理相同)
- 前端追加一個定時器,定時 N 秒中獲取文件/版本號變化情況,來決定是否彈出強制刷新/清緩存提醒。
- 實現手段
- 監聽后端打包的前端文件是否發生變化(這個辦法不佳,因為控制不夠靈活)
- 優點:一勞永逸,只要重新打包發版,就會觸發提醒
- 缺點:控制不夠靈活,沒法針對刷新瀏覽器與清空緩存分開控制; 并且有時候不需要刷新也會被刷新
- 通過后端提供getVersion 接口,動態返回版本號以及是否清空緩存標識來控制
- 優點:控制更加靈活
- 缺點:后端需要同步提供一個接口配合使用,然后每次通過修改數據字典的版本號來實現。
- 監聽后端打包的前端文件是否發生變化(這個辦法不佳,因為控制不夠靈活)
前端代碼
監聽文件變化法(不推薦)
此法優缺點詳見實現原理介紹,根據自己訴求選擇方案。 有的項目有這樣的訴求,完全可以用此法。
此法還有一個最大的弊端
- 就是監聽生效的前提,必須開啟頁面并聚焦到當前瀏覽器上才生效。 如果當前沒有聚焦到頁面,那么當文件變化后,超過定時器的時間間隔后,并不會生效。
auto-update.js 核心源碼
// 發版刷新頁面,根據監測上傳文件實現刷新
import Data from "xe-utils/date";let lastSrcs;
//獲取到js名字
const scriptReg = /\<script.*src=["'](?<src>[^"']+)/gm;//獲取最新頁面中的script鏈接
async function extractNewScripts() {// _timestamp避免緩存,獲取當前時間戳const html = await fetch('/?_timestamp=' + Data.now()).then((resp) =>resp.text());scriptReg.lastIndex = 0;let result = [];let match;while ((match = scriptReg.exec(html))) {result.push(match.groups.src);}return result;
}//進行js文件命名對比
async function needUpdate() {const newScripts = await extractNewScripts();if (!lastSrcs) {lastSrcs = newScripts;return false;}let result = false;if (lastSrcs.length !== newScripts.length) {result = true;}for (let i = 0; i < lastSrcs.length; i++) {if (lastSrcs[i] !== newScripts[i]) {result = true;break;}}lastSrcs = newScripts;return result;
}
//每五秒進行一次比對
const DURATION = 5000;
//出現的彈窗里的文字
function autoRefresh() {setTimeout(async () => {const willUpdate = await needUpdate();if (willUpdate) {const result = confirm('有新功能發布,請點擊確定刷新頁面!');if (result) {location.reload(true);}}autoRefresh();},DURATION);
}autoRefresh();
- 在 main.ts(main.js) 里引入auto-update.js
// 頁面構建刷新
import './auto-update/auto-update.js';
getVersion 法(推薦)
前端在 App.vue 中追加如下代碼
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-HlikCEAI-1692003330296)(/tfl/pictures/202308/tapd_32565483_1692001218_668.png)]
下面附上源碼
前端追加定時器
created(){// 定時器,每五秒請求一次接口對比版本號setInterval(()=>{this.checkVersion()}, 5000)
},
前端 checkVersion() 核心源碼
checkVersion () {// 在需要判斷登錄狀態的地方,獲取sessionStorage存儲的dialogInfo并驗證const dialogInfo = sessionStorage.getItem('dialogInfo');if (dialogInfo) {// 已登錄時的處理邏輯// 可以在這里請求接口等操作axios.get(`/baseCode/getVersion`,{headers: {'Cache-Control': 'no-cache'}}) // 反正就是要請求到json文件的內容, 并且禁止緩存.then(res => {// servie版本號const versionServie = res.data.data.value// 當前瀏覽器端緩存的版本號const clientVersion = localStorage.getItem('_version_')console.log('當前服務器端servie版本號=', versionServie, ',當前瀏覽器端緩存的版本號=', clientVersion)// 和最新版本不同,刷新頁面if (versionServie !== clientVersion) {if (res.data.data.att1 === '1') {// 先刷瀏覽器,再清緩存(sessionStorage,cookie)const result = confirm('有新功能發布!\n需點擊確定自動為您清除瀏覽器緩存(cookie,sessionStorage)!\n本操作將退出當前登錄!');if (result) {//將最新的版本號存儲到本地localStorage.setItem('_version_', versionServie)location.reload(true);// 第一步:清除sessionStorage中的緩存數據sessionStorage.clear(); // 清除所有的緩存數據// 第二步:清除所有的cookieconst cookies = document.cookie.split(';');for (let i = 0; i < cookies.length; i++) {const cookie = cookies[i];const eqPos = cookie.indexOf('=');const name = eqPos > -1 ? cookie.substr(0, eqPos) : cookie;document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;}}} else {// 只刷新瀏覽器不清除緩存const result = confirm('有新功能發布,請點擊確定刷新頁面!');if (result) {localStorage.setItem('_version_', versionServie)location.reload(true);}}}else {//版本號一致不做處理console.log('版本號一致不做處理')}}).catch(error => {console.log(error)})} else {// 未登錄時的處理邏輯,跳轉到登錄頁面this.$router.push({ name: 'Login' })}
},
說明:
1、dialogInfo 用來判斷當前登錄 session 狀態,若已登錄則進行后續流程判斷
2、/baseCode/getVersion 為后端接口,可根據自己項目情況靈活調整
3、業務邏輯
1)若當前瀏覽器端緩存的版本號clientVersion
與服務器端返回的版本號versionServie
不一致,則觸發提醒機制
2)若強制清空瀏覽器緩存標志att1
為0
則只觸發刷新瀏覽器
動作
3)若強制清空瀏覽器緩存標志att1
為1
則觸發清空瀏覽器緩存(sessionStorage,cookie)
動作
4、注意,以上接口結構可根據自己項目架構實際調整,不一定非得跟我的一致,原理就是后端接口返回了兩個字段,一個版本號用于控制是否觸發校驗機制,一個就是是否同步出發清空瀏覽器緩存標志。 一定要這倆分開控制,因為有的時候只需要刷新瀏覽器不需要清緩存。
后端 getVersion() 接口
說明
BaseCodeInfo 為我項目中的數據字典,你可根據你項目中的數據字典進行合理替換,不用單獨跟我一樣創建一張表。
此表主要有三個控制機制:
- 此功能的全局開關:validstatus (1-開啟,0-關閉)
- value:版本號,若想出發前端刷新或情空緩存,版本號必變
- att1:是否強制刷新緩存標志,在 value 變更的前提下,若att1 = 0,則只刷新瀏覽器,若att1 = 1,則 清空瀏覽器緩存(sessionStorage,cookie)
- 因為前端的定時器監聽時間間隔在秒級別,所以后端要使用 redis 緩存存儲 value 以及 att1,否則頻繁查詢數據庫,性能會受到影響。這里注意兩個點:
1)當 redis 查不到時,兼容查詢數據庫是否存在,若存在也可以返回結果,并刷新 redis 數據
2)當修改數據字典的值時,同步刷新 redis
getVersion() 接口
/*** value: 版本號* att1:是否同步刷新緩存(1-是,0 否);* <p>* 1、瀏覽器緩存版本號與 value 不一致則刷新瀏覽器;* 2、若value不一致的同時 att1等于 1 則同步清緩存** @return*/
public BaseCodeResVO getVersion() {String value = redisTemplate.opsForValue().get(UPGRADE_VERSION);BaseCodeResVO baseCodeResVO = null;if (StringUtils.isNotBlank(value) && value.contains("_")) {String[] arr = value.split("_");if (arr.length > 0) {baseCodeResVO = new BaseCodeResVO();baseCodeResVO.setCode(UPGRADE_VERSION);baseCodeResVO.setValue(arr[0]);baseCodeResVO.setAtt1(arr[1]);return baseCodeResVO;}}BaseCodeInfo baseCodeInfo = baseCodeInfoDao.selectOne(new QueryWrapper<BaseCodeInfo>().lambda().eq(BaseCodeInfo::getType, UPGRADE_VERSION).eq(BaseCodeInfo::getValidStatus, ValidStatusEnum.VALID.value()));if (baseCodeInfo != null) {log.info("從 redis 獲取系統升級版本號失敗,請及時跟進!");baseCodeResVO = new BaseCodeResVO();baseCodeResVO.setCode(UPGRADE_VERSION);baseCodeResVO.setValue(baseCodeInfo.getValue());baseCodeResVO.setAtt1(baseCodeInfo.getAtt1());String finalValue = baseCodeInfo.getValue() + "_" + baseCodeInfo.getAtt1();redisTemplate.opsForValue().set(UPGRADE_VERSION, finalValue);} else {log.error("獲取系統升級版本號失敗,value={}", value);}return baseCodeResVO;
}