update-version.js
#!/usr/bin/env node
/**
* @FileDesc: 部署前自动更新版本号脚本
* @Usage: 在package.json的build脚本中调用
* 例如: "build": "node scripts/update-version.js && vite build"
*/
#!/usr/bin/env node
/**
* @FileDesc: 部署前自动更新版本号脚本
* @Usage: 在package.json的build脚本中调用
* 例如: "build": "node scripts/update-version.js && vite build"
*/
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs"
import { resolve, join } from "path"
import { fileURLToPath } from "url"
// 获取当前文件的目录
const __filename = fileURLToPath(import.meta.url)
const __dirname = resolve(__filename, "..")
try {
// 获取项目根目录
const projectRoot = resolve(__dirname, "../")
const packageJsonPath = join(projectRoot, "package.json")
const versionJsonPath = join(projectRoot, "public/version.json")
// 确保 public 目录存在
const publicDir = join(projectRoot, "public")
if (!existsSync(publicDir)) {
mkdirSync(publicDir, { recursive: true })
}
// 从 package.json 获取版本号
const packageJsonContent = readFileSync(packageJsonPath, "utf-8")
const packageJson = JSON.parse(packageJsonContent)
const version = packageJson.version || "1.0.0"
// 获取当前时间(ISO 8601格式)
const buildTime = new Date().toISOString()
// 创建 version.json 内容
const versionInfo = {
version,
buildTime
}
// 写入 version.json 文件
writeFileSync(versionJsonPath, JSON.stringify(versionInfo, null, 2))
console.log("✅ 版本文件已更新:")
console.log(` 版本号: ${version}`)
console.log(` 构建时间: ${buildTime}`)
} catch (error) {
console.error("❌ 版本文件更新失败:", error.message)
process.exit(1)
}
version.ts
/*
* @FileDesc: 版本管理和更新检测工具
*/
import { Toast } from "antd-mobile"
import { Dialog } from "antd-mobile"
/** 声明全局变量 */
declare const __BUILD_TIME__: string | undefined
/** 版本检查相关常量 */
export const VERSION_CHECK_CONFIG = {
// 版本检查的localStorage key - 存储 buildTime,用于检测更新
STORAGE_KEY_VERSION: "APP_BUILD_TIME",
// 版本检查间隔(毫秒),5分钟检查一次
CHECK_INTERVAL: 5 * 60 * 1000,
// 版本API接口
VERSION_URL: `${import.meta.env.VITE_BASE_PATH || "/"}version.json`
}
/** 版本信息接口 */
export interface IVersionInfo {
version: string
buildTime: string
}
/**
* 获取当前应用版本
*/
export const getCurrentVersion = (): string => {
const buildTime = typeof __BUILD_TIME__ !== "undefined" ? __BUILD_TIME__ : ""
return buildTime || localStorage.getItem(VERSION_CHECK_CONFIG.STORAGE_KEY_VERSION) || "0.0.0"
}
/**
* 获取远端版本信息
*/
export const fetchRemoteVersion = async (): Promise<IVersionInfo | null> => {
try {
const response = await fetch(VERSION_CHECK_CONFIG.VERSION_URL, {
cache: "no-cache"
})
if (!response.ok) {
return null
}
const data: IVersionInfo = await response.json()
return data
} catch (error) {
console.error("获取远端版本失败:", error)
return null
}
}
/**
* 清除所有缓存和本地数据(除了版本信息)
*/
export const clearAllCaches = async () => {
try {
// ⚠️ 关键:保存版本信息,防止清除后丢失
const versionBuildTime = localStorage.getItem(VERSION_CHECK_CONFIG.STORAGE_KEY_VERSION)
// 清除Service Worker缓存
if ("caches" in window) {
const cacheNames = await caches.keys()
await Promise.all(cacheNames.map(cacheName => caches.delete(cacheName)))
}
// 清除localStorage和sessionStorage
localStorage.clear()
sessionStorage.clear()
// 如果存在IndexedDB,也清除掉
if ("indexedDB" in window) {
const databases = await indexedDB.databases()
databases.forEach(db => {
if (db.name) {
indexedDB.deleteDatabase(db.name)
}
})
}
// 恢复版本信息,防止重复检测
if (versionBuildTime) {
localStorage.setItem(VERSION_CHECK_CONFIG.STORAGE_KEY_VERSION, versionBuildTime)
}
} catch (error) {
console.error("清除缓存失败:", error)
}
}
/**
* 清除登录状态
*/
export const clearLoginState = async () => {
try {
// 清除与登录相关的localStorage数据
const loginRelatedKeys = ["xczzH5Token", "applyStatus"]
loginRelatedKeys.forEach(key => {
localStorage.removeItem(key)
sessionStorage.removeItem(key)
})
} catch (error) {
console.error("清除登录状态失败:", error)
}
}
/**
* 处理应用更新
*/
export const handleAppUpdate = async (newBuildTime?: string) => {
// 显示更新提示对话框
Dialog.show({
title: "发现新版本",
content: "应用已更新,请重新登录以获取最新功能",
closeOnAction: true,
actions: [
{
key: "confirm",
text: "重新登录",
onClick: async () => {
const toastHandler = Toast.show({
content: "正在清除缓存...",
duration: 0
})
// 先更新版本号,再清除缓存
if (newBuildTime) {
localStorage.setItem(VERSION_CHECK_CONFIG.STORAGE_KEY_VERSION, newBuildTime)
}
// 清除所有缓存和登录状态
await clearAllCaches()
await clearLoginState()
if (toastHandler) {
toastHandler.close()
}
// 重定向到登录页
window.location.href = `${import.meta.env.VITE_BASE_PATH || "/"}login`
}
}
]
})
}
/**
* 检查应用版本并处理更新
* ⚠️ 关键改进:
* 1. 添加去重标志,防止重复弹窗
* 2. 添加详细日志追踪
* 3. 添加异常处理
*/
export const checkAndHandleUpdate = async () => {
try {
// 获取本地记录的构建时间
const storedBuildTime = localStorage.getItem(VERSION_CHECK_CONFIG.STORAGE_KEY_VERSION)
// 获取远端版本
const remoteVersion = await fetchRemoteVersion()
if (!remoteVersion) {
console.warn("⚠️ 无法获取远端版本信息")
return
}
// 添加详细日志
console.log("📊 版本检查详情:", {
"远端 buildTime": remoteVersion.buildTime,
"本地 buildTime": storedBuildTime || "未记录(首次访问)",
版本号: remoteVersion.version,
需要更新: !!storedBuildTime && storedBuildTime !== remoteVersion.buildTime
})
if (storedBuildTime && storedBuildTime !== remoteVersion.buildTime) {
console.log(`🔄 检测到版本更新!`)
console.log(` 旧版本: ${storedBuildTime}`)
console.log(` 新版本: ${remoteVersion.buildTime}`)
// 传递新的 buildTime,在用户确认后更新
await handleAppUpdate(remoteVersion.buildTime)
} else if (!storedBuildTime) {
// 第一次访问,记录当前版本
console.log(`📝 首次访问,记录 buildTime: ${remoteVersion.buildTime}`)
localStorage.setItem(VERSION_CHECK_CONFIG.STORAGE_KEY_VERSION, remoteVersion.buildTime)
} else {
console.log("✅ 版本无更新,应用为最新版本")
}
} catch (error) {
console.error("❌ 版本检查出错:", error)
}
}
/**
* 启动版本检查(定时检查)
*/
export const startVersionCheck = () => {
console.log("🚀 版本检查已启动")
console.log(`📋 检查间隔: ${VERSION_CHECK_CONFIG.CHECK_INTERVAL / 1000} 秒`)
console.log(`🔗 版本 API: ${VERSION_CHECK_CONFIG.VERSION_URL}`)
// 立即检查一次
checkAndHandleUpdate()
// 定时检查
const intervalId = setInterval(() => {
console.log(`⏰ [${new Date().toLocaleTimeString()}] 执行定时版本检查...`)
checkAndHandleUpdate()
}, VERSION_CHECK_CONFIG.CHECK_INTERVAL)
// 暴露到全局作用域,方便调试
if (typeof window !== "undefined") {
;(window as any).__versionCheckUtils__ = {
// 手动检查版本
checkNow: () => {
console.log("🔍 手动触发版本检查...")
checkAndHandleUpdate()
},
// 清空版本记录,下次访问会重新记录
resetVersion: () => {
localStorage.removeItem(VERSION_CHECK_CONFIG.STORAGE_KEY_VERSION)
console.log("✅ 版本记录已清空,刷新页面后会重新记录")
},
// 查看当前版本信息
getVersionInfo: () => {
return {
"本地 buildTime": localStorage.getItem(VERSION_CHECK_CONFIG.STORAGE_KEY_VERSION),
"存储 key": VERSION_CHECK_CONFIG.STORAGE_KEY_VERSION,
"API 地址": VERSION_CHECK_CONFIG.VERSION_URL,
"检查间隔(秒)": VERSION_CHECK_CONFIG.CHECK_INTERVAL / 1000
}
},
// 获取远端版本
getRemoteVersion: async () => {
const version = await fetchRemoteVersion()
console.log("🌐 远端版本:", version)
return version
},
// 测试清除缓存
testClearCache: async () => {
console.log("🧹 测试清除缓存...")
await clearAllCaches()
console.log("✅ 缓存清除完成")
},
// 间隔 ID
intervalId
}
console.log("💡 调试工具已加载,可在控制台使用 window.__versionCheckUtils__")
console.log(" - checkNow() 手动检查版本")
console.log(" - resetVersion() 清空版本记录")
console.log(" - getVersionInfo() 查看版本信息")
console.log(" - getRemoteVersion()获取远端版本")
console.log(" - testClearCache() 测试清除缓存")
}
}
版本更新与缓存清除
🎯 核心功能
1. 自动版本检测
- 应用启动时自动检查
/version.json - 每5分钟定时检查一次
- 比对构建时间(buildTime)检测新版本
2. 缓存清除
- Service Worker 缓存
- localStorage / sessionStorage
- IndexedDB 数据库
- 所有登录相关数据
3. 强制重新登录
- 检测到新版本时显示"发现新版本"提示
- 点击"重新登录"清除缓存并跳转到登录页
- 用户重新登录后获取最新版本代码
📁 文件结构
src/
├── utils/
│ └── version.ts # 核心版本检查工具
├── hooks/
│ └── update/
│ └── useVersionCheck.ts # 版本检查 Hook
├── stores/
│ └── userInfo.ts # 已集成 logout 方法(清除缓存)
└── App.tsx # 已集成 useVersionCheck Hook
public/
└── version.json # 版本信息文件(自动生成)
scripts/
├── update-version.js # 自动更新版本脚本
├── update-version.sh # Linux/Mac 版本脚本
└── update-version.bat # Windows 版本脚本
package.json # build 脚本
🚀 使用方式
1. 开发环境
pnpm start
2. 构建项目
# 自动更新版本并构建
pnpm build
# 测试云构建
pnpm build:testcloud
# 生产云构建
pnpm build:prodcloud
3. 手动更新版本(可选)
pnpm update-version
🔄 工作流程
1. 执行 pnpm build
↓
2. 运行 update-version.js 脚本
↓
3. 读取 package.json 中的版本号
↓
4. 生成当前构建时间(ISO 8601)
↓
5. 生成/更新 public/version.json
↓
6. Vite 构建应用
↓
7. 部署到服务器
↓
8. 用户访问应用
↓
9. useVersionCheck Hook 启动
↓
10. 定时检查 /version.json
↓
11. 版本不同 → 显示更新提示 → 清除缓存 → 重新登录
🛠️ 部署配置
Nginx 配置(重要)
必须禁用 version.json 的缓存,否则更新检测无法生效:
# 版本检查文件 - 禁用缓存
location /version.json {
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache";
add_header Expires "0";
}
# 其他资源 - 长期缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# HTML 文件
location ~* \.html$ {
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
📊 版本信息文件格式
public/version.json 内容示例:
{
"version": "1.4.3",
"buildTime": "2026-01-27T10:30:00Z"
}
自动生成说明
version:从package.json中读取buildTime:构建时的当前时间(自动生成)
🧪 测试版本更新
本地测试步骤
-
修改版本号
# 编辑 package.json,改变 version 字段 # 例如:1.4.3 -> 1.4.4 -
重新构建
pnpm build pnpm preview -
验证功能
- 打开应用
- 打开浏览器开发者工具
- 编辑
public/version.json,改变 buildTime - 刷新页面
- 应该会弹出"发现新版本"提示
🔍 调试
查看版本信息
在浏览器控制台执行:
// 获取远端版本
fetch("/version.json")
.then(r => r.json())
.then(data => console.log("远端版本:", data))
// 检查本地版本记录
console.log("本地版本:", localStorage.getItem("APP_VERSION"))
查看缓存
// Service Worker 缓存
caches.keys().then(names => console.log("缓存列表:", names))
// localStorage
console.log("localStorage:", localStorage)
// sessionStorage
console.log("sessionStorage:", sessionStorage)
手动清除缓存
import { clearAllCaches, clearLoginState } from "@/utils/version"
// 清除所有缓存
await clearAllCaches()
console.log("缓存已清除")
// 清除登录状态
await clearLoginState()
console.log("登录状态已清除")
⚙️ 配置调整
修改检查间隔
编辑 src/utils/version.ts:
export const VERSION_CHECK_CONFIG = {
// 改为 10 分钟检查一次
CHECK_INTERVAL: 10 * 60 * 1000
// ...其他配置
}
禁用版本检查
在 src/App.tsx 中注释掉:
// useVersionCheck() // 注释掉这行
自定义更新提示
编辑 src/utils/version.ts 中的 handleAppUpdate() 函数
📚 更多资源
详细部署指南请查看 DEPLOYMENT_GUIDE.md
💡 关键要点
✅ 一定要做的事:
- 在 Nginx/Apache 中禁用 version.json 的缓存
- 确保 Service Worker 正常注册
- 每次部署前运行
pnpm build(会自动更新版本)
❌ 不要做的事:
- 手动修改 public/version.json(会被覆盖)
- 启用 version.json 的缓存
- 在部署前忘记构建项目
📞 常见问题
Q: 用户更新后仍看到旧版本? A: 检查 Nginx 缓存配置,确保禁用了 version.json 的缓存
Q: 版本文件未生成? A: 检查是否有写入 public 目录的权限
Q: 如何强制所有用户更新? A: 修改 version.json 中的 buildTime 字段