2026-06-23-配置收敛-operate-control连接组单一数据源-design.md 9.2 KB

配置收敛设计 —— operate↔control 连接组单一数据源 + 死键清理

日期:2026-06-23 · 当前任务:operate/control 双进程拆分 · 本专项 = 昨日建议「配置收敛」 范围铁律:只碰 operate↔control 的 7 个共享连接键 + 删 operate 死键。front、Java 微服务、凭据组、oplog-config.json 本轮不动

一、为什么做(问题)

operate/control 双进程拆分后,同一台机器上两个进程各自带一份 App.config,其中约 25 个键几乎逐字重复。control 那份(control/ivf_tl_ControlHost/App.config)的注释甚至明写「内容与 operate App.config 保持同步」——这是拆分把早先「operate = 单一数据源」(M5-01/M5-02 治理意图)又打破后留下的伤疤。

真实痛点:换中间件服务器、或调连接参数时,要在 operate + control 两份文件里各改一遍,漏一处 → 两进程连接目标不一致 → 行为打架,且不易发现。

已坐实的现状(codegraph 核实):

  • control 的 AppData(control/ivf_tl_Control/AppData.cs:47-133):读全部 13 个换气/CCD 业务键 + 连接组 + 凭据。
  • operate 的 AppData(ivf_tl_Operate/AppData.cs:77-112):只读 urlIp/urlPort/mqttIp/mqttPort/outInter;kfkaIP/kfkaPort 在 App.xaml.cs 读;凭据/cacheDisk/Language/houseEnabled 在登录/路径处读。operate 不读那 13 个换气/CCD 键 → 它们是合并单进程时代的死副本。
  • operate 已有「统一配置」UI(UnifiedConfigViewModel + AppConfigHelper.SaveAll),编辑连接组+本机+凭据,但 AppConfigHelper.SaveOpenExeConfiguration 只写 operate 自己的 config,不知道 control 还有一份 → 这就是单一数据源缺口的代码体现。
  • 凭据:operate 存 DPAPI 密文(SetApp/SaveEncrypted + 启动 MigratePlaintextCredentials),control 那份是明文 123456 → 本身已不一致。

二、目标 / 非目标

目标:

  1. operate 与 control 共享的连接组(7 键)收敛为唯一数据源,换机只改一处。
  2. 清理 operate 里那批换气/CCD 死副本键,职责清晰(换气/CCD 只归 control)。

非目标(本轮明确不做):

  • 凭据组(userName/passWord/engineerPwd/tlNum)并入共享文件(涉 DPAPI 跨进程解密 + 动 control C# 代码,留独立小决策)。
  • front 的 App.config、Java 8 个 server.ip、oplog-config.json 的收敛。
  • control 侧任何 C# 逻辑改动(本设计刻意让 control 只加一行 XML)。

三、设计决策(brainstorming 已拍板)

  • 范围:只收敛 operate↔control。
  • 核心目标:单一数据源 + 清理死键。
  • 机制:方案一 —— 共享配置片段 + <appSettings file=> 只读合并,写入由 operate 收口
    • file= 而非 configSource:file= 属性支持 .. 上级相对路径(control 在 operate 子目录,要指向父目录的共享文件);configSource 禁止 ..
    • 选方案一而非方案二(ProgramData + 自写加载器):方案一对最关键的 control 进程只加一行 XML、C# 零改动,风险最低;且复用已有 UI 与 AppConfigHelper
  • 凭据:本轮维持现状(operate 存密文、control 用自己那份/creds.dat)。

四、文件布局

operate 输出根目录/
├─ ivf_tl_Operate.exe
├─ ivf_tl_Operate.dll.config        ← 只留 operate 独有键 + 引用共享文件
├─ tl-shared.config                 ← 【新增·唯一数据源】只放共享连接组
└─ control/
   ├─ ivf_tl_ControlHost.exe
   └─ ivf_tl_ControlHost.dll.config ← 只留 control 独有键 + 引用 ..\tl-shared.config

tl-shared.config(独立 appSettings 片段,这是 file= 期望的格式):

<?xml version="1.0" encoding="utf-8"?>
<appSettings>
  <add key="urlIp" value="http://127.0.0.1"/>
  <add key="urlPort" value="10010"/>
  <add key="mqttIp" value="192.168.0.108"/>
  <add key="mqttPort" value="1883"/>
  <add key="kfkaIP" value="192.168.0.108"/>
  <add key="kfkaPort" value="9092"/>
  <add key="outInter" value="0"/>
</appSettings>

operate App.config:<appSettings file="tl-shared.config"> …operate 独有键 inline… </appSettings> control App.config:<appSettings file="..\tl-shared.config"> …control 独有键 inline… </appSettings>

五、键归属总表

类别 处置
共享组(7) urlIp, urlPort, mqttIp, mqttPort, kfkaIP, kfkaPort, outInter 从两份 App.config 删除,只存 tl-shared.config
operate 独有 userName, passWord, engineerPwd, tlNum, cacheDisk, Language, houseEnabled, autoFocus, controlPort, controlExePath 留 operate config
control 独有 csTime, gbTime, VentNum, VentPre, VentWaitTimeB, VentWaitTimeD, AutoWaitTime, CCDAutoWaitTime, CCDError, CCDFailedNumber, CCDFailedWaitTime, QueuAir, StopPro, cacheDisk, 凭据(现状) 留 control config
operate 死键(删) csTime, gbTime, VentNum, VentPre, VentWaitTimeB, VentWaitTimeD, AutoWaitTime, CCDAutoWaitTime, CCDError, CCDFailedNumber, CCDFailedWaitTime, QueuAir 从 operate App.config 删除(operate 不读;实现时再 grep 全 operate 兜底确认无其它消费点)

注:cacheDisk 两进程都可能用,各自保留 inline(非"换机要改"的连接类,不进共享文件,无漂移风险)。autoFocus/StopPro/QueuAir 等非连接键不进共享文件。死键删除以"operate 侧确无消费点"为前提,实现时逐键 grep 坐实再删。

六、读 / 写数据流

读(两进程 C# 零改动):

  • 各自 App.config 的 <appSettings file="…tl-shared.config">,.NET 启动时把共享文件的键合并进 ConfigurationManager.AppSettings
  • operate 的 AppData/AppConfigHelper.GetString、control 的 AppData 照旧 AppSettings["urlIp"] 即读到。

写(只动 operate 侧 AppConfigHelper):

  • 统一配置 UI 保存共享组 7 键时,AppConfigHelper 把它们写进 tl-shared.config(用 XDocument 直接读写这个小文件,绕开 ConfigurationManager 写回 file= 的不确定行为),写后 ConfigurationManager.RefreshSection("appSettings") 让本进程即时生效。
  • 非共享键(凭据/本机)的写回逻辑不变,仍 OpenExeConfiguration 落 operate 自己的 config。
  • 实现要点:AppConfigHelper 内部维护一个"共享键集合",Save(key,value) 按 key 归属分流到 tl-shared.config 或 operate config;SaveAll 不改调用方,只改底层落点。

control 侧:只在 ControlHost/App.configfile="..\tl-shared.config",C# 一行不改

七、部署 + 迁移

  • 构建:tl-shared.config 加进 ivf_tl_ControlHost 或 operate 工程,以 <None><CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory></None> 随 operate 输出根目录;control 子目录不需要拷贝(运行期靠相对 ..\ 指过去)。
    • 注:control 通过 csproj 把 ControlHost 输出到 operate 的 control\ 子目录;tl-shared.config 落在 operate 根 = control 的父目录,..\tl-shared.config 命中。
  • 首启迁移(幂等、保老部署不丢值):operate 启动时若 tl-shared.config 不存在,用当前 operate config 里那 7 个共享键的现值生成它。已存在则不动。
  • 文档同步:开发环境/双进程部署指南.md + 连接配置清单-换服务器必读.md 第三节:C# 侧换机由"改 operate+control 两份"改为"只改 tl-shared.config 一份";操作端逻辑与配置全景.md / control-逻辑与配置全景.md 的配置键章节标注共享文件来源。

八、错误处理 / 边界

  • 共享文件缺失:file= 指向不存在文件时 .NET 行为是"忽略外部文件、只用 inline"——故首启迁移要尽早生成它;且 AppConfigHelper.GetString 已有缺键回退默认值(不裸崩)。
  • 写共享文件失败(权限/占用):AppConfigHelper 写操作 try 兜底(沿用现有"写失败不抛出"策略),不阻断 UI/启动。
  • control 独立重拉(看门狗,operate 不在时):control 读的是磁盘上的 ..\tl-shared.config,值为 operate 上次保存的;持久化在磁盘,标准场景成立。
  • RefreshSection 后本进程重读:operate 写共享文件 + RefreshSection 后,本进程 AppSettings 是否即时反映合并后的外部文件值 —— 列入真机验证项(若不即时,UI 保存提示"重启生效"即可,不阻断)。

九、测试 / 验证

  1. 第一步硬验证(破假设):最小样例验证 <appSettings file="..\x.config"> 在子目录进程能读到父目录共享文件键(真机 Release 编译+跑)。不过则当场退回方案二
  2. 单测:AppConfigHelper 共享键读写(写 tl-shared.config→读回一致;非共享键仍落 operate config 不串);死键删除后 operate 编译 0 错。
  3. 真机端到端:改 tl-shared.config 的 urlIp/mqtt/kfka → operate 读到新值 + 看门狗/operate 拉起 control 也读到同一新值 → /status 连接正常、operate Release 真外壳登录通;control 启动 0 缺键 NPE / 0 DbException。
  4. 回归:既有 40 单测 + HIL 硬件在环套件全过;operate Release + control sln 双编译 0 错。

十、风险

  • file= + .. 不被支持(低概率)→ 第一步硬验证拦截,退方案二。
  • 死键误删(operate 实际仍有隐蔽消费点)→ 删前逐键 grep 全 operate 树坐实。
  • 写回 file= 合并的即时生效问题 → 用 XDocument 直写 + RefreshSection 规避;真机验证兜底。