写在开头

点赞 + 收藏 === 学会🤣🤣🤣

为什么需要版本检测

1. 解决浏览器缓存问题

  • 静态资源缓存:浏览器会缓存 JS、CSS 等静态资源,用户可能继续使用旧版本
  • 用户体验影响:用户无法及时获取新功能,导致功能缺失或操作异常

2. 保障功能一致性

  • 功能同步:确保所有用户都能使用最新的功能和修复
  • 数据一致性:避免因版本差异导致的数据不一致问题

3. 提升用户体验

  • 主动提醒:在新版本发布后主动通知用户更新
  • 无缝升级:减少用户手动刷新页面的需求

版本检测核心思路

整体架构

构建阶段 → 版本文件生成 → 运行时检测 → 版本对比 → 用户提醒

技术实现要点

1. 版本标识生成
  • 构建时生成:每次打包时生成唯一的版本标识
  • 时间戳方案:使用时间戳确保每次构建版本号唯一
2. 版本文件部署
  • JSON 格式:将版本信息保存为 version.json 文件
  • 静态访问:通过 HTTP 请求可直接访问版本文件
3. 客户端检测机制
  • 定时轮询:定期检查服务器版本文件
  • 版本对比:比较本地缓存版本与服务器版本
  • 智能提醒:仅在版本不一致时提醒用户

版本检测实现步骤

步骤一:构建版本文件生成脚本

创建 build-version.js 文件:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

// build-version.js (自动生成版本文件脚本)

const fs = require('fs')

const path = require('path')

// 方案A:使用时间戳作为版本标识(最简单,确保每次打包唯一)

const version = new Date().getTime().toString()

// 版本文件内容

const versionJson = {

  version: version,

  updateTime: new Date().toLocaleString() // 可选:添加更新时间,便于排查

}

// 写入version.json文件(项目根目录)

const versionPath = path.resolve(__dirname, 'public''version.json')

fs.writeFileSync(versionPath, JSON.stringify(versionJson, null, 2), 'utf-8')

console.log(`✅ 自动生成版本文件成功,版本号:${version}`)

步骤二:修改构建命令

在 package.json 中修改构建命令:

1

2

3

4

5

{

  "scripts": {

    "build:prod""node build-version.js && vue-cli-service build"

  }

}

步骤三:配置 Vue 构建过程

在 vue.config.js 中添加版本文件复制配置:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

chainWebpack(config) {

  // ... 其他配置

   

  // 复制 version.json 到 dist 目录

  config.plugin('copy')

    .tap(args => {

      const hasVersionJson = args[0].some(item => item.from === 'version.json')

      if (!hasVersionJson) {

        args[0].push({

          from: path.resolve(__dirname, 'public/version.json'),

          to: path.resolve(__dirname, 'dist/version.json')

        })

      }

      return args

    })

}

步骤四:实现版本检测工具类

创建 src/utils/versionUpdate.js

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

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

// src/utils/versionUpdate.js

import { Notification } from 'element-ui'

/**

 * 版本更新检测工具类(仅生产环境启用轮询,内置环境判断)

 */

class VersionUpdate {

  constructor(options = {}) {

    this.config = {

      versionFileUrl: '/version.json'// 版本文件地址

      localVersionKey: 'cmpVersion'// 本地存储的版本号key

      disableFetchCache: true// 禁用Fetch缓存

      pollInterval: 5 * 60 * 1000, // 5分钟轮询一次

      hasNotified: false // 是否已提醒过用户有新版本

    }

    Object.assign(this.config, options)

    // 定时轮询定时器

    this.pollTimer = null

    // 识别当前环境(Vue CLI 4 自动注入的环境变量)

    this.isProduction = process.env.NODE_ENV === 'production'

  }

  /**

   * 核心方法:执行版本检测

   */

  async checkVersion(isInit = false) {

    try {

      if (this.config.hasNotified) return false

      const localVersion = localStorage.getItem(this.config.localVersionKey) || ''

      const fetchOptions = {}

      if (this.config.disableFetchCache) {

        fetchOptions.cache = 'no-cache'

      }

      const response = await fetch(this.config.versionFileUrl, fetchOptions)

      if (!response.ok) {

        throw new Error(`版本文件请求失败,状态码:${response.status}`)

      }

      const latestVersionInfo = await response.json()

      const serverVersion = latestVersionInfo.version

      if (isInit) {

        this.cacheLatestVersion(serverVersion)

        return true

      }

      if (serverVersion && serverVersion !== localVersion) {

        this.config.hasNotified = true

        console.log('有新版本可用', latestVersionInfo)

        Notification({

          title: '🎉 有新版本可用',

          dangerouslyUseHTMLString: true,

          message: `<p style="font-size:12px;">建议点击刷新页面,以获取最新功能和修复</p> <p style="color:#cccccc;font-size:12px;">更新时间:${latestVersionInfo.updateTime}</p>`,

          duration: 0,

          customClass: 'check-version-notify',

          onClick: () => {

            this.forceRefreshPage()

          },

          onClose: () => {

            this.resetNotifyFlag()

          }

        })

        return true

      else {

        // 版本一致时,重置提醒标记,便于后续轮询检测新版本

        this.config.hasNotified = false

        // console.log('当前已是最新版本,已缓存最新版本号')

        return false

      }

    catch (error) {

      console.warn('版本检测异常,不影响应用运行:', error.message)

      return false

    }

  }

  /**

   * 启动定时轮询检测(内置环境判断:仅生产环境生效)

   */

  async startPolling() {

    // 核心:非生产环境,直接返回,不启动轮询

    if (!this.isProduction) {

      console.log('当前为非生产环境,不启动版本检测轮询')

      return

    }

    // 生产环境:正常启动轮询

    this.stopPolling() // 先停止已有轮询,避免重复启动

    this.checkVersion(true// 立即执行一次检测

    this.pollTimer = setInterval(() => {

      this.checkVersion()

    }, this.config.pollInterval)

    console.log(`生产环境版本轮询检测已启动,每隔${this.config.pollInterval / 1000 / 60}分钟检测一次`)

  }

  /**

   * 停止定时轮询检测

   */

  stopPolling() {

    if (this.pollTimer) {

      clearInterval(this.pollTimer)

      this.pollTimer = null

      console.log('版本轮询检测已停止')

    }

  }

  /**

   * 重置提醒标记

   */

  resetNotifyFlag() {

    this.config.hasNotified = false

  }

  // 缓存最新版本号

  cacheLatestVersion(version) {

    localStorage.setItem(this.config.localVersionKey, version)

    this.resetNotifyFlag()

  }

  // 强制刷新页面

  forceRefreshPage() {

    window.location.reload(true)

  }

}

const versionUpdateInstance = new VersionUpdate()

export { VersionUpdate, versionUpdateInstance }

export default versionUpdateInstance

创建自定义.check-version-notify的版本检测全局样式:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

// 版本检测通知样式

.check-version-notify{

  border: 3px solid transparent !important;

  cursor: pointer;

  background-color: rgba(255, 255, 255, 0.6) !important;

  backdrop-filter: blur(5px);

  &:hover{

    border: 3px solid $--color-primary !important;

  }

  .el-notification__icon{

    font-size: 18px;

    height: 18px;

  }

  .el-notification__title{

    font-size: 14px;

    line-height: 18px;

  }

  .el-notification__group{

    margin-left: 8px;

  }

}

步骤五:在应用入口启动版本检测

在 App.vue 或合适的入口文件中启动版本检测:

1

2

3

4

5

6

7

8

import versionUpdate from '@/utils/versionUpdate'

...

mounted() {

  versionUpdate.startPolling()

},

beforeDestroy() {

  versionUpdate.stopPolling()

}

更多推荐