第 9 章:自动更新与崩溃监控

应用发布后,你需要持续迭代和修复问题。本章将学习如何实现自动更新机制,让用户始终使用最新版本, 以及集成崩溃监控系统,及时发现和修复生产环境的问题。

9.1 自动更新概述

Electron 应用不像 Web 应用那样刷新就能获得更新。你需要实现自动更新机制,在后台下载新版本并提示用户安装。 electron-updater 是社区维护的自动更新库,支持 Windows、macOS 和 Linux。

💡给前端开发者的建议

把自动更新想象成 PWA 的 Service Worker 更新机制:检测到新版本 → 后台下载 → 提示用户刷新(重启)。 只不过 Electron 的"刷新"是重启整个应用。

自动更新流程

text
┌─────────────────────────────────────────────────────────┐
│                    自动更新流程                          │
├─────────────────────────────────────────────────────────┤
│  1. 应用启动时检查更新                                   │
│     ↓                                                   │
│  2. 向更新服务器查询最新版本                             │
│     ↓                                                   │
│  3. 发现新版本,开始后台下载                             │
│     ↓                                                   │
│  4. 下载完成,提示用户安装                               │
│     ↓                                                   │
│  5. 用户确认后,退出并安装更新                           │
│     ↓                                                   │
│  6. 应用重启,运行新版本                                 │
└─────────────────────────────────────────────────────────┘

9.2 配置 electron-updater

electron-updater 已经内置在 electron-builder 中,无需额外安装。只需要配置更新服务器即可。

安装与配置

bash
# electron-updater 已包含在 electron-builder 中
# 如果需要单独安装:
npm install electron-updater
javascript
// main.js - 自动更新配置
const { app } = require('electron');
const { autoUpdater } = require('electron-updater');
const log = require('electron-log');

// 配置日志
autoUpdater.logger = log;
autoUpdater.logger.transports.file.level = 'info';

// 检查更新(开发环境也可以测试)
function setupAutoUpdater(mainWindow) {
  // 配置更新服务器(使用 GitHub Releases)
  // 需要在 electron-builder.yml 中配置 publish 选项

  // ===== 更新事件 =====
  
  // 发现可用更新
  autoUpdater.on('update-available', (info) => {
    log.info('发现新版本:', info.version);
    mainWindow.webContents.send('update-available', {
      version: info.version,
      releaseDate: info.releaseDate,
      releaseNotes: info.releaseNotes
    });
  });

  // 没有可用更新
  autoUpdater.on('update-not-available', () => {
    log.info('当前已是最新版本');
  });

  // 下载进度
  autoUpdater.on('download-progress', (progress) => {
    const percent = Math.round(progress.percent);
    log.info(`下载进度: ${percent}%`);
    mainWindow.webContents.send('update-progress', { percent });
  });

  // 下载完成
  autoUpdater.on('update-downloaded', (info) => {
    log.info('更新下载完成:', info.version);
    mainWindow.webContents.send('update-downloaded', {
      version: info.version
    });
  });

  // 错误处理
  autoUpdater.on('error', (err) => {
    log.error('更新错误:', err);
    mainWindow.webContents.send('update-error', {
      message: err.message
    });
  });
}

// 检查更新
function checkForUpdates() {
  autoUpdater.checkForUpdates().catch(err => {
    log.error('检查更新失败:', err);
  });
}

// 立即安装更新(退出并重启)
function installUpdate() {
  autoUpdater.quitAndInstall();
}

module.exports = { setupAutoUpdater, checkForUpdates, installUpdate };

渲染进程中的更新 UI

vue
// Vue 组件 - 更新提示




9.3 更新策略

根据产品需求,可以选择不同的更新策略:强制更新、可选更新、静默更新等。

策略说明适用场景
强制更新发现新版本必须更新才能使用安全修复、重大Bug
可选更新提示用户,用户可选择稍后功能更新、优化
静默更新后台下载,下次启动时自动安装小修复、非紧急更新
手动检查用户点击"检查更新"按钮节省服务器资源

强制更新实现

javascript
// 强制更新 - 发现新版本后禁用应用功能
autoUpdater.on('update-available', (info) => {
  // 判断是否为强制更新(根据版本号或服务器配置)
  const isForceUpdate = info.releaseNotes?.includes('[FORCE]');
  
  mainWindow.webContents.send('update-available', {
    version: info.version,
    force: isForceUpdate
  });
  
  if (isForceUpdate) {
    // 立即开始下载
    autoUpdater.downloadUpdate();
  }
});

autoUpdater.on('update-downloaded', () => {
  // 强制更新:立即退出并安装
  autoUpdater.quitAndInstall();
});

9.4 崩溃监控(Sentry 集成)

生产环境的应用难免会出现崩溃和错误。集成 Sentry 可以实时监控应用健康状况,收集崩溃报告和错误日志。

安装 Sentry SDK

bash
# 安装 Sentry Electron SDK
npm install @sentry/electron

主进程配置

javascript
// main.js - Sentry 初始化
const Sentry = require('@sentry/electron/main');

Sentry.init({
  dsn: 'https://your-dsn@sentry.io/project-id',
  
  // 环境标识
  environment: app.isPackaged ? 'production' : 'development',
  
  // 发布版本(用于关联源码映射)
  release: `rpa-assistant@${app.getVersion()}`,
  
  // 采样率(生产环境建议 1.0,开发环境可以调低)
  sampleRate: 1.0,
  
  // 启用性能监控
  tracesSampleRate: 0.1,
  
  // 附加数据
  initialScope: {
    tags: {
      platform: process.platform,
      arch: process.arch
    }
  }
});

// 手动捕获异常
try {
  riskyOperation();
} catch (error) {
  Sentry.captureException(error);
}

// 添加面包屑(操作轨迹)
Sentry.addBreadcrumb({
  category: 'user-action',
  message: '用户点击了开始录制按钮',
  level: 'info'
});

渲染进程配置

javascript
// renderer.js 或 Vue 入口文件
import * as Sentry from '@sentry/electron/renderer';

Sentry.init({
  dsn: 'https://your-dsn@sentry.io/project-id',
  
  // 启用 Vue 集成
  integrations: [
    Sentry.vueIntegration({
      app, // Vue 应用实例
      attachProps: true
    })
  ],
  
  // 跟踪 Vue 组件性能
  tracesSampleRate: 0.1
});

// 在 Vue 组件中使用
function handleError() {
  Sentry.captureException(new Error('用户操作失败'));
}

// 设置用户信息
Sentry.setUser({
  id: 'user-123',
  username: '张三',
  email: 'zhangsan@example.com'
});

9.5 日志收集与分析

除了崩溃监控,还需要收集应用运行日志,帮助诊断用户遇到的问题。

日志上传实现

javascript
// logger.js - 带上传功能的日志系统
const log = require('electron-log');
const fs = require('fs').promises;
const path = require('path');
const { app } = require('electron');

class LogManager {
  constructor() {
    this.logDir = app.getPath('logs');
    this.setupLogTransport();
  }

  setupLogTransport() {
    // 文件日志
    log.transports.file.resolvePath = () => 
      path.join(this.logDir, 'app.log');
    log.transports.file.maxSize = 10 * 1024 * 1024; // 10MB
    
    // 控制台日志(开发环境)
    log.transports.console.level = app.isPackaged ? false : 'debug';
  }

  // 获取最近的日志内容
  async getRecentLogs(lines = 100) {
    try {
      const logPath = path.join(this.logDir, 'app.log');
      const content = await fs.readFile(logPath, 'utf-8');
      return content.split('\n').slice(-lines).join('\n');
    } catch (err) {
      return '无法读取日志文件';
    }
  }

  // 上传日志到服务器
  async uploadLogs() {
    try {
      const logs = await this.getRecentLogs(500);
      
      const response = await fetch('https://your-api.com/logs', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          version: app.getVersion(),
          platform: process.platform,
          logs: logs,
          timestamp: new Date().toISOString()
        })
      });
      
      if (response.ok) {
        log.info('日志上传成功');
        return true;
      }
    } catch (err) {
      log.error('日志上传失败:', err);
      return false;
    }
  }

  // 导出日志文件(供用户手动发送)
  async exportLogs() {
    const { dialog } = require('electron');
    const result = await dialog.showSaveDialog({
      defaultPath: `logs-${Date.now()}.txt`,
      filters: [{ name: '日志文件', extensions: ['txt'] }]
    });
    
    if (!result.canceled) {
      const logs = await this.getRecentLogs(1000);
      await fs.writeFile(result.filePath, logs, 'utf-8');
      return result.filePath;
    }
  }
}

module.exports = { LogManager };

9.6 性能监控

监控应用性能指标,及时发现性能瓶颈。

javascript
// performance-monitor.js
const { app } = require('electron');

class PerformanceMonitor {
  constructor() {
    this.metrics = {
      startupTime: 0,
      memoryUsage: [],
      cpuUsage: []
    };
    this.startMonitoring();
  }

  // 记录启动时间
  recordStartupTime() {
    this.metrics.startupTime = Date.now() - global.appStartTime;
    console.log(`应用启动时间: ${this.metrics.startupTime}ms`);
  }

  // 监控内存使用
  startMonitoring() {
    setInterval(() => {
      const usage = process.memoryUsage();
      this.metrics.memoryUsage.push({
        timestamp: Date.now(),
        rss: Math.round(usage.rss / 1024 / 1024), // MB
        heapUsed: Math.round(usage.heapUsed / 1024 / 1024),
        heapTotal: Math.round(usage.heapTotal / 1024 / 1024)
      });
      
      // 只保留最近100条记录
      if (this.metrics.memoryUsage.length > 100) {
        this.metrics.memoryUsage.shift();
      }
      
      // 内存告警(超过500MB)
      if (usage.rss > 500 * 1024 * 1024) {
        console.warn('内存使用过高:', usage.rss);
      }
    }, 30000); // 每30秒采样一次
  }

  // 获取性能报告
  getReport() {
    const memStats = this.metrics.memoryUsage;
    const avgMemory = memStats.reduce((sum, m) => sum + m.rss, 0) / memStats.length;
    
    return {
      startupTime: this.metrics.startupTime,
      averageMemoryMB: Math.round(avgMemory),
      maxMemoryMB: Math.max(...memStats.map(m => m.rss)),
      platform: process.platform,
      electronVersion: process.versions.electron,
      nodeVersion: process.versions.node
    };
  }
}

module.exports = { PerformanceMonitor };
💡运维最佳实践
  • 版本追踪:每个错误报告都要包含应用版本号,便于定位问题
  • 分级告警:根据错误频率和影响范围设置不同级别的告警
  • 用户反馈:提供"发送反馈"功能,让用户主动报告问题
  • 灰度发布:新版本先小范围推送,确认稳定后再全量发布
  • 回滚机制:保留旧版本安装包,紧急情况下可快速回滚