16 KiB
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,精读确认):
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)暴露:
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 语义,配置不当会暴露在内网甚至外网,构成命令执行级攻击面。
修复方向:
- 最小化:加访问令牌/来源 IP 限制(中间件统一校验)。
- 方法白名单:仅允许标注了
[MethodMember](或新增[WebInvokable])的方法被反射调用;拒绝其余。 - 语义化:执行类操作改
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:
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:619public new async void ForceStop()(精读确认):async void,catch 内MessageBox.Show(UI 耦合),最外层Console.WriteLine(e); throw;——async void抛出的异常无法被调用方捕获,直接打到SynchronizationContext/线程池,可致进程崩溃。new还隐藏基类ForceStop(CS0114)。VDA5050Car.cs:240throw 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。GoSitecatch 内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:本轮已完成磁导航统一与避障去重(
AvoidanceParamCoder4 参 +AvoidanceParamLWCoder2 参),结构清晰,建议继续把车型内联 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,最高优先级重构对象。
九、正面样板(建议作为团队基线)
StandardScene.Devices/Door/ModbusDoorController.cs:CancellationTokenSource协作式停止、lock线程安全、变更检测(_lastSentControl)、重连节流、统一Diagnosis.Log、资源清理。StandardScene.Core/TCP/AsyncTcpClient.cs:锁保护、陈旧回调防护、自动重连。StandardScene.Core/Charge/CommunicationMessageService.cs:报文解析前置长度校验(安全解析范本)。
十、整改路线图
第 1 批(P0,本次执行)
- Thread.Abort → 协作式停止(6 文件 8 处)。
- WebApi 反射 execute 加来源校验 +
[MethodMember]白名单。 - 充电实时报文路径补长度校验(复用安全解析)。
第 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 等。