268 lines
16 KiB
Markdown
268 lines
16 KiB
Markdown
# StandardScene 代码审查报告
|
||||
|
|
|
|||
|
|
> 审查对象:`E:\Work\Core\Simple-FR\StandardSence`(StandardScene.Core / StandardScene.Devices / StandardScene.Protocol.VDA5050)
|
|||
|
|
> 审查方式:全量反模式扫描(ripgrep)+ 关键文件精读 + 编译告警分类(dotnet build --no-incremental)
|
|||
|
|
> 目标框架:`net8.0-windows`(最终目标 `net8.0`)
|
|||
|
|
> 报告日期:2026-06-09
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 一、审查范围与方法
|
|||
|
|
|
|||
|
|
- **代码规模**:113 个 `.cs` 文件;最大文件 `WebApi.cs`(2661 行)、`AbstractLoopMission.cs`(1858 行)。
|
|||
|
|
- **扫描维度**:并发/线程、异常处理、报文解析边界、资源管理、配置硬编码、日志、UI/业务耦合、安全、编译告警。
|
|||
|
|
- **证据标注**:
|
|||
|
|
- `精读确认`:已逐行读取、行号精确。
|
|||
|
|
- `扫描命中`:ripgrep 命中文件级,行号待整改时逐一核对。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 二、总体评价与子系统评分
|
|||
|
|
|
|||
|
|
| 子系统 | 评分 | 说明 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| 设备驱动(StandardScene.Devices·新) | ★★★★☆ | `ModbusDoorController` 工程化优秀,可作团队样板 |
|
|||
|
|
| TCP 基础设施(AsyncTcpClient) | ★★★★☆ | 锁/陈旧回调防护/重连完善,少量瑕疵 |
|
|||
|
|
| Coders(重构后) | ★★★★☆ | 本轮已去重,结构清晰 |
|
|||
|
|
| CarTypes 车型族 | ★★☆☆☆ | 巨类、async void、空 catch、硬编码集中 |
|
|||
|
|
| 任务族 Missions | ★★☆☆☆ | Thread.Abort、while(true)+Sleep、状态机分散 |
|
|||
|
|
| 充电 Charge | ★★☆☆☆ | 实时报文路径越界风险、UDP 线程模型粗放 |
|
|||
|
|
| VDA5050 协议 | ★★☆☆☆ | 硬编码 IP、async void、throw ex、Console 日志 |
|
|||
|
|
| WebApi(老 Nancy) | ★☆☆☆☆ | 2661 行上帝文件、无鉴权反射调用、模板代码爆炸 |
|
|||
|
|
| Commons / 公共层 | ★★☆☆☆ | 上帝工具类、重复实现、隐性 bug |
|
|||
|
|
|
|||
|
|
**核心判断**:技术债**集中在旧模块**(CarTypes / Missions / Charge / VDA5050 / WebApi / Commons);**新写模块**(Devices、AsyncTcpClient)质量明显更高,说明团队具备写好代码的能力,债务主要是历史遗留。整改应"以新模块为范本、按子系统收敛旧债"。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 三、问题严重级别汇总
|
|||
|
|
|
|||
|
|
| 级别 | 含义 | 主要条目 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| **P0 阻断** | .NET8 下会崩溃/失效,或存在安全风险 | Thread.Abort 被吞、WebApi 无鉴权反射、实时报文越界 |
|
|||
|
|
| **P1 高危** | 生产环境易触发故障/难排障 | 硬编码 IP、async void+throw、空 catch、HttpClient 滥用 |
|
|||
|
|
| **P2 结构** | 可维护性/扩展性差 | 上帝类、重复代码、Console 日志、UI/业务耦合 |
|
|||
|
|
| **P3 整洁** | 编译告警与代码卫生 | 45 项告警(重复 using、未用字段、隐藏成员等) |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 四、P0 阻断级问题
|
|||
|
|
|
|||
|
|
### P0-1 `Thread.Abort()` 在 .NET8 必抛异常且被静默吞掉(线程停不掉)
|
|||
|
|
|
|||
|
|
`Thread.Abort()` 在 .NET8 抛 `PlatformNotSupportedException`(告警 SYSLIB0006,共 14 条/双配置)。多处 `Abort()` 外层是空 `catch{}`,导致**异常被吞、线程实际未停止**——任务"停止"后后台线程仍在跑,造成重复下发、资源泄漏、状态错乱。
|
|||
|
|
|
|||
|
|
精读确认命中点:
|
|||
|
|
|
|||
|
|
| 文件 | 行 |
|
|||
|
|
|---|---|
|
|||
|
|
| `StandardScene.Core/Chained/AbstractLoopMission.cs` | 1314 `_strategyThread?.Abort()`、1329 `_logicThread?.Abort()` |
|
|||
|
|
| `StandardScene.Core/Charge/StandardChargeMission.cs` | 681 `myThread?.Abort()`、684 `ChargeThread?.Abort()` |
|
|||
|
|
| `StandardScene.Core/Scheduler/SecuritySignalMission.cs` | 176 `myThread?.Abort()` |
|
|||
|
|
| `StandardScene.Core/Scheduler/NodeIsEnableMission.cs` | 121 `myThread?.Abort()` |
|
|||
|
|
| `StandardScene.Core/Scheduler/HeartBeatMission.cs` | 68 `_myThread?.Abort()` |
|
|||
|
|
| `StandardScene.Core/Chained/AbstractChainedDeliveryMission.cs` | 1606 `myThread.Abort()` |
|
|||
|
|
|
|||
|
|
典型代码(`AbstractLoopMission.cs:1309`,`精读确认`):
|
|||
|
|
|
|||
|
|
```1309:1334:E:\Work\Core\Simple-FR\StandardSence\StandardScene.Core\Chained\AbstractLoopMission.cs
|
|||
|
|
public virtual void StopLoop()
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
_strategyRunning = false;
|
|||
|
|
_strategyThread?.Abort();
|
|||
|
|
_strategyThread = null;
|
|||
|
|
...
|
|||
|
|
}
|
|||
|
|
catch { }
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**修复方向**:改为**协作式停止**——已有 `_strategyRunning/_logicRunning` 布尔标志,循环体应周期检查该标志退出;线程创建用 `IsBackground=true`,停止时 `flag=false` 后 `Join(timeout)`。删除所有 `Abort()`。对阻塞型循环用 `CancellationToken` + 可中断等待(`Task.Delay(token)` / `ManualResetEventSlim.Wait(token)`)替代 `Thread.Sleep`。参考 `ModbusDoorController` 的 `CancellationTokenSource` 范式。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### P0-2 WebApi 反射接口:无鉴权远程调用任意方法(RCE 级风险)
|
|||
|
|
|
|||
|
|
`StandardScene.Core/WebApi.cs`(`精读确认`,272–314 / 332–368)暴露:
|
|||
|
|
|
|||
|
|
```272:296:E:\Work\Core\Simple-FR\StandardSence\StandardScene.Core\WebApi.cs
|
|||
|
|
Get("/car_reflection/execute/{id}/{method}", parameters =>
|
|||
|
|
{
|
|||
|
|
...
|
|||
|
|
methodInfo = car.GetType().GetMethod((string)parameters.method);
|
|||
|
|
...
|
|||
|
|
return ExecuteMethod(methodInfo, car, queryParams);
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`mission_reflection/execute/{id}/{method}` 同款。问题:**通过 HTTP 即可按名反射调用 Car/Mission 上任意 public 方法**,无身份校验、无方法白名单、无危险方法拦截。结合 `GET` 语义,配置不当会暴露在内网甚至外网,构成命令执行级攻击面。
|
|||
|
|
|
|||
|
|
**修复方向**:
|
|||
|
|
1. 最小化:加访问令牌/来源 IP 限制(中间件统一校验)。
|
|||
|
|
2. 方法白名单:仅允许标注了 `[MethodMember]`(或新增 `[WebInvokable]`)的方法被反射调用;拒绝其余。
|
|||
|
|
3. 语义化:执行类操作改 `POST`;统一错误响应封装(见 P2-2)。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### P0-3 实时报文解析按固定下标取值、缺长度校验(IndexOutOfRange 崩溃)
|
|||
|
|
|
|||
|
|
对比发现两条解析路径**风险等级不同**:
|
|||
|
|
|
|||
|
|
- ✅ **正确范本**:`StandardScene.Core/Charge/CommunicationMessageService.cs`(`精读确认`)在取下标前先校验 `parts.Length < 30 / < 10`(198、276 行),再访问 `bytes[28]` 等,越界已被防住。
|
|||
|
|
- ❌ **风险路径**:实时 UDP/回调路径按固定下标直接取值,未见等价长度校验(`扫描命中`):
|
|||
|
|
- `StandardScene.Core/ChargeUdpService.cs`(`message[28]` 等)
|
|||
|
|
- `StandardScene.Devices/Charge/PCBChargeStation.cs`(`OnUdpMessage` 中 `message[1]`)
|
|||
|
|
- `StandardScene.Core/ChargeStationType/MuXingChargeStation.cs`
|
|||
|
|
|
|||
|
|
设备掉线/半包/异常帧时,直接 `IndexOutOfRangeException`,且若发生在 UDP 接收线程会拖垮整条接收链路。
|
|||
|
|
|
|||
|
|
**修复方向**:所有报文解析入口统一"先校验长度(与帧头/类型匹配)再取值",越界返回 null 并 `Diagnosis.Log` 记录原始帧;抽出 `ChargeFrameParser` 复用 `CommunicationMessageService` 的安全解析。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 五、P1 高危问题
|
|||
|
|
|
|||
|
|
### P1-1 配置硬编码(IP/URL/路径),多 AGV/换环境必失效(`扫描命中` 14 文件)
|
|||
|
|
|
|||
|
|
最典型(`精读确认`)`StandardScene.Protocol.VDA5050/VDACar/VDA5050Car.cs:150`:
|
|||
|
|
|
|||
|
|
```150:168:E:\Work\Core\Simple-FR\StandardSence\StandardScene.Protocol.VDA5050\VDACar\VDA5050Car.cs
|
|||
|
|
public async void GetVDA5050StateFromC()
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
//string jsonResponse1 = await hc.GetStringAsync($"http://{this.address}:8008/getStat");
|
|||
|
|
string jsonResponse1 = await hc.GetStringAsync($"http://192.168.2.1:8008/getStat");
|
|||
|
|
...
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
把按车 `this.address` 的写法**注释掉、写死 `192.168.2.1`**——多 AGV 场景必然全部打到同一地址;外加空 catch 吞错,故障"静默"。其余命中:`Kiva.cs`/`Forklift.cs`(`192.168.2.1:8008`)、`Map.cs`(`127.0.0.1:4321`)、`LoopMission.cs`(西门子 PLC IP)、`TransportDeliveryCallbacks.cs`(回调 URL)、`StandardCADTool.cs`(Windows 盘符路径)等。
|
|||
|
|
|
|||
|
|
**修复方向**:抽 `scene.json`/配置中心统一注入;禁止业务代码内联端点;`StandardCADTool` 路径改相对/可配置。
|
|||
|
|
|
|||
|
|
### P1-2 `async void` + 在其中 `throw`(CA2200 失栈)→ 进程级崩溃风险
|
|||
|
|
|
|||
|
|
- `Kiva.cs:619` `public new async void ForceStop()`(`精读确认`):`async void`,catch 内 `MessageBox.Show`(UI 耦合),最外层 `Console.WriteLine(e); throw;`——`async void` 抛出的异常无法被调用方捕获,直接打到 `SynchronizationContext`/线程池,可致进程崩溃。`new` 还隐藏基类 `ForceStop`(CS0114)。
|
|||
|
|
- `VDA5050Car.cs:240` `throw ex;`(`精读确认`)破坏原始堆栈(CA2200,全仓 4 条)。`DummyCar.cs` 同款。
|
|||
|
|
- `async void` 在 CarTypes/Charge/VDA5050/CAD 等多文件广泛存在(`扫描命中`)。
|
|||
|
|
|
|||
|
|
**修复方向**:事件处理器之外一律 `async Task`;确需 `async void` 的入口用 `try/catch` 兜底并 `Diagnosis.Log`,不得外抛;`throw ex;` → `throw;`。
|
|||
|
|
|
|||
|
|
### P1-3 空 `catch{}` 静默吞异常(`扫描命中` 10 文件)
|
|||
|
|
|
|||
|
|
`Kiva.cs`、`VDA5050Car.cs`、`FLChargeStation.cs`、`AbstractLoopMission.cs`、`Commons.cs`、`AsyncTcpClient.cs`、`LoopViewer.cs` 等。`Kiva.cs:606`、`VDA5050Car.cs:165` 为典型业务路径空 catch。
|
|||
|
|
|
|||
|
|
> 注:`AsyncTcpClient` / `ModbusDoorController` 中环绕 `Close()/Dispose()` 的空 catch 属可接受的清理兜底,应保留但加注释;业务路径空 catch 必须改为记录日志。
|
|||
|
|
|
|||
|
|
### P1-4 同步阻塞调用 `.Result/.Wait()/GetAwaiter().GetResult()`(`扫描命中` 7 文件)
|
|||
|
|
|
|||
|
|
`ButtonMission.cs`、`DoorMission.cs`、`VDA5050Car.cs` 等。`Kiva.cs:635 status.programs.task.Wait()`(`精读确认`)在 UI/线程上下文易死锁。**修复**:异步链路打通,避免 sync-over-async。
|
|||
|
|
|
|||
|
|
### P1-5 `new HttpClient()` 反复实例化(Socket 耗尽)(`扫描命中`)
|
|||
|
|
|
|||
|
|
`Forklift.cs`/`Kiva.cs`/`VDA5050Car.cs`/`WebApi.cs` 等频繁 `new HttpClient`。**修复**:单例或 `IHttpClientFactory`/`SocketsHttpHandler`(设 `PooledConnectionLifetime`)。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 六、P2 结构 / 质量问题
|
|||
|
|
|
|||
|
|
### P2-1 `Commons.cs` 上帝工具类(`精读确认`)
|
|||
|
|
- `AddOrUpdateCarField/SiteField/MissionField` "ContainsKey→Remove+Add / else Add" 模板**重复 4 份**,应为 `dict[key]=value`。
|
|||
|
|
- `AddOrUpdateTag` 名为 update 实为 add(更新逻辑被注释),重复键会抛错,命名误导。
|
|||
|
|
- `CarValue`:存在 `electricCurrent` 时**忽略入参 key** 直接返回,疑似 bug。
|
|||
|
|
- `GoSite` catch 内 `Console.WriteLine + Thread.Sleep(3000)`,注释写"重新执行"但**并未真正重试**。
|
|||
|
|
- 调度 `NearestTask`(约 398–538)巨函数、深嵌套、大段 `#region ObsoleteCode` 注释代码、魔法优先级 `Priority=50`。
|
|||
|
|
|
|||
|
|
### P2-2 `WebApi.cs` 2661 行上帝文件(`精读确认`)
|
|||
|
|
错误响应 `new { Success=false, Code=500, Data="null", Message=... }` 模板**复制几十处**;路由全堆一个文件。**修复**:抽 `ApiResult.Fail/Ok` 帮助器;按资源拆分路由模块;老接口归入 `WebApi.Core(deprecated)` 并规划迁移。
|
|||
|
|
|
|||
|
|
### P2-3 日志体系不统一(`扫描命中` 22 文件 `Console.WriteLine`)
|
|||
|
|
与 `Diagnosis.Log` 混用。`ModbusDoorController` 已全程 `Diagnosis.Log`,应作为统一范式推广;禁用 `Console.WriteLine` 于业务代码。
|
|||
|
|
|
|||
|
|
### P2-4 UI 与业务耦合(`扫描命中`)
|
|||
|
|
`MessageBox.Show` 出现在 `Commons.cs`(TrafficControl.OnDeadLock)、`Kiva.cs:648`、`DoorManager.cs`、`ButtonBoxManager.cs` 等业务/管理类中。**修复**:业务层只发事件/日志,弹窗交由 UI 层(后续 migu 平台)。
|
|||
|
|
|
|||
|
|
### P2-5 `while(true)+Thread.Sleep` 忙等/阻塞(`扫描命中` 13 文件)
|
|||
|
|
`Forklift.cs`、`StandardChargeMission.cs`、`ChargeUdpService.cs`、`VDA5050Car.cs` 等。**修复**:改 `CancellationToken`+可中断等待或定时器;与 P0-1 协作式停止一并处理。
|
|||
|
|
|
|||
|
|
### P2-6 AsyncTcpClient 细节(`精读确认`)
|
|||
|
|
- `Send` 失败抛 `InvalidProgramException`(异常类型不当,应 `InvalidOperationException`/自定义)。
|
|||
|
|
- `HandleDatagramWritten` 调 `EndWrite` 无 try,写失败异常落到线程池无人观测。
|
|||
|
|
- `uint on = 1;` 未使用字段(CS0414)。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 七、编译告警分类(全量,双配置合计 90 实例 ≈ 45/配置)
|
|||
|
|
|
|||
|
|
| 告警码 | 数量 | 含义 | 处置 |
|
|||
|
|
|---|---|---|---|
|
|||
|
|
| CS0108 | 16 | 隐藏继承成员未加 `new` | 显式 `new`/`override` 或改名 |
|
|||
|
|
| CS4014 | 14 | 调用未 `await`(即发即弃) | 显式 `await` 或 `_ =` 并说明 |
|
|||
|
|
| CS0414 | 14 | 私有字段赋值但从未使用 | 删除 |
|
|||
|
|
| SYSLIB0006 | 14 | `Thread.Abort` 已弃用 | 见 **P0-1** |
|
|||
|
|
| CS0105 | 6 | 重复 using | 删除 |
|
|||
|
|
| CS0162 | 6 | 不可达代码 | 清理 |
|
|||
|
|
| CS0168 | 6 | 变量声明未用 | 删除 |
|
|||
|
|
| CA2200 | 4 | `throw ex` 失栈 | 改 `throw;` |
|
|||
|
|
| CS8321 | 2 | 局部函数未用 | 删除(如 VDA5050Car `monitor()`) |
|
|||
|
|
| CS0169 | 2 | 字段从未使用 | 删除 |
|
|||
|
|
| CS0114 | 2 | 隐藏继承成员(如 ForceStop) | `override`/`new` |
|
|||
|
|
| CS0219 | 2 | 变量赋值未用 | 删除 |
|
|||
|
|
| CS0649 | 2 | 字段从未赋值 | 初始化或删除 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 八、分子系统评述(摘要,详版见架构方案)
|
|||
|
|
|
|||
|
|
- **CarTypes**:`BasicCarFields/SiteFields/TrackFields/PlanFields` 字段袋设计合理(`BasicFields.cs`,`CarLength/CarWidth` 默认 `-1` 作"未配置"哨兵,已被避障 Coder 正确利用);但具体车型(Kiva/Forklift/VDA5050Car)是巨类,混杂通信、状态、UI、调度,async void/空 catch/硬编码集中。
|
|||
|
|
- **Coders**:本轮已完成磁导航统一与避障去重(`AvoidanceParamCoder` 4 参 + `AvoidanceParamLWCoder` 2 参),结构清晰,建议继续把车型内联 Coder 收敛到 `CommonTrackCoders`。
|
|||
|
|
- **Missions**:Chained/InterLock/Scheduler 线程模型粗放(裸 `new Thread`+`Abort`+`while(true)`),状态机散落字符串 `status.status`。建议统一 `MissionRunnerBase`(CancellationToken + 状态枚举)。
|
|||
|
|
- **Charge**:解析存在"安全范本"与"风险路径"并存(见 P0-3);UDP 服务与 Mission 线程耦合。
|
|||
|
|
- **Devices(新)**:`ModbusDoorController` 优秀;唯一瑕疵是用析构函数兜底 `Disconnect`(在 GC 线程取锁+`Wait`,有风险),应实现 `IDisposable` 显式释放。
|
|||
|
|
- **VDA5050**:MQTT/HTTP 异步用法不规范(async void、throw ex、硬编码 IP、Console 日志),状态处理 `ProcessCacheAndSendOrderMessage` 内大量 Console。
|
|||
|
|
- **WebApi**:见 P0-2 / P2-2,最高优先级重构对象。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 九、正面样板(建议作为团队基线)
|
|||
|
|
|
|||
|
|
1. `StandardScene.Devices/Door/ModbusDoorController.cs`:`CancellationTokenSource` 协作式停止、`lock` 线程安全、变更检测(`_lastSentControl`)、重连节流、统一 `Diagnosis.Log`、资源清理。
|
|||
|
|
2. `StandardScene.Core/TCP/AsyncTcpClient.cs`:锁保护、陈旧回调防护、自动重连。
|
|||
|
|
3. `StandardScene.Core/Charge/CommunicationMessageService.cs`:报文解析前置长度校验(安全解析范本)。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 十、整改路线图
|
|||
|
|
|
|||
|
|
**第 1 批(P0,本次执行)**
|
|||
|
|
1. Thread.Abort → 协作式停止(6 文件 8 处)。
|
|||
|
|
2. WebApi 反射 execute 加来源校验 + `[MethodMember]` 白名单。
|
|||
|
|
3. 充电实时报文路径补长度校验(复用安全解析)。
|
|||
|
|
|
|||
|
|
**第 2 批(P1)**
|
|||
|
|
4. 硬编码端点配置化(VDA5050/Kiva/Forklift/Map/Loop/CAD)。
|
|||
|
|
5. `async void`→`Task`、`throw ex`→`throw`、业务空 catch 加日志。
|
|||
|
|
6. HttpClient 单例化;sync-over-async 拆解。
|
|||
|
|
|
|||
|
|
**第 3 批(P2/结构)**
|
|||
|
|
7. `ApiResult` 帮助器 + WebApi 拆分;`Commons` 拆服务、修 `CarValue`/`GoSite`/`AddOrUpdate`。
|
|||
|
|
8. 统一 `Diagnosis.Log`;UI 解耦;`MissionRunnerBase` 统一线程/状态机。
|
|||
|
|
|
|||
|
|
**第 4 批(P3 卫生)**
|
|||
|
|
9. 清 45 项告警(重复 using、未用字段、不可达代码、隐藏成员)。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 附录:扫描方法
|
|||
|
|
|
|||
|
|
- 反模式:`rg` 扫描 `Thread.Abort` / `catch\s*\{\s*\}` / `throw ex;` / `async void` / `\.Result|\.Wait\(\)` / 硬编码 IP / `Console.WriteLine` / `while\s*\(\s*true\s*\)` / `new HttpClient` / `MessageBox.Show` / 魔法下标。
|
|||
|
|
- 告警:`dotnet build --no-incremental` → `_buildwarnings.txt` → 按告警码聚合计数。
|
|||
|
|
- 精读:AsyncTcpClient、Commons、CommunicationMessageService、VDA5050Car、WebApi、Kiva、AbstractLoopMission、ModbusDoorController、BasicFields 等。
|