目录
- 一、方案描述
-
- 1.1 轮询检查版本文件
- 1.2 基于SSE的推送方案
- 1.3 WebSocket主动推送
- 二、建议说明
一、方案描述
1.1 轮询检查版本文件
版本文件 (version.json):
{
"version": "1.0.5",
"buildTime": "2024-01-15 10:30:00",
"force": false
}
前端轮询实现:
class VersionChecker {
constructor(options = {}) {
this.versionFile = options.versionFile || '/version.json';
this.interval = options.interval || 30000; // 30秒
this.currentVersion = null;
this.checkCount = 0;
this.init();
}
async init() {
await this.getCurrentVersion();
this.startPolling();
}
async getCurrentVersion() {
try {
// 添加时间戳防止缓存
const response = await fetch(`${this.versionFile}?t=${Date.now()}`);
const data = await response.json();
if (!this.currentVersion) {
this.currentVersion = data.version;
} else if (this.currentVersion !== data.version) {
this.showUpdateNotification(data);
}
return data;
} catch (error) {
console.error('Version check failed:', error);
}
}
startPolling() {
setInterval(() => {
this.getCurrentVersion();
}, this.interval);
}
showUpdateNotification(data) {
// 添加防抖,避免重复通知
if (this.notificationShown) return;
this.notificationShown = true;
const modal = document.createElement('div');
modal.style.cssText = `
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 10000;
`;
modal.innerHTML = `
<div style="
background: white;
padding: 32px;
border-radius: 12px;
max-width: 400px;
text-align: center;
">
<h3 style="margin-top: 0; color: #333;">📦 发现新版本</h3>
<p style="color: #666; margin-bottom: 24px;">
当前版本: ${this.currentVersion}<br>
最新版本: ${data.version}<br>
更新于: ${data.buildTime}
</p>
<div style="display: flex; gap: 12px; justify-content: center;">
<button onclick="location.reload()" style="
background: #4CAF50;
color: white;
border: none;
padding: 10px 24px;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
">立即刷新</button>
<button onclick="this.closest('div').remove()" style="
background: #f5f5f5;
border: 1px solid #ddd;
padding: 10px 24px;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
">稍后提醒</button>
</div>
</div>
`;
document.body.appendChild(modal);
}
}
// 使用
new VersionChecker({
versionFile: '/version.json',
interval: 60000 // 每分钟检查一次
});
优点
-
实现简单,无需特殊服务端
-
可与CI/CD流程集成
-
可控性强,可自定义检查频率
-
资源消耗可控
缺点
-
实时性较差,存在延迟窗口
-
频繁请求可能造成服务器压力
-
需要处理HTTP缓存问题
-
无法精确控制所有客户端
1.2 基于SSE的推送方案
后端(Node.js + Express):
const express = require('express');
const app = express();
let version = '1.0.0';
let clients = [];
app.get('/events', (req, res) => {
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
const clientId = Date.now();
const newClient = {
id: clientId,
res
};
clients.push(newClient);
// 发送当前版本
res.write(`event: version\\n`);
res.write(`data: ${JSON.stringify({ version })}\\n\\n`);
req.on('close', () => {
clients = clients.filter(client => client.id !== clientId);
});
});
// 更新版本
app.post('/update', (req, res) => {
version = `1.0.${Date.now()}`;
// 广播给所有客户端
clients.forEach(client => {
client.res.write(`event: update\\n`);
client.res.write(`data: ${JSON.stringify({
version,
time: new Date().toISOString(),
force: false
})}\\n\\n`);
});
res.json({ success: true });
});
app.listen(3001);
前端实现:
class SSEChecker {
constructor(options = {}) {
this.eventSource = null;
this.url = options.url || 'http://localhost:3001/events';
this.onUpdate = options.onUpdate || this.defaultHandler;
this.currentVersion = null;
this.init();
}
init() {
if (!window.EventSource) {
console.warn('SSE not supported');
return;
}
this.eventSource = new EventSource(this.url);
this.eventSource.addEventListener('version', (e) => {
const data = JSON.parse(e.data);
this.currentVersion = data.version;
console.log('Current version:', this.currentVersion);
});
this.eventSource.addEventListener('update', (e) => {
const data = JSON.parse(e.data);
this.onUpdate(data);
});
this.eventSource.onerror = (error) => {
console.error('SSE connection error:', error);
// 重连逻辑
setTimeout(() => {
this.init();
}, 5000);
};
}
defaultHandler(data) {
const banner = document.createElement('div');
banner.style.cssText = `
position: fixed;
top: 0;
left: 0;
right: 0;
background: #ff9800;
color: white;
padding: 12px 24px;
text-align: center;
z-index: 10000;
display: flex;
justify-content: center;
align-items: center;
gap: 20px;
`;
banner.innerHTML = `
<span>✨ 发现新版本 (${data.version}) | 更新时间: ${new Date(data.time).toLocaleTimeString()}</span>
<button onclick="location.reload()" style="
background: white;
color: #ff9800;
border: none;
padding: 6px 20px;
border-radius: 4px;
cursor: pointer;
font-weight: bold;
">立即刷新</button>
<button onclick="this.parentElement.remove()" style="
background: transparent;
color: white;
border: 1px solid white;
padding: 6px 20px;
border-radius: 4px;
cursor: pointer;
">忽略</button>
`;
document.body.prepend(banner);
}
close() {
if (this.eventSource) {
this.eventSource.close();
}
}
}
// 使用
const checker = new SSEChecker({
url: 'http://localhost:3001/events',
onUpdate: (data) => {
// 自定义处理逻辑
}
});
优点
-
实现简单,比WebSocket轻量
-
浏览器原生支持
-
自动重连机制
-
适合单向通知场景
缺点
-
仅支持单向通信
-
连接数限制(浏览器限制)
-
需要维护连接状态
-
老旧浏览器不支持
1.3 WebSocket主动推送
后端(Node.js + Socket.io):
const express = require('express');
const http = require('http');
const socketIo = require('socket.io');
const app = express();
const server = http.createServer(app);
const io = socketIo(server, {
cors: {
origin: "*",
methods: ["GET", "POST"]
}
});
// 存储当前版本号
let currentVersion = '1.0.0';
const clients = new Set();
io.on('connection', (socket) => {
clients.add(socket);
// 发送当前版本给新连接的客户端
socket.emit('version', { version: currentVersion });
socket.on('disconnect', () => {
clients.delete(socket);
});
});
// 当代码更新时调用此函数
function notifyUpdate() {
currentVersion = `1.0.${Date.now()}`;
clients.forEach(socket => {
socket.emit('update', {
version: currentVersion,
message: '系统已更新,请刷新页面'
});
});
}
server.listen(3000, () => {
console.log('WebSocket server running on port 3000');
});
前端:
class UpdateNotifier {
constructor(options = {}) {
this.socket = null;
this.currentVersion = null;
this.onUpdateCallback = options.onUpdate || this.defaultUpdateHandler;
this.init();
}
init() {
this.socket = io('http://localhost:3000');
this.socket.on('version', (data) => {
this.currentVersion = data.version;
});
this.socket.on('update', (data) => {
this.onUpdateCallback(data);
});
}
defaultUpdateHandler(data) {
const notification = document.createElement('div');
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: #2196F3;
color: white;
padding: 16px 24px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
z-index: 9999;
animation: slideIn 0.3s ease;
font-family: system-ui;
`;
notification.innerHTML = `
<div style="display: flex; align-items: center; gap: 12px;">
<span>🔔 ${data.message || '发现新版本'}</span>
<button onclick="location.reload()" style="
background: white;
color: #2196F3;
border: none;
padding: 6px 16px;
border-radius: 4px;
cursor: pointer;
font-weight: 600;
">立即刷新</button>
<button onclick="this.parentElement.parentElement.remove()" style="
background: transparent;
color: white;
border: 1px solid white;
padding: 6px 16px;
border-radius: 4px;
cursor: pointer;
">稍后</button>
</div>
`;
document.body.appendChild(notification);
setTimeout(() => {
notification.remove();
}, 10000);
}
}
// 使用
new UpdateNotifier({
onUpdate: (data) => {
console.log('New version available:', data.version);
// 自定义更新处理逻辑
}
});
优点
-
实时性强,毫秒级通知
-
双向通信,可获取客户端状态
-
用户体验最佳,无延迟
-
可针对不同客户端差异化推送
缺点
-
需要维护WebSocket服务
-
服务器资源消耗较大
-
实现复杂度较高
-
需要处理断线重连
二、建议说明
最佳实践建议
-
分层策略: 主方案+备方案,如WebSocket断线时切换到轮询
-
用户体验优先:
非强制更新:提供"稍后提醒"选项
强制更新:重要安全更新,直接弹窗
静默更新:后台自动刷新(需保存用户状态)
版本管理:
// 版本对比逻辑增强
function compareVersions(oldVer, newVer) {
if (newVer.force) return 'force'; // 强制更新
if (majorUpdate(oldVer, newVer)) return 'major'; // 大版本
if (minorUpdate(oldVer, newVer)) return 'minor'; // 小版本
if (patchUpdate(oldVer, newVer)) return 'patch'; // 补丁
return 'none';
}
关键原则:
-
不要让用户感知到版本更新的复杂性
-
提供明确的操作指引和反馈
-
保护用户当前的操作状态
-
控制更新提醒频率,避免打扰
-
做好降级处理,确保基本功能可用
实施建议:
-
从简单的轮询方案开始
-
根据用户反馈和业务需求逐步优化
-
做好A/B测试,选择最适合的方案
-
监控版本更新成功率,持续优化


