Back to Prompt Optimizer

桌面端应用发布与智能更新系统 - 技术实现详解

docs/archives/118-desktop-auto-update-system/implementation.md

2.10.212.4 KB
Original Source

桌面端应用发布与智能更新系统 - 技术实现详解

1. 总体设计目标

构建一个专业、跨平台、用户体验优先的桌面应用更新系统。系统应为非侵入式,将完整的控制权交给用户,同时确保更新流程的稳定性和数据的安全性。


2. 打包与发布策略 (CI/CD)

目标: 自动化构建支持自动更新的安装包和供高级用户使用的便携包,并将其发布到 GitHub Releases。

  • 涉及文件:
    • packages/desktop/package.json
    • .github/workflows/release.yml

2.1. 构建配置 (package.json)

  1. 核心依赖: 添加 electron-updaterdependencies
  2. 更新源配置: 在 build 节点下,添加 publish 配置,指向项目的 GitHub 仓库(提供 ownerrepo)。
  3. 多目标构建:
    • win.target: 设置为 ['nsis', 'zip'],同时生成 Windows 安装包和便携包。
    • mac.target: 设置为 ['dmg', 'zip'],同时生成 macOS 安装包和便携包。
    • linux.target: 设置为 ['AppImage', 'zip'],同时生成 Linux 安装包和便携包。

2.2. 自动化工作流 (release.yml)

  1. 上传所有产物: 在 build-windows, build-macos, build-linux 这三个 job 中,修改 actions/upload-artifact 步骤,确保上传所有生成的文件(如 *.exe, *.dmg, *.AppImage, *.zip, *.yml),而不仅仅是 .zip
  2. 发布所有产物: 在最终的 create-release job 中,修改 softprops/action-gh-releasefiles 参数,使用通配符(如 artifacts/**/*)将所有下载的 artifact 文件附加到 GitHub Release 中。

3. 核心更新逻辑 (主进程)

目标: 编写健壮的主进程逻辑,作为整个交互式更新流程的后端引擎。

  • 涉及文件: packages/desktop/main.js

3.1. checkUpdate 异步函数

  1. 读取持久化设置: 在函数开始时,从 PreferenceService 异步读取 updater.allowPrereleaseupdater.ignoredVersion 的值。
  2. 配置更新器:
    • 根据读取到的偏好设置 autoUpdater.allowPrerelease
    • 必须设置 autoUpdater.autoDownload = false,将下载控制权交给用户。
  3. 处理 update-available 事件:
    • 智能忽略: 在回调函数第一行,进行判断:if (info.version === ignoredVersion) return;。如果发现的版本是用户忽略过的,则提前终止流程。
    • 构建详情链接: 根据 package.json 中的 publish 配置和 info.version,动态构建出指向 GitHub Release 页面的 releaseUrl
    • 发送通知: 通过 IPC (update-available-info) 将包含版本信息和 releaseUrl 的对象发送给 UI 层。

3.2. IPC 处理器

  1. start-download-update: 调用 autoUpdater.downloadUpdate(),开始下载更新。
  2. install-update: 调用 autoUpdater.quitAndInstall(),安装更新并重启应用。
  3. ignore-update: 接收版本号参数,将其保存到 PreferenceServiceupdater.ignoredVersion 中。
  4. open-external-link: 接收 URL 参数,使用 shell.openExternal() 在用户的默认浏览器中打开链接。

4. UI 层交互设计

目标: 设计一个简洁、直观的用户界面,让用户能够轻松控制更新流程。

  • 涉及文件:
    • packages/ui/src/composables/useUpdater.ts
    • packages/ui/src/components/UpdaterIcon.vue
    • packages/ui/src/components/UpdaterModal.vue

4.1. useUpdater Composable

  1. 状态管理: 定义 hasUpdate, updateInfo, downloadProgress, isDownloading, isDownloaded, allowPrerelease 等响应式状态。
  2. IPC 通信: 封装与主进程的 IPC 通信,提供 checkUpdate, startDownload, installUpdate, ignoreUpdate, togglePrerelease 等方法。
  3. 事件监听: 监听主进程发送的 update-available-info, update-download-progress, update-downloaded 事件,并更新相应的状态。

4.2. UpdaterIcon 组件

  1. 条件渲染: 仅在 Electron 环境中显示,使用 isRunningInElectron() 进行环境检测。
  2. 状态指示: 根据 hasUpdate 状态显示更新提示(如小红点)。
  3. 点击交互: 点击图标弹出 UpdaterModal 组件。

4.3. UpdaterModal 组件

  1. 多状态视图:
    • 默认状态: 显示当前版本,提供"检查更新"按钮。
    • 更新可用: 显示新版本信息,提供"下载"、"查看详情"、"忽略"按钮。
    • 下载中: 显示下载进度条。
    • 下载完成: 提供"安装并重启"按钮。
  2. 用户控制: 提供预览版开关,让用户选择是否接收预览版更新。

5. 多形态产品兼容性

目标: 确保更新功能仅在桌面环境中可见,对 Web 和 Extension 环境完全透明。

5.1. 环境检测

使用 @prompt-optimizer/core 包中的 isRunningInElectron() 函数进行环境检测:

typescript
import { isRunningInElectron } from '@prompt-optimizer/core'

// 仅在 Electron 环境中显示更新组件
<div v-if="isRunningInElectron()">
  <UpdaterIcon />
</div>

5.2. 条件渲染策略

  1. 组件级别: 在 UpdaterIcon 组件内部进行环境检测,非 Electron 环境直接返回空。
  2. Composable 级别: 在 useUpdater 中提供空实现,保持 API 一致性。
  3. 集成级别: 在 App.vue 中条件性地包含更新组件。

6. 安全性考虑

6.1. 外部链接安全

open-external-link IPC 处理器中,验证 URL 的协议,仅允许 http://https:// 链接:

javascript
if (!url.startsWith('http://') && !url.startsWith('https://')) {
  throw new Error('Only HTTP and HTTPS URLs are allowed');
}

6.2. 版本验证

对接收到的版本号进行格式验证,防止恶意输入:

javascript
const versionRegex = /^v?\d+\.\d+\.\d+(-[\w.-]+)?(\+[\w.-]+)?$/;
if (!versionRegex.test(version)) {
  throw new Error('Invalid version format');
}

6.3. 配置安全

使用配置文件管理敏感信息,避免硬编码:

javascript
const { buildReleaseUrl, validateVersion } = require('./config/update-config');

7. 错误处理与恢复

7.1. 网络错误处理

  1. 超时机制: 为所有网络请求设置合理的超时时间。
  2. 重试策略: 允许用户手动重试失败的操作。
  3. 降级处理: 在服务不可用时提供基本功能。

7.2. 状态恢复

  1. 智能重置: 根据用户操作上下文决定状态重置策略。
  2. 错误边界: 在关键操作周围设置错误边界。
  3. 状态锁: 使用状态锁防止并发操作导致的状态混乱。

8. 性能优化

8.1. 事件监听器管理

  1. 生命周期管理: 在组件挂载时注册监听器,卸载时清理。
  2. 避免重复注册: 确保事件监听器只在应用启动时注册一次。
  3. 内存泄漏防护: 正确清理所有事件监听器。

8.2. 状态更新优化

  1. 批量更新: 合并相关的状态更新操作。
  2. 条件更新: 只在状态真正改变时触发更新。
  3. 异步处理: 使用异步操作避免阻塞 UI。

9. 测试策略

9.1. 多环境测试

  1. Web 环境: 验证更新组件不显示。
  2. Desktop 环境: 验证完整的更新流程。
  3. 构建测试: 验证多平台构建产物。

9.2. 边缘情况测试

  1. 网络中断: 测试下载过程中的网络异常。
  2. 并发操作: 测试用户快速重复操作的场景。
  3. 错误恢复: 测试各种异常情况的恢复机制。

10. 部署与维护

10.1. 发布流程

  1. 版本标记: 使用语义化版本号。
  2. 自动构建: 通过 CI/CD 自动构建和发布。
  3. 质量检查: 发布前进行完整的质量验证。

10.2. 监控与维护

  1. 更新成功率: 监控更新操作的成功率。
  2. 错误日志: 收集和分析错误日志。
  3. 用户反馈: 建立用户反馈机制。

11. 总结

本技术方案实现了一个完整、安全、用户友好的桌面应用自动更新系统。通过多形态产品兼容性设计,确保了更新功能仅在需要的环境中可见。通过完善的错误处理和状态管理,保证了系统的稳定性和可靠性。

12. 深度重构技术实现

12.1. 错误处理机制重构

详细错误响应函数

javascript
function createDetailedErrorResponse(error) {
  const timestamp = new Date().toISOString();
  let detailedMessage = `[${timestamp}] Error Details:\n\n`;

  if (error instanceof Error) {
    detailedMessage += `Message: ${error.message}\n`;
    if (error.code) detailedMessage += `Code: ${error.code}\n`;
    if (error.statusCode) detailedMessage += `HTTP Status: ${error.statusCode}\n`;
    if (error.url) detailedMessage += `URL: ${error.url}\n`;
    if (error.stack) detailedMessage += `\nStack Trace:\n${error.stack}\n`;

    // 捕获其他属性和JSON兜底机制
    const jsonError = JSON.stringify(error, Object.getOwnPropertyNames(error), 2);
    if (jsonError && jsonError !== '{}') {
      detailedMessage += `\nComplete Object Dump:\n${jsonError}`;
    }
  }

  return { success: false, error: detailedMessage };
}

preload.js 错误信息保留

javascript
// 修复前:丢失详细信息
if (!result.success) {
  throw new Error(result.error);
}

// 修复后:保留完整信息
if (!result.success) {
  const error = new Error(result.error);
  error.originalError = result.error;
  error.detailedMessage = result.error;
  throw error;
}

12.2. 组件架构重构

智能组件设计

vue
<!-- UpdaterModal.vue - 智能组件 -->
<script setup lang="ts">
// 内部管理所有更新逻辑
const {
  state,
  checkUpdate,
  startDownload,
  installUpdate,
  ignoreUpdate,
  togglePrerelease,
  openReleaseUrl
} = useUpdater()

// 简化的接口
interface Props {
  modelValue: boolean
}

const emit = defineEmits<{
  'update:modelValue': [value: boolean]
}>()
</script>

简化组件设计

vue
<!-- UpdaterIcon.vue - 简化组件 -->
<script setup lang="ts">
// 只获取状态用于图标显示
const { state } = useUpdater()

// 只管理模态框显示
const showModal = ref(false)
</script>

<template>
  <!-- 极简调用 -->
  <UpdaterModal v-model="showModal" />
</template>

12.3. 开发环境智能处理

环境检测逻辑

javascript
// 开发模式下的更新检查配置
if (process.env.NODE_ENV === 'development' || !app.isPackaged) {
  const fs = require('fs');
  const devConfigPath = path.join(__dirname, 'dev-app-update.yml');
  if (fs.existsSync(devConfigPath)) {
    autoUpdater.forceDevUpdateConfig = true;
  } else {
    // 返回友好的开发环境提示
    responseData.message = 'Development environment: Update checking is disabled';
    return createSuccessResponse(responseData);
  }
}

12.4. 状态管理系统

状态类型定义

typescript
interface UpdaterState {
  lastCheckResult: 'none' | 'available' | 'not-available' | 'error' | 'dev-disabled'
  // ... 其他状态
}

状态转换逻辑

javascript
if (checkData.hasUpdate && checkData.checkResult?.updateInfo) {
  state.lastCheckResult = 'available'
} else if (checkData.remoteVersion && !checkData.hasUpdate) {
  state.lastCheckResult = 'not-available'
} else if (checkData.message?.includes('Development environment')) {
  state.lastCheckResult = 'dev-disabled'
} else {
  state.lastCheckResult = 'error'
}

12.5. 动态UI实现

根据状态显示不同按钮

vue
<template #footer>
  <!-- 开发环境:只显示关闭按钮 -->
  <div v-if="state.lastCheckResult === 'dev-disabled'">
    <button @click="$emit('update:modelValue', false)">关闭</button>
  </div>

  <!-- 默认状态:关闭 + 立即检查 -->
  <div v-else-if="!state.hasUpdate && !state.isCheckingUpdate">
    <button @click="$emit('update:modelValue', false)">关闭</button>
    <button @click="handleCheckUpdate">立即检查</button>
  </div>

  <!-- 有更新:多个操作按钮 -->
  <div v-else-if="state.hasUpdate">
    <button @click="handleStartDownload">下载更新</button>
  </div>
</template>

关键特性:

  • 用户控制: 用户完全控制更新时机和选择
  • 环境适配: 多形态产品的优雅兼容
  • 安全可靠: 完整的安全验证和错误处理
  • 易于维护: 配置化设计和完善的文档
  • 架构健壮: 组件职责清晰,错误处理完善
  • 开发友好: 智能环境检测,详细错误诊断