结合Worker通知应用更新

概述

项目部署上线后,特别是网页项目,提示正在操作系统的用户去更新版本非常 important。一般我们都会用"刷新大法"来清理缓存,但是对于正在操作网页的用户,不造系统更新了,请求的还是老版本的资源。

为了确保用户能够及时获得最新的功能和修复的 bug,我们需要通知用户刷新页面获取最新的代码。

方案

每次打包时,都生成一个时间戳,作为系统的伪版本,放到JSON文件中,通过对比文件的响应头Etag判断是否有更新。具体步骤如下:

1: 在public文件夹下加入manifest.json文件,里面存放两个字段:更新内容、更新时间戳

2: 前端打包的时候向manifest.json写入当前时间戳信息

3: 在入口文件main.js中引入检查版本更新的逻辑,有更新则提示更新。

路由守卫router.beforeResolve(vite+vue),检查更新,对比manifest.json文件的响应头Etag判断是否有更新

通过Worker轮询,检查更新,对比manifest.json文件的响应头Etag判断是否有更新。Worker线程并不影响其他线程的逻辑。

流程如下:

实现

注意:以下为vite+vue为例

新建manifest.json

public目录下新建manifest.json

js 复制代码
{
  "timestamp":21312321311,
  "msg":"更新内容如下:\n--1.添加系统更新提示机制"
}

vite.config.js配置

配置vite打包输出更新mainfest.json

js 复制代码
import { defineConfig, type AliasOptions } from 'vite'
import { fileURLToPath, URL } from 'node:url';

import vue from '@vitejs/plugin-vue'

import path from 'path'
import { readFile, writeFile } from 'fs'

// 获取路径
const filePath = path.resolve(`./public`, 'manifest.json')
// 读取文件内容
readFile(filePath, 'utf8', (err, data) => {

  if (err) {
    console.error('读取文件时出错:', err)
    return
  }
  // 将文件内容转换JSON
  const dataObj = JSON.parse(data)
  //修改时间戳
  dataObj.timestamp = new Date().getTime()
  // 将修改后的内容写回文件
  writeFile(filePath, JSON.stringify(dataObj), 'utf8', err => {
    if (err) {
      console.error('写入文件时出错:', err)
      return
    }
  })
})


const alias: AliasOptions = {
  '@': fileURLToPath(new URL('./src', import.meta.url)),
};

// https://vite.dev/config/
export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      ...alias,
    },
  },
})

新建src/checkUpdate.js

检查更新文件

  1. 客户端发起请求,请求中包含上次获取的资源的ETag。
  2. 服务器收到请求后,比较客户端提供的ETag与当前资源的ETag是否一致。 5.如果一致,则返回HTTP 304 Not Modified响应,表示资源未发生变化,客户端可以使用缓存的版本。
  3. 如果不一致,服务器返回最新的资源内容,同时更新ETag。
  4. 客户端收到响应后,更新本地缓存的资源内容和ETag。
js 复制代码
import router from "./router/index.ts";
//上次的Etag
let lastEtag = "";
//是否更新
let hasUpdate = false;
//创建worker线程
// const worker = new Worker();
const worker =  new Worker(new URL("./checkUpdate.worker.js", import.meta.url))


console.log(worker, "worker");



//检查版本更新
async function checkUpdate() {
  try {
    // 检测前端资源是否有更新
    let response = await fetch(`/manifest.json?v=${Date.now()}`, {
      method: "head",
    });
    // 获取最新的etag
    let etag = response.headers.get("etag");
    hasUpdate = lastEtag && etag !== lastEtag;
    lastEtag = etag;
    console.log((lastEtag = etag), "lastEtag = etag");
  } catch (e) {
    return Promise.reject(e);
  }
}

async function confirmReload(msg = "", lastEtag) {
  worker &&
    worker.postMessage({
      type: "pause",
    });
  try {
    console.log("版本更新了");
  } catch (e) {}
}
// 路由拦截
router.beforeEach(async (to, from, next) => {
  next();
  try {
    await checkUpdate();
    if (hasUpdate) {
      worker.postMessage({
        type: "destroy",
      });
      location.reload();
    }
  } catch (e) {}
});

worker.postMessage({
  type: "check",
});

worker.onmessage = ({ data }) => {
  console.log(data, "data");
  if (data.type === "hasUpdate") {
    hasUpdate = true;
    confirmReload(data.msg, data.lastEtag);
  }
};

新建src/checkUpdate.worker.js

js 复制代码
let lastEtag
let hasUpdate = false
let intervalId = ''
async function checkUpdate() {
 
  try {
    // 检测前端资源是否有更新
    let response = await fetch(`/manifest.json?v=${Date.now()}`, {
      method: 'get'
    })
    // 获取最新的etag和data
    let etag = response.headers.get('etag')
    let data = await response.json()
    hasUpdate = lastEtag !== undefined && etag !== lastEtag
  
    if (hasUpdate) {
      postMessage({
        type: 'hasUpdate', 
        msg: data.msg,
        lastEtag: lastEtag,
        etag: etag
      })
    }
    lastEtag = etag
  } catch (e) {
    return Promise.reject(e)
  }
}

// 监听主线程发送过来的数据
addEventListener('message', ({ data }) => {
  console.log(data,'消息')
  if (data.type === 'check') {  
     console.log('checkcheckcheck')
    // 每5分钟执行一次
    // 立即执行一次,获取最新的etag,避免在setInterval等待中系统更新,第一次获取的etag是新的,但是lastEtag还是undefined,不满足条件,错失刷新时机
    // checkUpdate()
    intervalId = setInterval(()=>{
      checkUpdate()
      console.log('检查版本更新')
    },  3 * 1000)
  }
  if (data.type === 'recheck') {
    // 每5分钟执行一次
    hasUpdate = false
    lastEtag = data.lastEtag
    intervalId = setInterval(()=>{
      checkUpdate()
      console.log('检查版本更新')
    },  3 * 1000)
    console.log('recheckrecheckrecheck')
  }
  if (data.type === 'pause') {
    clearInterval(intervalId)
  }
  if (data.type === 'destroy') {
    clearInterval(intervalId)
    close()
  }
})

入口文件

示例:main.ts

js 复制代码
import "./checkUpdate.js"

注意

在工程化项目中,由于要打包后部署,因此work线程文件需要处理资源路径

js 复制代码
//创建worker线程
// const worker = new Worker();
const worker =  new Worker(new URL("./checkUpdate.worker.js", import.meta.url))
相关推荐
叶玳言3 小时前
【LVGL】从HTML到LVGL:嵌入式UI的设计迁移与落地实践
前端·ui·html·移植
高级测试工程师欧阳3 小时前
HTML 基本结构
前端
Gazer_S3 小时前
【Element Plus 表单组件样式统一 & CSS 文字特效实现指南】
前端·css·vue.js
一只小阿乐3 小时前
Html重绘和重排
前端·html
_Rookie._3 小时前
vue3 使用css变量
前端·javascript·html
杨超越luckly3 小时前
HTML应用指南:利用GET请求获取全国招商银行网点位置信息
前端·arcgis·信息可视化·html·银行网点
蔗理苦3 小时前
2025-09-04 HTML3——区块布局与表单
前端·css·html
GIS之路3 小时前
GDAL 开发起步
前端
被巨款砸中3 小时前
前端视角下的 Web 安全攻防:XSS、CSRF、DDoS 一次看懂
前端·安全·xss