一个真实场景:客户车间 30 台工控机跑着我们的上位机,改了一个报警文案要发版——工程师带着 U 盘跑现场,一台台拷、一台台重启,一天就没了;拷漏一台,现场版本就永远对不齐。多站点部署的上位机,自动升级不是锦上添花,是维护成本的生死线。这篇把我们落地过的升级系统完整拆开:版本清单、增量包、独立升级器、回滚兜底、产线互锁。

一、版本号与升级清单:一切自动化的地基

版本号用四段式 主.次.修订.构建(如 2.4.1.38),规则写死:主版本变=数据库结构或通信协议不兼容,次版本变=功能新增,修订=缺陷修复。服务端维护一个 manifest.json,客户端启动和定时(每 30 分钟)拉取比对:

{
  "version": "2.4.1.38",
  "minClient": "2.3.0.0",        // 低于此版本强制升级
  "package": "patch_2.4.0.35_to_2.4.1.38.zip",
  "sha256": "9f2c...",              // 包完整性校验
  "size": 1834022,
  "force": false,                   // 是否强制(安全补丁用)
  "notes": "修复报警确认状态偶发丢失"
}

▲ manifest 里的 sha256 不能省:车间网络环境复杂,下载包损坏或被篡改都要在安装前拦住。

二、增量包:别每次都传 200MB 全量

上位机程序动辄一两百 MB(带运行库、报表模板、驱动),全量升级在车间百兆内网上要传几分钟,30 台并发时服务器也吃不消。增量方案:

  • 发布时自动生成差量包:构建脚本对比上一版本目录,按文件哈希找出新增/变更文件打包——通常只有几个 exe 和 dll,包体 1~5MB;
  • 跨版本升级走链式补丁:2.4.0 → 2.4.1 → 2.4.2 依次打,每步都可校验;补丁链断了(太老的版本)自动降级为全量包;
  • 配置文件永不覆盖:差量包只含程序文件,客户现场的 config、数据库、日志目录在升级规则里列入白名单,碰都不碰。

三、独立 Updater 进程:绕开"文件正在使用"

主程序自己替换自己的 exe 必然失败——文件被进程占用。标准解法是主程序退出、拉起独立升级器、升级器替换文件、再拉起新版主程序

主程序下载包+校验
优雅停机停采集/落盘/关连接
拉起Updater主程序退出
备份+替换旧版移入backup
拉起新版校验启动成功
// Updater 核心:替换前先把旧版整体挪进 backup 目录
if (Directory.Exists(backupDir)) Directory.Delete(backupDir, true);
Directory.CreateDirectory(backupDir);
foreach (var f in patchFiles)
{
    var target = Path.Combine(appDir, f.RelativePath);
    if (File.Exists(target))
        File.Move(target, Path.Combine(backupDir, f.RelativePath));  // 备份
    File.Move(Path.Combine(stagingDir, f.RelativePath), target);     // 替换
}
WriteVersionFile(newVersion);   // 写入版本标记,主程序启动时自检
等待主程序退出要带超时重试。主程序发出退出信号到进程真正结束有几百毫秒延迟,Updater 直接动文件仍可能报占用。我们的做法:循环尝试重命名主 exe,成功说明进程已退出,最多等 10 秒,超时则放弃本次升级并回滚日志上报。

四、回滚兜底:升级失败必须能一键退回

升级系统的可信度不取决于成功路径,取决于失败路径。三条兜底规则:

失败场景兜底动作
替换中途失败(断电/占用)Updater 把 backup 目录文件移回,恢复原状,上报失败原因
新版启动即崩溃主程序启动写"启动成功"标记;Updater 等待 30 秒未见标记,自动回滚并重启旧版
新版运行异常(业务层)界面保留"回退到上一版本"按钮(工程师权限),一键执行反向流程
数据库升级与程序升级必须解耦。主版本带库结构变更时,用带版本号的迁移脚本(migrate_38.sql 这种),程序启动时按版本号顺序补执行,且每个脚本先备份库再执行。程序回滚时库结构只做兼容不做回退——新表新列留着无害,回退库结构才是灾难。

五、产线互锁:什么时候不许升级比怎么升级更重要

工业现场升级有一条铁律:产线运行中绝不升级。我们的互锁设计:升级窗口由排班表定义(默认仅停机检修时段);主程序检测到"设备运行中/工单进行中"状态时,即使有强制更新也只提示不执行;升级前自动记录当前工单进度,升级后首屏显示"升级完成,请核对工单状态"。另外 30 台机器采用分批灰度:先发 2 台观察 24 小时,无异常再全量推送——升级系统自己也要有"报警分级"思维。

这套系统上线后的收益很直接:一次发版从"工程师跑现场一整天"变成"服务端点一下、30 台机器 10 分钟内全部完成",版本一致率 100%,且每次升级都有完整日志(谁发的版、哪台成功、哪台回滚了)。对交付型团队来说,升级系统就是售后成本的杠杆——这笔投入值得在项目第一天就做。