一个真实场景:客户车间 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 目录 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 把 backup 目录文件移回,恢复原状,上报失败原因 |
| 新版启动即崩溃 | 主程序启动写"启动成功"标记;Updater 等待 30 秒未见标记,自动回滚并重启旧版 |
| 新版运行异常(业务层) | 界面保留"回退到上一版本"按钮(工程师权限),一键执行反向流程 |
五、产线互锁:什么时候不许升级比怎么升级更重要
工业现场升级有一条铁律:产线运行中绝不升级。我们的互锁设计:升级窗口由排班表定义(默认仅停机检修时段);主程序检测到"设备运行中/工单进行中"状态时,即使有强制更新也只提示不执行;升级前自动记录当前工单进度,升级后首屏显示"升级完成,请核对工单状态"。另外 30 台机器采用分批灰度:先发 2 台观察 24 小时,无异常再全量推送——升级系统自己也要有"报警分级"思维。
这套系统上线后的收益很直接:一次发版从"工程师跑现场一整天"变成"服务端点一下、30 台机器 10 分钟内全部完成",版本一致率 100%,且每次升级都有完整日志(谁发的版、哪台成功、哪台回滚了)。对交付型团队来说,升级系统就是售后成本的杠杆——这笔投入值得在项目第一天就做。
