UzumakiItachi
首页
  • JavaSript
  • Vue

    • Vue2
    • Vue3
  • React

    • React_18
  • WebPack
  • 浏览器相关
  • 工程化相关
  • 工作中遇到的问题以及解决方案
  • Git
  • 面试
  • 学习
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
  • 个人产出
  • 实用工具
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

UzumakiItachi

起风了,唯有努力生存。
首页
  • JavaSript
  • Vue

    • Vue2
    • Vue3
  • React

    • React_18
  • WebPack
  • 浏览器相关
  • 工程化相关
  • 工作中遇到的问题以及解决方案
  • Git
  • 面试
  • 学习
  • 心情杂货
  • 实用技巧
  • 友情链接
关于
  • 个人产出
  • 实用工具
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • WebPack

  • 浏览器相关

  • 工程化相关

  • 工作中遇到的问题以及解决方案

    • 解决浏览器返回页面不刷新的问题
    • 前端如何下载文件流
    • uniapp APP端实现更新最新安装包
    • GitHub Actions Process completed with exit code 128 的解决方案
    • 保留文字输入的空格和换行
    • 缩放适配大屏页
    • 一次性加载n多张图片的性能优化方案
    • NOT-Cool 低代码页面架构思路
    • h5下载vcard快捷保存联系人信息
    • 结合elementui实现的动态主题
    • 解决monorepo场景下子包作为依赖项在开发和生产暴露文件的问题
    • 给npm配置github令牌
    • 移动端电子印章解决方案
    • 小程序canvas绘制海报中遇到的一些坑
    • 超详细的虚拟列表实现过程
    • 小程序实现一个事件中心
    • 请求超时后如何优雅的重新请求
    • 超详细的大文件上传实现方案
    • 仿抖音短视频组件实现方案
    • swiper渲染大量数据的优化方案
    • 前端检测更新,自动刷新网页
      • 前言
      • 调研
        • 用户主动触发
        • 网页自动触发
      • 正文
        • 生成version.js文件
        • 插件代码
        • 插件使用
        • 轮询
      • 总结
  • Git

  • Vite

  • 一些小工具

  • 算法

  • 服务器

  • HTTP

  • 技术
  • 工作中遇到的问题以及解决方案
hanhanbuku
2025-06-09
目录

前端检测更新,自动刷新网页

# 前言

最近在开发过程中遇到了一个颇为头疼的问题,前端发布了新版本,但是用户侧没有感知。对于长时间停留在网页上的用户来说,他们操作的还是旧版本。这就会导致很多问题,发布了新功能用户不知道,修复了bug用户也不知道。只有当他刷新网页或者下一次重新打开网页才能加载到最新的系统。因此现在需要做一个自动检测更新并提示用户刷新页面的功能

# 调研

提示

在开发之前,首先在网上搜罗了一圈,总结如下: 大众所采用的方案大致分为两种

  1. 用户侧主动触发
  2. 网页自动触发

下面就来详细了解一下这两种方式

# 用户主动触发

用户侧主动触发指的是,用户切换了路由或者某些操作触发了接口调用时触发版本比对,具体操作:前端和服务端统一维护一个版本号,服务端在接口的响应体或者响应头里带上这个版本号,前端在每次请求接口的时候都去比对这个版本号,如果发现本版号有变化就提示用户

# 网页自动触发

网页自动触发大体上就是两种方案:

  • 服务端主动推送更新
    • 服务端通过ws或者sse主动告知客户端有版本变动,客户端做出处理
  • 客户端轮询
    • 客户端轮询服务器上的文件或者服务端的接口,通过比对差异做出相应的处理

上述的方案网上有很多示例,可自行啃食,本文主要讲述通过轮询的方式

# 正文

通过轮询的方式也有许多种实现方案,比较典型的有请求服务器html文件比对内容,亦或者不比对内容比对响应头上的缓存字段。由于比对这些都需要直接对服务器发起请求,轮询频率*用户基数会造成服务器产生一些不必要的开销。所以我决定生成一个版本文件用来记录当前版本信息,这个文件可以部署到服务器上 也可以放在oss或者cdn上,比较自由。如果服务器吃不消就放在oss上这样不影响我们的功能也不会对服务器造成什么影响。

# 生成version.js文件

由于项目的构建工具是webpack,所以这里需要写一个webpack插件用来生成version信息,用vite的项目也是同样的道理。

插件的代码也非常简单,在构建完毕输出产物之前生成一个version.js文件,并把它加入到构建产物中

# 插件代码

/**
 * 生成构建指纹 用来比对服务器上文件是否更新
 */

module.exports = {
  UploadPagePlugin: class UploadPagePlugin {
    constructor(props) {
      this.active = props || false
      console.log(props)
    }
    apply(compiler) {
      const _this = this
      console.log(compiler.options.mode, '插件获取到的环境')
      if (_this.active) {
        // 使用 emit 钩子,这个钩子会在生成资源到 output 目录之前执行
        compiler.hooks.emit.tapAsync('UploadPagePlugin', (compilation, callback) => {
          try {
            const sourceString = `export const version = ${new Date().getTime()}`
            compilation.assets['version.js'] = {
              source: () => sourceString,
              size: () => sourceString.length
            }
            callback()
          } catch (error) {
            console.error('UploadPagePlugin error:', error)
            callback()
          }
        })
      }
    }
  }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

# 插件使用

// vue.config.js

const plugin = require('./plugins/UploadPagePlugin.js')

module.exports = {
    chainWebpack(config){
        config.plugin('UploadPagePlugin').use(plugin.UploadPagePlugin, [process.env.NODE_ENV === 'staging'])
    }
}
1
2
3
4
5
6
7
8
9

插件比较简单,就是生成了一个文件,这里开放了个参数给使用者传递,来确认是否激活插件的功能。在使用的代码里我判断了只有测试环境才激活插件,这里的条件可自行更改。

接下来就是需要写一段轮询代码了

# 轮询

轮询的代码可简单可复杂,实现主要的功能点就行了。这里我写的比较复杂一点,支持配置化,对后续扩展比较友好



const axios = require('axios')

class VersionChecker {
  constructor(options = {}) {
    this.options = Object.assign(
      {
        versionUrl: '/version.js', // 版本文件路径
        checkInterval: 5 * 60 * 1000, // 默认5分钟检查一次
        onNewVersion: null // 发现新版本时的回调函数
      },
      options
    )
    
    this.currentVersion = null // 当前版本
    this.checkTimer = null // 轮询的定时器
    this.isFirstCheck = true // 是否是第一次查询版本
  }

  // 开始轮询检查
  startChecking() {
    // 立即执行一次检查
    this.checkVersion()
    
    // 设置定时检查
    this.checkTimer = setInterval(() => {
      this.checkVersion()
    }, this.options.checkInterval)
    
    // 当页面从隐藏变为可见时,也触发检查
    document.addEventListener('visibilitychange', () => {
      if (document.visibilityState === 'visible') {
        this.checkVersion()
      }
    })
  }

  // 停止轮询检查
  stopChecking() {
    if (this.checkTimer) {
      clearInterval(this.checkTimer)
      this.checkTimer = null
    }
  }

  // 检查版本
  async checkVersion() {
    try {
      // 添加时间戳防止缓存
      const timestamp = new Date().getTime()
      const response = await axios.get(`${this.options.versionUrl}?t=${timestamp}`)
      
      // 从响应中提取版本号
      let versionData = response.data
      
      // 如果返回的是字符串(如export const version = 1234567890),需要提取数字部分
      if (typeof versionData === 'string') {
        const match = versionData.match(/version\s*=\s*(\d+)/)
        if (match && match[1]) {
          versionData = match[1]
        }
      }
      
      // 首次检查,记录当前版本
      if (this.isFirstCheck) {
        this.currentVersion = versionData
        this.isFirstCheck = false
        console.log('初始版本:', this.currentVersion)
        return
      }
      
      // 比较版本
      if (versionData !== this.currentVersion) {
        console.log('检测到新版本:', versionData, '当前版本:', this.currentVersion)
        
        // 如果设置了回调,则调用
        if (typeof this.options.onNewVersion === 'function') {
          this.options.onNewVersion(versionData, this.currentVersion)
        }
        
        // 更新当前版本
        this.currentVersion = versionData
        // 发现新版本后结束轮询  
        this.stopChecking()  
      }
    } catch (error) {
      console.error('检查版本失败:', error)
    }
  }
}

export default VersionChecker
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93

这里我写了一个VersionChecker类,支持外部传入请求路径,轮询时间,以及发现新版本后的回调。整体的逻辑也很简单,就是 首次执行代码的时候会去请求一次远端的version文件,然后记录下来里面的内容,后续每个一段时间去请求这个version文件,并比对二者内容是否一致 然后调用实例化VersionChecker的时候传入的回调函数。

// main.js

// 导入版本检测器
import VersionChecker from '@/utils/versionChecker'
import { MessageBox } from 'bi-eleme'

// 创建版本检测器实例
const versionChecker = new VersionChecker({
  versionUrl: '/version.js', // 版本文件的URL
  checkInterval: 5 * 60 * 1000, // 5分钟检查一次
  onNewVersion: () => {
    // 当检测到新版本时,提示用户刷新页面
    MessageBox.confirm(
      '系统已更新到新版本,请刷新页面以获取最新功能和修复。',
      '发现新版本',
      {
        confirmButtonText: '立即刷新',
        cancelButtonText: '稍后刷新',
        type: 'warning'
      }
    ).then(() => {
      // 用户点击确认,刷新页面
      window.location.reload(true)
    }).catch(() => {
      // 用户点击取消,不做操作
    })
  }
})

// 启动版本检测
versionChecker.startChecking()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

至此我们的功能就已经完善了。

# 总结

实现此功能的方案有很多种,但是没有完美的方案,每一种都有弊端,例如通过用户主动触发这种方式是有滞后性的,如果用户长时间停留在网页上 不做任何操作是没法感知道版本有更新的。轮询的方式又会对性能造成一定的损耗,大家可自行选择合适的方案食用

编辑 (opens new window)
上次更新: 2025/06/09, 15:43:40
swiper渲染大量数据的优化方案
git提交信息规范-commitlint

← swiper渲染大量数据的优化方案 git提交信息规范-commitlint→

最近更新
01
swiper渲染大量数据的优化方案
06-06
02
仿抖音短视频组件实现方案
02-28
03
超详细的大文件上传实现方案
01-17
更多文章>
Theme by Vdoing | Copyright © 2023-2025 UzumakiItachi | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式