从零搭建灌装监控系统(二十一):项目打包与部署,完结篇

📅 发布时间:2026/10/18 23:10:39
从零搭建灌装监控系统(二十一):项目打包与部署,完结篇
项目打包与部署完结篇这是「从零搭建灌装监控系统」系列第21篇也是最后一篇。功能写完了测试过了但把整个目录拷到现场机器上双击它不一定跑得起来。这篇把系统打成真正能交付的包发布配置、自包含运行时、单文件的取舍以及首次启动必须自动完成的初始化。拷过去打不开第一次交付的时候我把开发机上bin\Debug整个目录拷到 U 盘拿到现场双击 exe。弹了一个框“需要安装 .NET Desktop Runtime”。现场的工控机装的是 Win7 升级上来的 Win10没有 .NET 8 桌面运行时。现场也没网装不了。我只好回办公室重新发布一次。第二次我发了自包含版本能跑了。但第三天操作员说软件打开要等十几秒比在我这儿慢得多——单文件解压的开销。第三次交付前我把该做的都做了一遍。这篇就是那份清单。发布命令先明确一件事bin\Debug不是部署包。它包含调试符号、没有优化、依赖开发机上的运行时。部署要用publishdotnet publish FillTrack.csproj ^-cRelease ^-rwin-x64 ^ --self-containedtrue^-p:PublishSingleFiletrue ^-o.\publish参数逐个说参数作用能不能省-c Release关掉调试符号、打开优化不能省-r win-x64指定目标运行时RID不能省--self-contained true把 .NET 运行时打进包里看现场-p:PublishSingleFiletrue合成单个 exe可省有取舍-o .\publish输出目录可省自包含现场机器不一定有运行时这是第一个必须做的决定。框架依赖发布--self-contained false产出的包很小几十 KB 到几 MB但要求目标机器装了对应版本的 .NET 桌面运行时。工业现场的机器通常没网装不了运行时有网也不敢随便装——很多厂区禁止私自安装软件系统版本杂Win10 各种小版本都有自包含发布把运行时一起打包产物大概 150-200 MB。换来的是双击就能跑不依赖机器上装了什么。对现场交付来说这个交换非常值。U 盘拷贝本来就无所谓体积但“打不开”是现场事故。单文件方便但有代价PublishSingleFiletrue把几十个 dll 合成一个 exe看起来干净很多。代价是首次启动变慢。单文件 exe 运行时需要把内容解压到临时目录冷启动可能多出好几秒。第二次启动会快一些因为解压结果被缓存了——但缓存在哪个目录、会不会被清理工具删掉都不完全可控。所以在项目里加了这么一个设置PropertyGroupOutputTypeWinExe/OutputTypeTargetFrameworknet8.0-windows/TargetFrameworkNullableenable/NullableImplicitUsingsenable/ImplicitUsingsUseWPFtrue/UseWPFIncludeAllContentForSelfExtracttrue/IncludeAllContentForSelfExtractPlatformsAnyCPU;x64/Platforms/PropertyGroupIncludeAllContentForSelfExtract让内容文件也参与自解压避免运行时要访问的配置/资源文件散落在 exe 外面找不到。取舍建议如果现场对启动速度敏感比如操作员上岗就开机、开机就要用用非单文件的自包含发布——一个文件夹多几十个文件但启动快、路径行为跟开发时一致。单文件的收益主要是“看起来整洁”和“不容易误删某个 dll”。不要开裁剪看到 200 MB 的包第一反应是开PublishTrimmedtrue瘦身。WPF 项目不要开裁剪。WPF 大量依赖反射XAML 里的{Binding}、DataTemplate按类型名匹配、依赖属性的注册都发生在运行时。裁剪器只做静态分析看不到这些隐式引用会把“看起来没用到”的类型删掉。结果是编译通过、发布成功、运行时报TypeLoadException或绑定静默失效。更隐蔽的是绑定失效不报错。界面能打开只是某些字段永远是空的——这种 bug 在现场极难定位。同理ReadyToRun之类的优化可以按需开但裁剪对 WPF 是明确的风险项。运行时的目录约定打包只是第一步程序跑起来之后往哪儿写数据同样要想清楚。不要把数据库和日志写在 exe 所在目录。如果部署到C:\Program Files\下普通用户没有写权限程序一启动就崩。用用户数据目录vardataRootPath.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),FillTrack);Directory.CreateDirectory(Path.Combine(dataRoot,Data));Directory.CreateDirectory(Path.Combine(dataRoot,Logs));vardbPathPath.Combine(dataRoot,Data,filltrack.db);DbProvider.Initialize($Data Source{dbPath});三个目录各司其职目录内容是否随程序升级保留Data\SQLite 数据库✅ 必须保留Logs\滚动日志文件按保留策略清理配置设备参数 JSON✅ 必须保留现场升级软件的时候通常只替换程序文件数据目录不动。所以数据目录必须在程序目录之外——否则升级时整个覆盖历史生产记录全没了。首次启动要自动做完的事一个能交付的程序第一次运行应该自己把环境准备好而不是让人看一份安装文档。启动流程按这个顺序否是启动创建数据目录和日志目录初始化日志 Serilog初始化数据库 DbProviderAutoSyncStructure 建表有用户吗?创建默认管理员账号跳过加载配置 无则用安全默认值初始化设备服务弹出登录窗口进入主界面顺序不能乱尤其两点日志初始化必须在数据库之前。否则数据库初始化失败时错误没地方记现场只能看到程序闪退。建表和建默认账户在登录之前。登录要查用户表表不存在直接崩。部署清单每次交付前对照一遍dotnet publish -c Release发布通过自包含发布目标机器无需装 .NET 运行时未开启PublishTrimmed数据库/日志/配置写在用户数据目录不在程序目录首次启动能自动建表、建默认账户、建目录默认账户密码已在交付前修改或强制首登修改数据目录不在升级覆盖范围内现场机器上实际双击验证过一次断网状态下验证过启动和离线功能最后两条最容易被跳过也最容易出问题。在开发机上跑通不等于在现场机器上跑通。踩坑记录拷bin\Debug当部署包带调试符号、没优化、依赖开发机环境。必须走publish。框架依赖发布到没装运行时的机器现场弹“需要安装 .NET Desktop Runtime”。工业现场大概率装不了用自包含。对 WPF 开裁剪编译通过、发布成功、运行时绑定静默失效。这种 bug 在现场几乎没法查。不要开。数据和程序放同一个目录部署到Program Files下没写权限升级覆盖时历史数据一起丢。数据目录必须在程序目录之外。日志初始化放在数据库之后数据库初始化失败时没有任何记录现场只能看到闪退。本篇小结环节要点发布命令dotnet publish -c Release -r win-x64 --self-contained运行时自包含现场不用装 .NET单文件方便但冷启动慢按现场需求取舍裁剪WPF 不要开数据目录用户数据目录与程序目录分离首次启动建目录 → 初始化日志 → 建库建表 → 建默认账户 → 登录验证必须在现场机器上双击跑一次系列完结二十一篇从一行代码都没有到一个能拷到现场、双击就跑的灌装监控系统。回头看这条路径打地基— MVVM 骨架、依赖注入、导航、主题接通设备— Modbus RTU 协议、串口/TCP 双模式、轮询循环、断线重连留住数据— SQLite FreeSql、条码追溯、CSV 导出、配置原子写入让它说话— 报警生命周期、四路日志、日志查看器给人看— Dashboard、实时曲线、自定义控件能交付— 单元测试、权限管理、打包部署如果只能留三条经验给准备做类似项目的人第一先想清楚出错时怎么办。断线要重连、配置要能恢复、导出要能取消、密码要能挡住。这些逻辑不写软件在办公室永远看着正常一到现场就出问题。这个系列里我花在异常路径上的篇幅比花在正常路径上多。第二别让界面直接碰数据。从第一层的 MVVM 到最后一层的打包所有设计都指向同一个目标让谁负责什么有明确边界。ViewModel 不碰 Modbus控件不碰 Service测试不碰现场数据。边界一清楚改一处就不会崩一片。第三测试不是可选项。手动验证二十次和自动验证二十次看着结果一样区别在第二十一次——那时候你还能不能确定没改坏。这个系列记录的是一个单机灌装监控上位机通信协议是 Modbus RTU规模不大。如果你对更复杂的工业控制场景感兴趣——多设备并行、工艺配方编排、安全联锁、权限分级更细、部署到产线集群——可以看我的另一个系列「从零搭建工业控制系统」那边把这些问题都展开写过。写到这里就结束了。感谢一路看到这里的读者如果这个系列帮你在自己的项目里少踩一个坑那它就值了。