# 软件包在线升级功能设计与实现方案 ## 需求整理 ### 核心功能需求 1. **程序启动时版本检查**:应用初始化完成后,自动检查是否有新版本可用 2. **新版本提示**:检测到新版本时,显示用户友好的提示对话框 3. **用户选择机制**:允许用户选择是否立即更新、稍后提醒或跳过此版本 4. **下载进度显示**:用户确认更新后,显示实时下载进度条 5. **自动安装**:下载完成后自动触发安装过程 6. **状态反馈**:在整个过程中提供清晰的状态反馈和错误处理 ### 详细需求点 - **触发时机**:在 `initializeApp()` 函数执行完成后触发版本检查 - **检查频率**:每次应用启动时检查,避免过于频繁的网络请求 - **版本比较**:比较当前版本与最新版本,支持语义化版本号比较 - **用户体验**:非阻塞式提示,不干扰正常业务流程 - **错误处理**:网络失败、服务器错误等情况的优雅处理 - **国际化支持**:所有提示文本支持多语言 ## 参与者分析(从粗到细) ### 1. UI层组件 - **VersionUpdateModal**:新版本提示对话框 - 显示新版本信息(版本号、更新内容、包大小) - 提供"立即更新"、"稍后提醒"、"跳过此版本"按钮 - **DownloadProgressModal**:下载进度对话框 - 实时进度条显示 - 下载速度、剩余时间显示 - 取消下载按钮 ### 2. 状态管理层 - **versionUpdateSlice**:Redux slice管理更新相关状态 - `currentVersion`:当前版本 - `latestVersion`:最新版本信息 - `downloadProgress`:下载进度 - `updateStatus`:更新状态(idle, checking, available, downloading, installing, completed, error) ### 3. API服务层 - **versionUpdateAPI**:版本更新相关API调用 - `checkVersion()`:调用 `/api/v1/auth/settings/package/version` 获取版本信息 - `downloadPackage(version)`:调用 `/api/v1/auth/settings/package/download` 下载安装包 - `installPackage(version)`:调用 `/api/v1/auth/settings/package/install` 安装包 ### 4. 业务逻辑层 - **VersionUpdateService**:核心业务逻辑 - 版本比较逻辑 - 下载进度跟踪 - 安装流程管理 - 错误处理和重试机制 ### 5. 工具层 - **versionUtils**:版本号处理工具 - 语义化版本比较 - 版本格式验证 - **mqttUtils**:MQTT消息处理工具 - MQTT连接管理 - 消息订阅/取消订阅 - 消息解析和分发 ## TodoList ### 创建/修改的文件和方法 #### 新增文件 1. `src/states/versionUpdateSlice.ts` - Redux状态管理 2. `src/API/versionUpdate.ts` - API调用封装 3. `src/services/VersionUpdateService.ts` - 业务逻辑服务 4. `src/components/VersionUpdateModal.tsx` - 版本更新提示组件 5. `src/components/DownloadProgressModal.tsx` - 下载进度组件 6. `src/utils/versionUtils.ts` - 版本处理工具 7. `src/utils/mqttUtils.ts` - MQTT消息处理工具 #### 修改现有文件 1. `src/pages/index/index.tsx` - 在服务器连接检查之后、登录之前添加版本检查调用 2. `src/app.tsx` - 在initializeApp()中添加版本检查调用(作为备用检查) 3. `src/pages/system/SettingsModal/sections/SystemHome/index.tsx` - 添加版本信息显示和手动检查更新按钮 4. `src/types/global.d.ts` - 添加版本更新相关类型定义 #### 新增方法 1. `VersionUpdateService.checkForUpdates()` - 检查更新主流程 2. `VersionUpdateService.startDownload()` - 调用下载API并订阅进度 3. `VersionUpdateService.installUpdate()` - 调用安装API 4. `VersionUpdateService.handleMqttDownloadMessage()` - 处理MQTT下载进度消息 5. `versionUtils.compareVersions()` - 版本比较 6. `mqttUtils.subscribeDownloadProgress()` - 订阅下载进度MQTT消息 7. `mqttUtils.unsubscribeDownloadProgress()` - 取消订阅下载进度 8. `mqttUtils.getClient()` - 获取MQTT连接客户端 ## 📊 生成 Mermaid 图表 ### 序列图 - 组件交互泳道图 ```mermaid sequenceDiagram participant App as 应用启动 participant Init as initializeApp() participant VU as VersionUpdateService participant API as versionUpdateAPI participant MQTT as MQTT消息 participant Modal as VersionUpdateModal participant Progress as DownloadProgressModal participant Redux as versionUpdateSlice App->>Init: 应用启动 Init->>VU: checkForUpdates() VU->>API: checkVersion() API-->>VU: 版本信息响应 VU->>VU: 比较版本 VU->>Redux: 更新状态 (newVersionAvailable) VU->>Modal: 显示更新提示 Modal->>Modal: 用户选择"立即更新" Modal->>VU: startDownload() VU->>API: downloadPackage() VU->>VU: 订阅MQTT下载进度 VU->>Progress: 显示下载进度 loop 下载中 MQTT->>VU: MODULE/TASK/DOWNLOAD/PACKAGE 消息 VU->>VU: 解析MQTT消息 VU->>Redux: 更新进度状态 VU->>Progress: 更新进度条 end VU->>VU: 下载完成/失败 VU->>VU: 取消MQTT订阅 VU->>API: installPackage() API-->>VU: 安装完成 VU->>Redux: 更新状态 (completed) VU->>Modal: 显示安装完成提示 ``` ### 流程图 - 版本更新流程 ```mermaid flowchart TD A[应用启动] --> B[initializeApp完成] B --> C[checkForUpdates] C --> D{API调用成功?} D -->|否| E[错误处理] D -->|是| F[获取版本信息] F --> G{有新版本?} G -->|否| H[结束] G -->|是| I[显示VersionUpdateModal] I --> J{用户选择} J -->|立即更新| K[显示DownloadProgressModal] J -->|稍后提醒| L[记录提醒时间] J -->|跳过版本| M[记录跳过版本] K --> N[startDownload] N --> O[API下载包] O --> P{下载进度更新} P --> Q{下载完成?} Q -->|否| P Q -->|是| R[installPackage] R --> S{安装成功?} S -->|是| T[显示成功提示] S -->|否| U[显示错误提示] L --> V[下次启动检查] M --> W[版本升级时检查] ``` ### 类图 - 核心类结构 ```mermaid classDiagram class VersionUpdateService { +checkForUpdates() +startDownload(version) +installUpdate(version) -compareVersions(current, latest) -handleError(error) } class versionUpdateAPI { +checkVersion() +downloadPackage(version) +installPackage(version) } class versionUpdateSlice { +currentVersion +latestVersion +downloadProgress +updateStatus +setUpdateStatus() +setDownloadProgress() } class VersionUpdateModal { +versionInfo +onUpdate() +onSkip() +onRemindLater() } class DownloadProgressModal { +progress +onCancel() } VersionUpdateService --> versionUpdateAPI VersionUpdateService --> versionUpdateSlice VersionUpdateModal --> VersionUpdateService DownloadProgressModal --> VersionUpdateService ``` ## 数据流 1. **初始化阶段**:`initializeApp()` → `VersionUpdateService.checkForUpdates()` → API调用获取版本信息 → 状态更新 2. **用户交互阶段**:Modal显示 → 用户选择 → `startDownload()` → 下载进度更新 → `installUpdate()` → 完成 3. **状态流转**:Redux状态驱动UI更新 → 业务逻辑响应用户操作 → API调用 → 状态更新循环 ## 相关数据结构 ### 版本信息数据结构 ```typescript interface VersionInfo { name: string; version_pulled: boolean; new_version_available: boolean; current_version: string; newest_version: string; newest_package_size: number; newest_time: string; newest_md5_sum: string; } interface DownloadProgress { loaded: number; total: number; speed: number; // bytes per second eta: number; // estimated time of arrival in seconds percentage: number; // 下载进度百分比 (0.0 - 1.0) timeSpent: number; // 已用时(秒) status: 'IN_PROGRESS' | 'SUCCESS' | 'FAILURE'; // 下载状态 message: string; // 失败原因 } interface MqttDownloadMessage { name: string; // 包名,目前为 "backend" content_length: number; // 包总大小(bytes) download_size: number; // 已下载大小(bytes) percentage: number; // 下载进度百分比 (0.0 - 1.0) speed: number; // 平均速度(MB/s) time_spent: number; // 已用时(秒) status: 'IN_PROGRESS' | 'SUCCESS' | 'FAILURE'; // 下载状态 msg: string; // 失败原因 } interface UpdateState { status: 'idle' | 'checking' | 'available' | 'downloading' | 'installing' | 'completed' | 'error'; versionInfo: VersionInfo | null; downloadProgress: DownloadProgress | null; error: string | null; lastChecked: Date | null; skippedVersions: string[]; // 用户跳过的版本列表 remindLaterTime: Date | null; // 稍后提醒的时间戳 userChoice: 'none' | 'update_now' | 'remind_later' | 'skip_version'; // 用户的选择记录 } ``` ## 执行流程 ### 起点:应用启动 1. 用户启动应用程序 2. `index.tsx` 检查登录状态(现有实现): - 使用 `isLoggedIn(userInfo)` 检查登录状态 - 未登录 → 显示登录页面 - 已登录 → 继续到应用初始化 3. 服务器连接配置检查: - 需要配置 → 显示服务器配置对话框 - 配置完成 → 显示登录页面 4. 用户登录成功 → **执行版本检查** 5. 版本检查完成后 → 执行应用初始化 6. 初始化完成 → 显示主界面 ### 时序关系 **版本更新检查在用户登录成功后、应用初始化前执行** 完整的时序流程: 1. 应用启动 → 检查登录状态 2. 未登录 → 检查服务器连接配置 3. 服务器配置正常 → 显示登录页面 4. 用户登录 → **执行版本检查**(此时已登录) 5. 版本检查完成 → 执行应用初始化(产品状态、i18n等) 6. 应用初始化完成 → 显示主界面 ### 设计优势 - **用户体验优化**:只有登录用户才会看到版本更新提示 - **权限控制**:避免未登录用户看到敏感的更新信息 - **网络检查复用**:利用现有的服务器连接检查逻辑 ### 版本更新详细流程: 1. `initializeApp()` 完成 → `checkForUpdates()` → API获取版本信息 2. 版本比较 → 如果有新版本 → 显示 `VersionUpdateModal` 3. 用户选择更新 → 显示 `DownloadProgressModal` → 开始下载 4. 下载进度实时更新 → 下载完成 → 自动安装 5. 安装完成 → 显示成功提示 → 可选重启应用 ## 用户选择执行逻辑 ### 三种用户选择的详细逻辑 #### 1. 立即更新 (Update Now) **触发条件**:用户点击"立即更新"按钮 **执行逻辑**: 1. **状态更新**:设置 `userChoice = 'update_now'` 2. **关闭提示窗口**:隐藏 `VersionUpdateModal` 3. **显示下载窗口**:打开 `DownloadProgressModal` 4. **开始下载**: - 调用 `VersionUpdateService.startDownload()` - 调用API `/api/v1/auth/settings/package/download` - 实时更新下载进度到Redux状态 - 显示进度条、速度、剩余时间 5. **下载完成处理**: - 验证文件MD5完整性 - 调用API `/api/v1/auth/settings/package/install` 安装 - 显示安装进度 6. **安装完成**: - 显示成功提示 - 可选:自动重启应用或提示用户手动重启 7. **异常处理**: - 下载失败:显示重试/取消选项 - 安装失败:显示错误信息,提供解决方案 #### 2. 稍后提醒 (Remind Later) **触发条件**:用户点击"稍后提醒"按钮 **执行逻辑**: 1. **状态更新**: - 设置 `userChoice = 'remind_later'` - 设置 `remindLaterTime = Date.now() + REMIND_INTERVAL` (例如24小时后) 2. **关闭提示窗口**:隐藏 `VersionUpdateModal` 3. **记录用户选择**:将提醒时间持久化存储(localStorage或配置文件) 4. **下次启动检查逻辑**: - 启动时检查 `remindLaterTime` - 如果当前时间 >= `remindLaterTime`,则重新显示更新提示 - 如果当前时间 < `remindLaterTime`,则跳过此次更新检查 5. **特殊情况**: - 如果有更新的版本发布,会覆盖之前的"稍后提醒"设置 - 用户可以通过设置页面手动触发检查更新 #### 3. 跳过此版本 (Skip This Version) **触发条件**:用户点击"跳过此版本"按钮 **执行逻辑**: 1. **状态更新**: - 设置 `userChoice = 'skip_version'` - 将当前 `newest_version` 添加到 `skippedVersions` 数组 2. **关闭提示窗口**:隐藏 `VersionUpdateModal` 3. **持久化存储**:将跳过的版本列表保存到本地存储 4. **后续检查逻辑**: - 检查新版本时,先比对是否在 `skippedVersions` 中 - 如果是已跳过的版本,不显示更新提示 - 只有当有更新的版本号时,才重新显示提示 5. **版本升级规则**: - 跳过的是具体版本号,不是版本范围 - 例如:跳过 v1.8.8,当 v1.8.9 或 v1.9.0 发布时仍会提示 6. **用户重置选项**: - 在设置页面提供"重置跳过版本"选项 - 允许用户清除 `skippedVersions` 列表 ### 用户选择的数据流 ```mermaid flowchart TD A[显示VersionUpdateModal] --> B{用户选择} B -->|立即更新| C[设置userChoice='update_now'] B -->|稍后提醒| D[设置userChoice='remind_later'] B -->|跳过版本| E[设置userChoice='skip_version'] C --> F[关闭Modal] D --> F E --> F F --> G[开始下载流程] G --> H{下载成功?} H -->|是| I[自动安装] H -->|否| J[显示错误] I --> K{安装成功?} K -->|是| L[显示成功提示] K -->|否| M[显示安装失败] D --> N[记录remindLaterTime] N --> O[下次启动检查] E --> P[添加到skippedVersions] P --> Q[新版本发布时检查] ``` ### 存储策略 #### 本地存储设计: ```typescript interface PersistentUpdateState { skippedVersions: string[]; // ["1.8.8", "1.9.0"] remindLaterTime: number | null; // timestamp lastUserChoice: 'update_now' | 'remind_later' | 'skip_version' | null; lastCheckedVersion: string; // 上次检查的版本号 } ``` #### 存储位置: - **Web环境**:localStorage - **Electron环境**:用户配置目录的配置文件 - **自动清理**:定期清理过期的跳过版本记录 ### 用户体验考虑 #### 立即更新: - **优势**:及时获得最新功能和安全补丁 - **用户预期**:快速、无缝的升级体验 - **反馈机制**:详细的进度反馈,避免用户焦虑 #### 稍后提醒: - **适用场景**:用户当前有重要工作在进行 - **提醒频率**:避免过于频繁,默认24小时后提醒 - **智能调整**:如果用户多次选择"稍后提醒",可以适当延长提醒间隔 #### 跳过版本: - **适用场景**:用户对当前版本满意,或新版本有已知问题 - **版本控制**:精确到具体版本号,提供灵活性 - **信息透明**:在设置页面显示已跳过的版本列表 ## MQTT下载进度消息处理 ### MQTT消息格式和字段说明 **Topic**: `MODULE/TASK/DOWNLOAD/PACKAGE` **消息示例**: ```json { "name": "backend", "content_length": 156024824, "download_size": 56024824, "percentage": 0.56, "speed": 1.16, "time_spent": 120, "status": "IN_PROGRESS", "msg": "" } ``` **字段详细说明**: - `name`: 包名称,目前固定为 "backend" - `content_length`: 包总大小(字节) - `download_size`: 已下载大小(字节) - `percentage`: 下载进度百分比(0.0-1.0) - `speed`: 平均下载速度(MB/s) - `time_spent`: 已用时(秒) - `status`: 下载状态 - `"IN_PROGRESS"`: 下载中 - `"SUCCESS"`: 下载成功 - `"FAILURE"`: 下载失败 - `msg`: 失败原因(当status为"FAILURE"时填充) ### MQTT消息处理流程 #### 1. 订阅阶段 **触发时机**:用户选择"立即更新"并开始下载时 **处理逻辑**: ```typescript // VersionUpdateService.startDownload() const startDownload = async (version: string) => { try { // 1. 调用下载API await versionUpdateAPI.downloadPackage(version); // 2. 订阅MQTT消息 mqttUtils.subscribeDownloadProgress( 'MODULE/TASK/DOWNLOAD/PACKAGE', handleMqttDownloadMessage ); // 3. 显示下载进度窗口 dispatch(setUpdateStatus('downloading')); } catch (error) { handleError(error); } }; ``` #### 2. 消息接收处理 **消息处理器**: `VersionUpdateService.handleMqttDownloadMessage()` **处理逻辑**: ```typescript const handleMqttDownloadMessage = (message: MqttDownloadMessage) => { // 1. 转换数据格式 const progress: DownloadProgress = { loaded: message.download_size, total: message.content_length, speed: message.speed * 1024 * 1024, // MB/s 转换为 bytes/s percentage: message.percentage, timeSpent: message.time_spent, status: message.status, message: message.msg, eta: calculateETA(message) // 计算剩余时间 }; // 2. 更新Redux状态 dispatch(updateDownloadProgress(progress)); // 3. 处理不同状态 switch (message.status) { case 'IN_PROGRESS': // 继续显示进度 break; case 'SUCCESS': // 下载完成,准备安装 handleDownloadComplete(); break; case 'FAILURE': // 下载失败,显示错误 handleDownloadError(message.msg); break; } }; ``` #### 3. 取消订阅阶段 **触发时机**:下载完成或失败时 **处理逻辑**: ```typescript const handleDownloadComplete = () => { // 1. 取消MQTT订阅 mqttUtils.unsubscribeDownloadProgress('MODULE/TASK/DOWNLOAD/PACKAGE'); // 2. 更新状态为安装中 dispatch(setUpdateStatus('installing')); // 3. 开始安装流程 installUpdate(); }; const handleDownloadError = (errorMessage: string) => { // 1. 取消MQTT订阅 mqttUtils.unsubscribeDownloadProgress('MODULE/TASK/DOWNLOAD/PACKAGE'); // 2. 更新错误状态 dispatch(setUpdateError(errorMessage)); dispatch(setUpdateStatus('error')); }; ``` ### MQTT工具类实现 #### 工具类设计 ```typescript // src/utils/mqttUtils.ts import { getMqttBrokerUrl } from '../API/config'; // MQTT客户端类型(需要安装mqtt库) import mqtt, { MqttClient, IClientOptions } from 'mqtt'; class MqttUtils { private client: MqttClient | null = null; private subscriptions: Map void> = new Map(); private isConnecting: boolean = false; // 获取或创建MQTT连接 private async getClient(): Promise { if (this.client && this.client.connected) { return this.client; } if (this.isConnecting) { // 等待连接完成 return new Promise((resolve, reject) => { const checkConnection = () => { if (this.client && this.client.connected) { resolve(this.client); } else if (!this.isConnecting) { reject(new Error('MQTT连接失败')); } else { setTimeout(checkConnection, 100); } }; checkConnection(); }); } this.isConnecting = true; try { const brokerUrl = getMqttBrokerUrl(); const options: IClientOptions = { clientId: `version-update-${Date.now()}`, clean: true, reconnectPeriod: 5000, // 重连间隔 connectTimeout: 10000, // 连接超时 }; this.client = mqtt.connect(brokerUrl, options); return new Promise((resolve, reject) => { this.client!.on('connect', () => { console.log('MQTT连接成功'); this.isConnecting = false; resolve(this.client!); }); this.client!.on('error', (error) => { console.error('MQTT连接失败:', error); this.isConnecting = false; reject(error); }); this.client!.on('message', (topic, message) => { this.handleIncomingMessage(topic, message); }); }); } catch (error) { this.isConnecting = false; throw error; } } // 订阅下载进度消息 async subscribeDownloadProgress(topic: string, callback: (message: MqttDownloadMessage) => void) { try { const client = await this.getClient(); this.subscriptions.set(topic, callback); await client.subscribeAsync(topic, { qos: 1 }); console.log(`已订阅MQTT主题: ${topic}`); } catch (error) { console.error('MQTT订阅失败:', error); throw error; } } // 取消订阅下载进度消息 async unsubscribeDownloadProgress(topic: string) { try { if (this.client && this.client.connected) { await this.client.unsubscribeAsync(topic); this.subscriptions.delete(topic); console.log(`已取消订阅MQTT主题: ${topic}`); } } catch (error) { console.error('MQTT取消订阅失败:', error); } } // 处理接收到的MQTT消息 private handleIncomingMessage(topic: string, message: Buffer) { const callback = this.subscriptions.get(topic); if (callback) { try { // 解析MQTT消息并调用回调 const parsedMessage = JSON.parse(message.toString()); callback(parsedMessage); } catch (error) { console.error('MQTT消息解析失败:', error); } } } // 断开连接(可选,用于清理资源) disconnect() { if (this.client) { this.client.end(); this.client = null; this.subscriptions.clear(); } } } export const mqttUtils = new MqttUtils(); ``` **说明**: - **连接管理**:根据 `config.ts` 中的 `getMqttBrokerUrl()` 获取MQTT broker地址 - **懒加载连接**:在首次订阅时创建MQTT连接,避免不必要的连接开销 - **自动重连**:配置了重连机制,提高连接稳定性 - **资源管理**:提供disconnect方法用于清理资源 ### 异常情况处理 #### MQTT连接断开 - **检测**:监听MQTT连接状态变化 - **处理**:显示连接错误,允许用户重试或取消 #### 消息格式异常 - **检测**:消息解析失败或字段缺失 - **处理**:记录错误日志,使用默认进度显示 #### 消息顺序错乱 - **检测**:接收到percentage小于当前进度的新消息 - **处理**:忽略旧消息,只接受进度递增的消息 #### 长时间无消息 - **检测**:超过设定时间(例如30秒)未收到进度消息 - **处理**:显示"连接超时",提供重试选项 ### 测试建议 #### MQTT消息模拟测试 1. **Mock MQTT消息**:在测试环境中模拟不同状态的消息 2. **边界值测试**: - percentage: 0.0, 0.5, 0.99, 1.0 - speed: 0, 0.1, 1.0, 10.0 - status: "IN_PROGRESS", "SUCCESS", "FAILURE" #### 异常情况测试 1. **消息延迟测试**:模拟网络延迟,验证UI响应 2. **消息丢失测试**:模拟消息丢失,验证超时处理 3. **连接断开测试**:模拟MQTT连接断开,验证重连逻辑 ### 涉及的文件 1. `src/utils/mqttUtils.ts` - MQTT工具类(新增) 2. `src/services/VersionUpdateService.ts` - 添加MQTT消息处理方法 3. `src/states/versionUpdateSlice.ts` - 添加进度更新action 4. `src/components/DownloadProgressModal.tsx` - 更新UI显示逻辑 ## 🧪 测试方案 ### E2E测试准备工作 #### 1. 创建版本更新Mock Handlers 创建 `cypress/support/mock/handlers/versionUpdate.ts`: ```typescript /** * 版本更新相关Mock Handlers */ // 检查版本 - 有新版本 export function mockCheckVersionHasUpdate() { cy.intercept('GET', '/dr/api/v1/auth/settings/package/version', { statusCode: 200, body: { code: "0x000000", data: { name: "backend", version_pulled: true, new_version_available: true, current_version: "1.14.1", newest_version: "1.15.0", newest_package_size: 104857600, // 100MB newest_time: "2025-12-18T07:00:00Z", newest_md5_sum: "abc123..." } } }).as('checkVersionHasUpdate'); } // 检查版本 - 无新版本 export function mockCheckVersionNoUpdate() { cy.intercept('GET', '/dr/api/v1/auth/settings/package/version', { statusCode: 200, body: { code: "0x000000", data: { new_version_available: false, current_version: "1.14.1" } } }).as('checkVersionNoUpdate'); } // 下载包API export function mockDownloadPackage() { cy.intercept('POST', '/dr/api/v1/auth/settings/package/download', { statusCode: 200, body: { code: "0x000000", data: {} } }).as('downloadPackage'); } // 安装包API export function mockInstallPackage() { cy.intercept('POST', '/dr/api/v1/auth/settings/package/install', { statusCode: 200, body: { code: "0x000000", data: {} } }).as('installPackage'); } ``` #### 2. 扩展Page Object 在 `cypress/support/pageObjects/LoginPage.ts` 中添加版本更新相关方法: ```typescript // 版本更新弹窗相关方法 getVersionUpdateModal() { return cy.get('[data-testid="version-update-modal"]'); } getUpdateNowButton() { return cy.get('[data-testid="update-now-btn"]'); } getRemindLaterButton() { return cy.get('[data-testid="remind-later-btn"]'); } getSkipVersionButton() { return cy.get('[data-testid="skip-version-btn"]'); } // 下载进度弹窗相关方法 getDownloadProgressModal() { return cy.get('[data-testid="download-progress-modal"]'); } getDownloadProgressBar() { return cy.get('[data-testid="download-progress-bar"]'); } getDownloadCancelButton() { return cy.get('[data-testid="download-cancel-btn"]'); } ``` #### 3. 创建版本更新测试用例 创建 `cypress/e2e/versionUpdate.cy.ts`: ```typescript import { mockLoginSuccess } from '../support/mock/handlers/user'; import { mockCheckVersionHasUpdate, mockCheckVersionNoUpdate, mockDownloadPackage, mockInstallPackage } from '../support/mock/handlers/versionUpdate'; import LoginPage from '../support/pageObjects/LoginPage'; describe('Version Update', () => { const loginPage = new LoginPage(); beforeEach(() => { mockLoginSuccess(); }); it('should check for updates after login', () => { mockCheckVersionHasUpdate(); // 登录流程 loginPage.visit(); loginPage.login('admin', '123456'); cy.wait('@loginSuccess'); // 等待版本检查完成 cy.wait('@checkVersionHasUpdate'); // 验证版本更新弹窗出现 loginPage.getVersionUpdateModal().should('be.visible'); }); it('should start download when user clicks update now', () => { mockCheckVersionHasUpdate(); mockDownloadPackage(); // 登录并等待版本检查 loginPage.visit(); loginPage.login('admin', '123456'); cy.wait('@loginSuccess'); cy.wait('@checkVersionHasUpdate'); // 点击立即更新 loginPage.getUpdateNowButton().click(); // 验证下载弹窗出现 loginPage.getDownloadProgressModal().should('be.visible'); // 验证API调用 cy.wait('@downloadPackage'); }); it('should skip version when user chooses to skip', () => { mockCheckVersionHasUpdate(); loginPage.visit(); loginPage.login('admin', '123456'); cy.wait('@loginSuccess'); cy.wait('@checkVersionHasUpdate'); // 点击跳过版本 loginPage.getSkipVersionButton().click(); // 验证弹窗关闭 loginPage.getVersionUpdateModal().should('not.exist'); }); it('should not show update modal when no new version available', () => { mockCheckVersionNoUpdate(); loginPage.visit(); loginPage.login('admin', '123456'); cy.wait('@loginSuccess'); cy.wait('@checkVersionNoUpdate'); // 验证没有版本更新弹窗 loginPage.getVersionUpdateModal().should('not.exist'); }); }); ``` #### 4. 运行测试环境准备 **启动测试环境**: ```bash # 启动开发服务器 npm run h5:electron # 或者启动浏览器版本用于调试 npm run h5:browser # 运行e2e测试 npm run e2e ``` #### 5. 测试数据隔离 - 使用独立的测试数据库/配置 - 确保版本更新测试不会影响其他测试 - 清理测试后的状态数据 ### 功能测试场景 1. **登录后自动触发版本检查** - 前置条件:用户成功登录 - 操作步骤: 1. 用户登录成功 2. 等待版本检查API调用 3. 观察控制台日志确认版本检查执行 - 预期结果:版本检查在登录后自动执行 2. **正常更新流程** - 前置条件:服务器有新版本可用,用户已登录 - 操作步骤: 1. 用户登录成功 2. 等待版本检查完成 3. 确认新版本提示对话框出现 4. 点击"立即更新" 5. 观察下载进度条正确显示 6. 等待下载和安装完成 - 预期结果:更新成功,显示完成提示 3. **用户选择跳过更新** - 前置条件:有新版本可用,用户已登录 - 操作步骤: 1. 用户登录看到更新提示 2. 点击"跳过此版本" - 预期结果:对话框关闭,不再提示此版本更新 4. **稍后提醒** - 前置条件:有新版本可用,用户已登录 - 操作步骤: 1. 点击"稍后提醒" 2. 重启应用并重新登录 - 预期结果:下次启动时仍显示更新提示 5. **无新版本时不显示弹窗** - 前置条件:当前版本已是最新,用户已登录 - 操作步骤: 1. 用户登录成功 2. 等待版本检查完成 - 预期结果:不显示版本更新提示弹窗 ### 异常情况测试 1. **版本检查失败** - 前置条件:服务器返回错误或网络连接失败 - 操作:用户登录触发版本检查 - 预期:显示错误提示,但不影响应用正常启动 2. **下载中断** - 前置条件:下载过程中断开网络 - 操作:点击取消下载或等待超时 - 预期:下载停止,显示错误提示,可重试 3. **安装失败** - 前置条件:模拟安装权限不足 - 操作:完成下载后安装 - 预期:显示安装失败提示,提供解决方案 ### 性能测试 1. **启动时间影响** - 测量:应用启动时间对比(有/无版本检查) - 预期:版本检查不明显增加启动时间 2. **内存占用** - 测量:更新过程中的内存使用情况 - 预期:内存使用在合理范围内 ### 兼容性测试 1. **不同操作系统** - Windows 10/11 - 预期:功能正常工作 2. **不同网络环境** - 快网、慢网、无网 - 预期:相应错误处理 ## 🐛 潜在问题分析 ### 边界情况和异常处理 1. **版本号格式异常** - 问题:服务器返回非标准版本号格式 - 解决:添加版本号格式验证,异常时跳过比较 2. **并发更新检查** - 问题:多次快速触发版本检查 - 解决:添加防抖机制,限制检查频率 3. **下载文件损坏** - 问题:下载的文件MD5不匹配 - 解决:下载完成后验证文件完整性,失败则重新下载 4. **安装权限不足** - 问题:用户没有管理员权限 - 解决:检测权限,提示用户提升权限或联系管理员 5. **磁盘空间不足** - 问题:下载目录磁盘空间不足 - 解决:下载前检查可用空间,不足则提示用户清理 6. **网络超时** - 问题:网络请求超时 - 解决:设置合理的超时时间,提供重试机制 7. **应用重启失败** - 问题:安装完成后自动重启失败 - 解决:提示用户手动重启,提供重启按钮 ### 安全考虑 1. **文件来源验证**:验证下载文件的数字签名 2. **HTTPS通信**:确保所有API调用使用HTTPS 3. **权限最小化**:只在需要时请求管理员权限 ### 用户体验优化 1. **非阻塞提示**:更新提示不干扰正常工作流程 2. **进度反馈**:详细的进度信息和预计时间 3. **可取消操作**:允许用户随时取消下载 4. **错误恢复**:提供重试和跳过选项 ### 监控和日志 1. **操作日志**:记录所有更新相关操作 2. **错误上报**:收集错误信息用于问题分析 3. **使用统计**:统计更新成功率和用户行为