13 KiB
SimController 及相关接口实现建议 v4-2(结合当前工程现状)
本文档基于 情景模拟分析结果v3.md 第 3 条,并结合当前可运行代码的真实调用链,给出可落地的实现建议。
0. 现状盘点(必须先对齐)
0.1 当前“线上可用”的仿真接口在哪里
目前仿真入口实际在 ProjectController,而不是 SimController:
- 初始化:
POST /projects/simulation/init→ ProjectController.java:L275-L300 → ProjectServiceImpl.initSimulation - 运行:
POST /projects/simulation/run→ ProjectController.java:L288-L300 → ProjectServiceImpl.runSimulation
0.2 SimController 为什么不可用
SimController.java 目前是“原型草稿”,在本仓库中无法落地,原因是:
- 引用的
ProjectRepository/EventRepository/InfluenceRepository/InferenceConverter仅在该文件出现,仓库中不存在真实实现。 SimBuilder.buildUnits/buildEvents/buildInfluenceNodes也不存在(SimBuilder.java 只有注释草稿)。SimService当前逻辑是 KV 级简化引擎,并且事件优先级与现有initSimulation的行为不一致(SimService.java)。
0.3 当前系统的“真实数据格式”
runSimulation 解析的是一种“frames → devices”结构,并按 deviceType 分组:
- 调用入口:ProjectServiceImpl.runSimulation
- 解析工具:DeviceDataParser.parseAndGroupDeviceData
- 单步模型:DeviceStepInfo.java
因此,“让 SimController 可用”的关键,不是从 0 造一套新格式,而是:复用现有 frames 格式(或在 SimController 内把 SimContext 转为该格式),以便直接复用 DeviceDataParser + DeviceInferService 的整条推理/落库链路。
1. 建议的目标形态:SimController 成为“仿真编排入口”
建议把 SimController 从“原型”升级为生产入口,但要遵循当前工程已存在的服务边界:
1.1 目标职责划分(贴合当前代码)
- SimController:只处理 HTTP + 参数校验 + 返回值结构统一(保持与 ProjectController 相同的
{code,msg,data}或复用统一响应体)。 - SimulationFacade(新增):负责把“现有 services + topology JSON + event 表”组装成可计算输入;并在需要时调用推理、落库、更新情景状态。
- SimulationEngine(新增或重构现有 SimService):只做“时序帧生成/影响计算/事件注入”,纯内存计算,不碰 DB。
- Converter(新增):把 Engine 输出转成
DeviceDataParser能吃的 frames JSON(或直接输出 Map 结构)。
这样做的好处:既保留 SimController 分层方向,又不引入仓库里不存在的 Repository/Converter 类型。
2. 具体落地建议(按当前文件/类名对齐)
2.1 先做最小可用:让 /sim 接口复用现有 ProjectServiceImpl 的能力
为了快速验证链路,第一阶段建议不要立刻重写引擎,而是“搬运+封装”:
- 新增
SimulationFacade(建议放在com.yfd.business.css.service.sim包)
内部依赖现有 service:ProjectService/ScenarioService/EventService/DeviceInferService(以及MaterialService若需要 DB 补全静态物料属性)。
它暴露两类能力:
init(projectId, scenarioId, params):复用 ProjectServiceImpl.initSimulation 或将其中解析/计算段迁移出来。run(projectId, scenarioId, params):复用 ProjectServiceImpl.runSimulation。
- SimController 直接调用 facade,并提供与 ProjectController 一致的返回结构
这样能做到:新增/sim/*不影响现有/projects/simulation/*,并且复用现有稳定链路。
这一阶段的目标是:先让 SimController 可用、可回归测试、可逐步迁移。
2.2 第二阶段:把 initSimulation 拆成“解析/计算/输出”三个可替换模块
当前 initSimulation 里混杂了:拓扑解析、设备顺序、静态注入、影响计算、事件解析、帧输出。建议拆成 3 个模块,便于未来替换而不改 API:
A. Topology & Static 解析模块(建议:TopologyParser)
直接复用现有实现(迁移或抽取):
- 设备顺序:
parseDeviceOrder(projectId):ProjectServiceImpl.java:L370-L397 - 设备-物料绑定:
buildDeviceMaterialMap:ProjectServiceImpl.java:L1063-L1090 - 静态/影响解析:
- 设备:
parseDeviceStaticsAndInfluences:ProjectServiceImpl.java:L806-L860 - 物料:
parseMaterialStaticsAndInfluences:ProjectServiceImpl.java:L861-L928
- 设备:
- DB 物料静态补全:
buildMaterialStaticFromDb:ProjectServiceImpl.java:L1116-L1141 - 设备 size 注入:
injectDeviceSize:ProjectServiceImpl.java:L776-L804
建议输出一个结构化的 DTO,例如:
List<Device> orderedDevicesMap<deviceId, deviceType>Map<deviceId, materialId>devStatic/devInfluence/matStatic/matInfluence/matStaticDb
B. Event 解析模块(建议:EventScheduleBuilder)
直接复用现有 attr_changes 解析逻辑:
buildValueProviders:ProjectServiceImpl.java:L929-L996collectTimePoints:ProjectServiceImpl.java:L1037-L1061readValue:ProjectServiceImpl.java:L998-L1035
补强建议(贴合现状的缺口):
- 当
timePoints为空时不要直接失败:允许用 params 提供start/end/interval生成时间网格(否则“无事件仿真”无法跑)。 - ramp 事件目前仅把起止时刻作为输出点;若前端想看平滑曲线,需要补齐采样点(例如每 1s)。
C. Frame 生成模块(建议:FrameGenerator)
复用 initSimulation 的生成循环,但把“覆盖策略”参数化:
- 现在的覆盖策略是:Static → Influence → overrideWithEvents(最终覆盖)
代码:overrideWithEvents - 建议支持两类事件:Input 与 Override(可先用 params 开关模拟)
- Input:计算前注入
- Override:计算后强制覆盖
这样第三阶段才需要动到“事件类型”定义;第二阶段只要把钩子留好即可。
3. SimService/SimModel 如何与现有链路对接(不要重新造轮子)
3.1 现有 SimModel 的适配建议(只做必要改造)
当前 SimModel 过于抽象(纯 KV),无法表达“设备/物料静态属性注入、deviceType、material 绑定”等现有业务关键点:
SimUnit只有unitId/deviceId/materialId/deviceType:SimUnit.javaSimContext只有Map<SimPropertyKey, Double>的 currentValues:SimContext.java
建议的最小增强(为了能生成当前 runSimulation 可消费的 frames):
SimUnit增加Map<String, Double> staticProps(至少承载 diameter/height 与物料关键属性)。SimEvent增加boolean override或扩展 EventType(区分 Input/Override)。SimInfluenceSource.delay目前未在SimService使用;若要支持 delay,必须用ctx.timeline回看历史值(当前SimContext已能保留 step 的快照)。
3.2 Converter:把 SimContext 输出转换成现有 frames 格式
建议增加一个 Converter(替代原型里的 InferenceConverter),输出结构与 DeviceDataParser 一致:
{
\"data\": {
\"frames\": [
{
\"step\": 0,
\"time\": 0,
\"devices\": {
\"dev-001\": {\"deviceType\": \"CylindricalTank\", \"diameter\": 20, \"height\": 20, \"u_concentration\": 20}
}
}
]
}
}
理由:这样 SimController 可以直接调用 DeviceDataParser.parseAndGroupDeviceData + DeviceInferService.processDeviceInference,与现有落库路径完全一致。
4. SimController 的接口形态建议(与现有系统兼容)
建议同时支持“两段式”和“一段式”,避免推倒重来:
4.1 两段式(兼容现有前端/流程)
POST /sim/init:返回 frames(与/projects/simulation/init对齐)POST /sim/run:接收 frames,调用推理并落库(与/projects/simulation/run对齐)
4.2 一段式(面向后端批处理/自动化)
POST /sim/run-all:内部调用 init 生成 frames,再立即 run 推理落库,返回摘要(如 snapshots、结果条数、耗时)
5. 迁移与风险控制(建议强制执行)
- 先引入新接口,不删旧接口:让
/sim/*与/projects/simulation/*并行一段时间。\n\n2) 帧格式不变:任何新实现必须输出DeviceDataParser可解析的结构,否则推理链路与前端都要一起改,风险最大。\n\n3) 行为一致性测试:对比新旧 init 输出(同 projectId/scenarioId)是否一致;对比 run 后写入scenario_result条数与 key 字段是否一致。\n\n4) 清理 System.out.println:ProjectServiceImpl的 init/run 里有大量System.out.println(例如 ProjectServiceImpl.java:L644-L661 与 runSimulation debug),建议迁移到统一日志体系后再移除,以免污染生产日志与性能。\n\n---\n\n## 6. 最小实现清单(按优先级排序)\n\n- 建议 1:让SimController先“可用”——删除/替换不存在的 Repository/Converter 依赖,改用现有 Service。\n- 建议 2:新增SimulationFacade,把现有ProjectServiceImpl.initSimulation/runSimulation先封装起来。\n- 建议 3:逐步抽取TopologyParser/EventScheduleBuilder/FrameGenerator,把ProjectServiceImpl中的计算逻辑迁移出来。\n- 建议 4:最后再考虑把增强后的SimService/SimModel正式替换成唯一引擎实现。\n+