Files
StandardSence/StandardScene架构重构方案.md
T
2026-06-14 11:19:15 +08:00

268 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# StandardScene 架构重构方案
> 配套文档:《StandardScene代码审查报告.md》(质量/缺陷)、《StandardScene拆分计划.md》(拆分进度)
> 本文聚焦**结构与架构合理性**:现状全景 → 逐模块深度分析 → 目标分层 → 程序集边界 → net8.0 去 Windows → 分阶段迁移路径。
> 日期:2026-06-09
---
## 一、现状架构全景
### 1.1 程序集与依赖
| 程序集 | AssemblyName | TFM | 角色 | 依赖 |
|---|---|---|---|---|
| StandardScene.Core | `StandardScene` | net8.0-windows | 基座:抽象+业务+任务+调度+WebApi+UI | SimpleLite/SimpleCore/CommonUsage/MDCSToolBox/Topaz + NuGet(MQTTnet/Nancy/Jint/EasyModbus/IoTClient/OpenXml/Newtonsoft) |
| StandardScene.Devices | `StandardScene.Devices` | net8.0-windows | 具体设备驱动(门/充电桩/按钮盒) | →Core + SimpleLite/SimpleCore/Topaz + leegKeys-sdk |
| StandardScene.Protocol.VDA5050 | `StandardScene.Protocol.VDA5050` | net8.0-windows | VDA5050 协议车型 | →Core + SimpleLite/SimpleCore + NuGet(MQTTnet/Nancy/Jint/Newtonsoft) |
插件清单 `scene.json`Devices / VDA5050 各一份):
```json
{ "id":"devices", "assembly":"StandardScene.Devices.dll", "requiresCore":"StandardScene.dll",
"provides": { "doorControllers":[...], "chargeStations":[...], "buttonBoxes":[...] } }
```
发现机制:`SimpleLite.Utils.UiTypeDiscovery.AllTypes()` 跨程序集扫描 + 类型特性(`[DoorType]`/`[ChargeType]`/`[ButtonBox...]`/`[CarType]`)。
### 1.2 当前依赖方向(问题版)
```mermaid
graph TD
Devices --> Core
VDA5050 --> Core
Core -->|NuGet| MQTTnet
Core -->|NuGet| Nancy
Core -->|NuGet| EasyModbus
Core -->|NuGet| IoTClient
Core -->|NuGet| OpenXml
Core --> WinForms[WinForms 12+ 窗体]
Core --> SimpleLite
subgraph 卫星
Devices
VDA5050
end
```
**核心结构问题**Core 是"万能基座"——既是抽象基座,又塞满了具体协议依赖(MQTT 属 VDA5050、Modbus/IoTClient 属设备、Nancy 属 WebApi、OpenXml 属报表),还内置 12+ WinForms 窗体。卫星只能依赖这个臃肿 Core,无法独立演进。
### 1.3 体量分布(非 Designer,前列)
WebApi 2686 / AbstractLoopMission 1858 / VehicleMonitor 1502(UI) / AbstractChainedDeliveryMission 1407(已 Compile Remove) / ChainedDeliveryMission 1365 / ChargeStationManagementForm 1250(UI) / AbstractChargeLogicMission 1143 / VDA5050Car 938 / DoorMission 932 / ButtonBoxManager 913 / DoorManager 848 / Kiva 827 / ButtonMission 743 / StandardChargeMission 671 / Commons 653 …
> 12+ 个 600~2700 行巨类,是可维护性的主要矛盾。
---
## 二、结构性问题诊断(按影响排序)
| # | 问题 | 证据 | 影响 |
|---|---|---|---|
| S1 | **分层污染**:Core 背负协议/设备/报表专有依赖 | Core.csproj 引 MQTTnet/EasyModbus/IoTClient/Nancy/OpenXml | 卫星无法瘦身;Core 编译/部署重;职责不清 |
| S2 | **WinForms 全模块渗透**,阻塞纯 net8.0 | 12+ `*.Designer.cs`Charge 4 个 Form、VehicleMonitor、各 Manager/Viewer);三 csproj 均 `UseWindowsForms=true` + `net8.0-windows` | 无法 `net8.0` 跨平台/瘦运行;与"UI 迁 migu"目标冲突 |
| S3 | **God-class 泛滥** | WebApi 2686 / AbstractLoopMission 1858 / ChainedDeliveryMission 1365 / AbstractChargeLogicMission 1143 … | 改动风险高、测试困难、并发态难推理 |
| S4 | **充电子系统未独立**,却已自成体系(20 文件) | `Charge/` 任务+站点+配置+数据服务+通信+4 表单 | 应为独立卫星,却深埋 Core |
| S5 | **抽象与实现同居 Core** | 设备基类/特性/Mission/Manager 在 Core,仅具体驱动在 Devices | 卫星仍强依赖 Core 内部;热插拔受限 |
| S6 | **干净抽象反向耦合到巨类** | `Loop/ILoopRules.cs` 顶部 `using static AbstractLoopMission;`(依赖其嵌套 `LoopPoint`) | 好接口被巨类绑架,无法独立复用 |
| S7 | **全局可变静态** | `DeliveryCallbackRegistry`static 字典)、`Commons` 静态工具、各 `static HttpClient` | 隐式耦合、测试隔离难、生命周期不可控 |
| S8 | **内核 Coder 注册表限定本程序集反射** | `ProgramCoderSet`/`SegmentPlan.Coder` 特性驱动、按内核程序集反射 | 导航类卫星(磁/二维码/激光)无法热插拔(C2 阻塞) |
| S9 | **构建可移植性差** | csproj 多处绝对 `HintPath`E:\Work…、D:\MDCS…) | 换机/CI 无法直接构建 |
| S10 | **死文件/弃用并存** | `AbstractChainedDeliveryMission.cs`(1407) 被 `Compile Remove` 仍在树 | 认知噪音、误改风险 |
| S11 | **命名空间与程序集名不一致** | 三程序集 `RootNamespace=StandardScene`;类型散落 `StandardScene.*` 子命名空间 | 物理边界与逻辑边界错位,难判断"谁属于谁" |
---
## 三、逐模块深度分析
> 每个模块:**职责 / 结构 / 依赖与耦合 / 主要问题 / 目标处置**。
### M1 CarTypes(车型族,11 文件)
- **职责**:定义各 AGV 车型(Kiva 827、Forklift 398、MultiWheel*、MultiVehicle、DualLifting、ArmCar、DummyCar 666+ 字段袋 `BasicFields`(50) + 车辆监控 UI `VehicleMonitor`(1502, WinForms)。
- **结构**:字段袋 `BasicCarFields/SiteFields/TrackFields/PlanFields` 设计合理(`-1` 哨兵语义被 Coder 正确利用);车型类承载通信(HttpClient)、状态机、调度、UI、强制控制等多职责。
- **耦合**:车型直引 `Commons`、HttpClient、`MessageBox`、内核类型;`VehicleMonitor` 把 UI 与车辆模型绑定。
- **问题**:巨类(Kiva 827)、`async void`+`throw`Kiva.ForceStop 619/659)、空 catch、硬编码 IP192.168.2.1:8008)。
- **目标处置**:车型保留在领域层;剥离"通信/HTTP/UI"为协作者(`ICarTransport`/`ICarStatusView`);`VehicleMonitor` 进 UI 程序集。
### M2 Coders(轨迹编码器,1 文件 + 车型内联)
- **职责**`CommonTrackCoders` 通用 `ITrackCoder`(磁导航统一 `MagneticTrackCoder`、避障 `AvoidanceParamCoder`4参/`AvoidanceParamLWCoder`2参)。
- **结构**:本轮已去重、结构清晰;`CommonTemplateTrackCoder` 提供 `SiteFieldsType` 扩展点。
- **耦合**:受内核 `ProgramTrackCoderSettings`/`TemplateTrackCoderSettings` 特性约束(S8)。
- **目标处置**:继续把车型内联 Coder 收敛至此;待内核放开注册表后,导航类 Coder 可下沉到导航卫星。
### M3 Chained(链式/循环任务族,12+2 文件)
- **职责**`AbstractLoopMission`(1858)、`ChainedDeliveryMission`(1365)、`TransportMission`(549)、`LoopMission`、回调注册表/附着器、`Loop/` 规则接口、若干 Viewer(UI)。
- **结构****两面性**——`Loop/ILoopRules`(IEnter/IExit/IJoin/IBranch/ITaskStrategy) 与 `DeliveryCallbackRegistry` 是规范的策略/注册表模式(亮点);但 `AbstractLoopMission` 是 1858 行巨类,且接口 `using static AbstractLoopMission`S6)反向耦合其嵌套类型。
- **耦合**:裸 `new Thread`+`while`+`Thread.Sleep`(P0-1 已改协作式停止);UI Viewer 混入。
- **问题**:巨类、状态用字符串 `status.status``AbstractChainedDeliveryMission`(1407) 死文件(S10)。
- **目标处置**:把 `LoopPoint/LoopTask` 等领域模型从巨类**上提**到 Model/Abstractions,让 `Loop` 接口独立;巨类按"调度循环/任务编排/显示"拆分;删除/归档死文件。
### M4 InterLock(互锁,4 文件)
- **职责**`AbstractInterlockMission`(367)、`TrafficInterlockMission`、Viewer(UI)。交通互锁逻辑。
- **耦合**:与调度/交通控制耦合;含 Viewer。
- **目标处置**:归入"交通/调度"领域子模块;UI 外提。
### M5 Scheduler(调度后台任务,4 文件)
- **职责**`HeartBeatMission``NodeIsEnableMission``SecuritySignalMission``RegionalTrafficControlMission`(377)。周期性后台任务(心跳/站点禁用/安全信号上传/区域交通)。
- **耦合**:直发 HTTP(硬编码端点)、`Console.WriteLine`、原 `while(true)`+`Thread.Abort`P0-1 已修为 `while(started)`/协作式)。
- **问题**:每个任务各写一套线程循环(重复),无统一基类。
- **目标处置**:抽 `MissionRunnerBase`CancellationToken + 状态枚举 + 统一日志),所有周期任务复用。
### M6 Charge(充电子系统,20 文件 + ChargeStationType)★需独立
- **职责**:任务(`StandardChargeMission`671/`AbstractChargeLogicMission`1143)、站点(`AbstractChargeStation`/`ChargeStation`)、配置(`ChargeStrategyConfig`/`AlarmConfig`/`ChargingSetting`)、数据服务(3 个 *DataService)、通信(`ChargeUdpService`/`CommunicationMessage(Service)`)、Helper、**4 个 WinForms 表单**。
- **结构**:自成完整子系统(任务+设备+配置+持久化+通信+UI),但全埋 Core。`CommunicationMessageService` 是**安全解析范本**(先校验长度)。
- **耦合**:与具体充电桩驱动(Devices/Charge)双向(Core 持任务/抽象,Devices 持 PCB/FL/MuXing 驱动);实时 UDP 路径越界(P0-3 已修)。
- **目标处置**:升级为**独立卫星 `StandardScene.Charge`**(含 Mission/抽象/配置/通信),具体桩驱动留 `Devices` 或并入;4 表单进 UI 程序集;通过 Abstractions 与 Core 解耦。
### M7 ExtendDevice + Devices(设备:门/按钮盒,Core 侧 13 + Devices 侧 6
- **职责**Core 侧 = 基类(`BasicDoorController`/`BasicButtonBox`)+特性(`DoorTypeAttribute` 等)+管理器(`DoorManager`848/`ButtonBoxManager`913, 含 UI)+任务(`DoorMission`932/`ButtonMission`743)+模型/Monitor(UI)Devices 侧 = 具体驱动(`ModbusDoorController`/`Azowie`/`Leeg`/3 充电桩)。
- **结构**:**新驱动质量优秀**(`ModbusDoorController`:CTS 协作停止/锁/变更检测/重连节流/统一日志);`DoorTypeAttribute` 特性发现规范。
- **耦合**:抽象+业务在 Core、驱动在 DevicesS5);Manager 含 WinForms。
- **问题**`ModbusDoorController` 用析构函数兜底 `Disconnect`(GC 线程取锁+Wait,风险)→应实现 `IDisposable`Manager 巨类含 UI。
- **目标处置**:把设备**抽象+特性**下沉到 `StandardScene.Abstractions`Manager 拆"设备生命周期服务 + UI";驱动统一 `IDisposable`
### M8 Protocol.VDA5050(协议卫星,9 文件)
- **职责**`VDA5050Car`(938)、`MasterMQTTCommunication``VDA5050Interface/Segment/Helper/Commons``VDA5050WebApi``TextViewer`(UI)。
- **结构**:已是独立卫星(好);自带 WebApi 与 MQTT 栈。
- **问题**`async void`+`throw ex`(240)、硬编码 `192.168.2.1:8008` 且注释掉按车地址(150)、空 catch、`Console.WriteLine` 满布、`monitor()` 死局部函数。
- **目标处置**:作为协议卫星范本;端点配置化(按车 `address`)、异步规范化、日志统一、`VDA5050WebApi` 与 Core WebApi 走统一 `ApiResult`/路由约定。
### M9 WebApi(老 Nancy 接口,2686 行)★最高优先重构
- **职责**:车辆/任务/地图/配置等 HTTP 接口(Nancy 2.0)。
- **结构**:单文件上帝路由;错误响应 `new{Success=false,Code=500,...}` 复制几十处;反射 execute 端点(P0-2 已加白名单)。
- **目标处置**:抽 `ApiResult.Ok/Fail` + 按资源拆模块(CarApi/MissionApi/MapApi…);统一鉴权中间件(来源/令牌,配置驱动);老接口归 `WebApi.Core(deprecated)` 规划迁移;最终独立 `StandardScene.WebApi` 程序集(隔离 Nancy 依赖)。
### M10 Model(领域/配置模型,12 文件)
- **职责**`Map/SimpleMap/MapStructure``SimpleConfig``TaskModel/LoopTask/MissionState``VehicleStatus`、各 `*Setting`
- **结构**:领域模型与配置混居;`Map.cs` 含硬编码 `127.0.0.1:4321`
- **目标处置**:拆"纯领域模型(→Abstractions"与"配置(→Configuration";端点配置化。
### M11 基础设施(TCP 5 / Utils 4 / CommonTools 2
- **职责**`AsyncTcpClient`(432, 质量较好)+事件args`JsonParser/JsonTool/ModbusClass/WebAPIHelper``AtomicFileUpdateHelper/SnowflakeIdGenerator`
- **问题**`AsyncTcpClient.Send``InvalidProgramException`(类型不当)、`EndWrite` 无异常处理、`uint on` 未用;`ModbusClass` 与设备 Modbus 重复关注点。
- **目标处置**:归入 `StandardScene.Infrastructure`net8.0 纯净,无 Windows);TCP/IO/序列化/ID 通用化。
### M12 Core 根 God-filesCommons 653 / Heuristic / LadderLogic / StandardCADTool / WebApi
- **职责**`Commons` 万能工具+调度(`NearestTask`)、`Heuristic` 启发式、`LadderLogic` 梯形逻辑、`StandardCADTool` CAD(硬编码盘符路径、`async void`)。
- **问题**`Commons.AddOrUpdateXxxField` 重复 4 份、`CarValue` 忽略 key、`GoSite` 假重试(详见审查报告 P2-1);根目录堆放无归属大文件。
- **目标处置**`Commons` 按职责拆(字段服务/调度服务/控制台辅助);`StandardCADTool` 路径配置化、归 CAD 子模块。
---
## 四、目标架构
### 4.1 分层与程序集边界(目标)
```mermaid
graph TD
subgraph L0[抽象层 net8.0 纯净]
Abstractions[StandardScene.Abstractions<br/>接口/特性/字段袋/领域模型/ITrackCoder/设备契约]
Infra[StandardScene.Infrastructure<br/>TCP/序列化/ID/IO/ILogger]
Config[StandardScene.Configuration<br/>配置模型+读写]
end
subgraph L1[领域层 net8.0]
Core2[StandardScene.Core<br/>车型/任务族/调度/交通/Coders]
end
subgraph L2[卫星 net8.0]
Charge2[StandardScene.Charge]
Devices2[StandardScene.Devices]
VDA[StandardScene.Protocol.VDA5050]
Nav[StandardScene.Nav.*(磁/二维码/激光,待内核放开)]
end
subgraph L3[宿主/接入 net8.0-windows]
Web[StandardScene.WebApiNancy 隔离)]
UI[StandardScene.UI.WinForms(临时·弃用,待 migu]
end
Core2 --> Abstractions
Core2 --> Infra
Core2 --> Config
Charge2 --> Abstractions
Devices2 --> Abstractions
VDA --> Abstractions
Nav --> Abstractions
Charge2 -. 受限 .-> Core2
Web --> Core2
UI --> Core2
Devices2 -. NuGet .-> Modbus
VDA -. NuGet .-> MQTT
Web -. NuGet .-> Nancy
```
**关键规则**
1. **依赖只向下**:卫星/宿主 → Abstractions(+受限 Core)**Core 不得依赖任何具体协议/设备 NuGet**MQTT/Modbus/IoTClient/OpenXml/Nancy 全部下放到对应卫星/宿主)。
2. **抽象先行**:接口、注册特性、字段袋、纯领域模型、`ITrackCoder`/设备契约统一进 `Abstractions`net8.0,无 Windows),卫星只认 Abstractions。
3. **UI 与协议 = 边缘**WinForms 全部收口到 `UI.WinForms`(标 deprecated,仅过渡,迁 migu 后删);Nancy 收口到 `WebApi`。如此 Abstractions/Infrastructure/Core/卫星可去 `-windows`,回到纯 `net8.0`
4. **发现统一**:设备/车型/协议统一"`[XxxType]` 特性 + `UiTypeDiscovery.AllTypes()`"`scene.json` 声明 `provides`
### 4.2 边界接口(最小集)
- `ILogger`(取代 `Console.WriteLine`/直连 Diagnosis):业务只依赖抽象。
- `IDeviceDriver`/`IDoorController`/`IChargeStation`/`IButtonBox`(下沉 Abstractions),`IDisposable` 释放。
- `MissionRunnerBase`CancellationToken + 状态枚举 + 统一异常/日志):统一所有后台任务线程模型。
- `ApiResult<T>`(统一 HTTP 响应),HTTP 鉴权中间件。
- `IEndpointProvider`/配置注入:消灭硬编码 IP/URL/路径。
- `ICarTransport`(车辆通信抽象):把 HttpClient 从车型类剥离,单例化。
---
## 五、net8.0 去 Windows 依赖路径(解 S2
1. **隔离 UI**:所有 `*Form/*Viewer/*Monitor + *.Designer.cs`12+)迁 `StandardScene.UI.WinForms`(唯一 `net8.0-windows`+`UseWindowsForms`)。
2. **去 MessageBox**:业务层 `MessageBox.Show`Commons/Kiva/各 Manager)改为事件/`ILogger`,弹窗交 UI 层。
3. **核对 Windows-only API**:去掉 CA1416 抑制后逐项消解(P/Invoke 控制台显隐等收口到宿主)。
4. **切 TFM**Abstractions/Infrastructure/Core/卫星改 `net8.0`;仅 UI 与(如需)宿主保留 `-windows`
---
## 六、分阶段迁移路径(低风险·每阶段 build-green·可回归)
> 延续既有"路线乙":**先结构移动(无逻辑变更)→ 再去重/解耦**,每步可独立验证。
- **A 抽象层奠基**:建 `StandardScene.Abstractions`,**纯移动**接口/特性/字段袋/纯模型(`ILoopRules``*TypeAttribute``BasicFields``LoopPoint/LoopTask` 等)。解 S6/S11。
- **B 基础设施收口**:建 `Infrastructure`TCP/Utils/CommonTools+`ILogger`;修 `AsyncTcpClient` 异常类型/EndWrite。解 S1(部分)。
- **C UI 隔离**:建 `UI.WinForms`,移走全部窗体;业务去 `MessageBox`。解 S2,打通去 `-windows`
- **D 协议/设备依赖下放**MQTT→VDA5050、Modbus/IoTClient→Devices、Nancy→WebApi、OpenXml→报表所在卫星;Core.csproj 清空专有 NuGet。解 S1。
- **E 充电独立**:抽 `StandardScene.Charge` 卫星(Mission/抽象/配置/通信),表单已在 UI 层。解 S4。
- **F 任务线程统一**:落地 `MissionRunnerBase`,迁移 Scheduler/Chained/Charge 后台循环(在 P0-1 协作式停止基础上)。解 S3(并发面)。
- **G WebApi 重构**`ApiResult`+按资源拆分+鉴权中间件;老接口归 deprecated。解 S3/S9WebApi)。
- **H 巨类拆分**AbstractLoopMission/ChainedDeliveryMission/AbstractChargeLogicMission/Kiva 按职责拆分。解 S3。
- **I 切 net8.0 + 清死文件/绝对路径**:TFM 收敛;删 `AbstractChainedDeliveryMission` 等死文件;`HintPath` 改相对/包变量。解 S2/S9/S10。
- **J 导航卫星(依赖内核)**:待内核放开 Coder 注册表(S8),抽磁/二维码/激光导航卫星。
每阶段出口标准:`dotnet build` 0 错误、警告不增、关键路径冒烟可过。
---
## 七、模块处置矩阵(速查)
| 模块 | 现位置 | 目标位置 | 关键动作 |
|---|---|---|---|
| 接口/特性/字段袋/纯模型 | Core 各处 | **Abstractions** | 纯移动 |
| TCP/Utils/CommonTools | Core | **Infrastructure** | 移动+`ILogger`+修 TCP |
| 全部窗体/Viewer/Monitor | 各模块 | **UI.WinForms(deprecated)** | 移动+去 MessageBox |
| 充电(任务/抽象/配置/通信) | Core/Charge | **StandardScene.Charge** | 卫星化 |
| 充电桩具体驱动 | Devices | Devices 或并入 Charge | 统一 IDisposable |
| 门/按钮盒 抽象+特性 | Core | Abstractions | 下沉 |
| 门/按钮盒 驱动 | Devices | Devices | IDisposable |
| MQTT/Modbus/IoTClient/Nancy/OpenXml | Core NuGet | 各卫星/WebApi | 依赖下放 |
| WebApi | Core 单文件 | **StandardScene.WebApi** | 拆分+鉴权+ApiResult |
| 车型 | Core/CarTypes | Core(领域) | 剥离通信/UI 协作者 |
| Coders | Core/Coders | Core(→导航卫星 J 阶段) | 继续收敛 |
| Commons/Heuristic/LadderLogic/CAD | Core 根 | 按职责归子模块 | 拆分+配置化 |
---
## 八、风险与约束
1. **内核耦合(SimpleLite/SimpleCore**:Coder 注册表按内核程序集反射(S8)→导航卫星热插拔需内核改造;`UiTypeDiscovery.AllTypes()` 已支持跨程序集发现(设备/车型可行)。
2. **无 Git 基线**:大量文件未纳入版本控制 → **强烈建议先建 Git 基线**再执行 A~J,保证可回滚。
3. **行为等价**:车型/充电/VDA5050 含设备协议时序,移动需保持时序与字段语义(沿用"先移动后去重")。
4. **绝对 HintPath/本地 dll**:迁移期保持 HintPath 可用,I 阶段统一相对化,避免中途断链。
5. **migu 平台 UI**UI.WinForms 仅过渡;接口层(Abstractions/WebApi)应面向 migu 提供稳定契约,UI 迁移后整包删除。
---
## 九、近期可立即执行(已具备条件,低风险)
1. 删除/归档死文件 `AbstractChainedDeliveryMission.cs`(已 Compile Remove)。
2. 新建 `StandardScene.Abstractions`,先迁 `BasicFields``*TypeAttribute``Loop/ILoopRules`(解 S6/S11,且不改逻辑)。
3. `MissionRunnerBase` 抽取(承接 P0-1 协作式停止成果,统一 Scheduler 四任务)。
4. `ApiResult` 帮助器(先在 WebApi 内消重,零行为变化)。