153 lines
6.7 KiB
Markdown
153 lines
6.7 KiB
Markdown
# SimController 及相关接口实现建议 v4
|
||
|
||
基于 [情景模拟分析结果v3.md](file:///e:/projectJava/JavaProjectRepo/business-css/%E6%83%85%E6%99%AF%E6%A8%A1%E6%8B%9F%E5%88%86%E6%9E%90%E7%BB%93%E6%9E%9Cv3.md) 中的分析,`SimController` 及其配套组件 (`SimService`, `SimBuilder`, `SimModel`) 代表了系统向**模块化、可测试化**方向演进的正确路径。
|
||
|
||
当前 `SimController` 处于不可用状态(依赖缺失、逻辑简化、代码注释)。为了将其转化为生产可用的仿真服务,以下是具体的实现建议与重构方案。
|
||
|
||
---
|
||
|
||
## 1. 总体架构设计
|
||
|
||
目标是将仿真逻辑从 `ProjectServiceImpl` 中剥离,构建独立的仿真层。
|
||
|
||
* **Controller 层 (`SimController`)**: 仅负责接收 HTTP 请求,参数校验,调用 Service,返回结果。
|
||
* **Facade 层 (`SimDataFacade`)**: **新增组件**。负责与现有的 `ProjectService`, `EventService`, `InfluenceService` 交互,获取原始数据(Project 实体, Event 列表等),并屏蔽数据库细节。*注:原设计中的 `ProjectRepository` 等接口在本项目中没有实现,直接复用现有的 Service 层更符合现状。*
|
||
* **Builder 层 (`SimBuilder`)**: 负责将原始数据(Entity/JSON)转换为仿真专用的领域模型 (`SimUnit`, `SimEvent`, `SimInfluenceNode`)。
|
||
* **Engine 层 (`SimService`)**: **核心计算引擎**。纯内存计算,不依赖数据库。执行 `Static -> Event -> Influence -> Override` 的标准管线。
|
||
|
||
---
|
||
|
||
## 2. 详细实现建议
|
||
|
||
### 2.1 补齐数据获取层 (SimDataFacade)
|
||
|
||
原 `SimController` 依赖了不存在的 `ProjectRepository` 等接口。建议创建一个 `SimDataFacade` 来封装数据获取逻辑。
|
||
|
||
```java
|
||
@Component
|
||
public class SimDataFacade {
|
||
private final ProjectService projectService;
|
||
private final EventService eventService;
|
||
// ... 其他 Service
|
||
|
||
// 封装获取逻辑:获取项目拓扑、事件列表、影响关系等
|
||
public SimDataPackage loadSimulationData(String projectId, String scenarioId) {
|
||
Project project = projectService.getById(projectId);
|
||
List<Event> events = eventService.list(new QueryWrapper<Event>().eq("scenario_id", scenarioId));
|
||
// ... 获取其他必要数据
|
||
return new SimDataPackage(project, events);
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2.2 激活并增强 Builder (SimBuilder)
|
||
|
||
`SimBuilder` 目前被注释掉了,需要激活并实现核心转换逻辑。重点是将 `ProjectServiceImpl.initSimulation` 中的解析逻辑迁移过来。
|
||
|
||
* **`buildUnits`**: 解析 `Project.topology` JSON,提取 Device 和 Material,构建 `SimUnit` 列表。
|
||
* *关键点*:需要包含静态属性(Static Values)的解析,作为 `SimUnit` 的初始状态。
|
||
* **`buildEvents`**: 解析 `Event.attr_changes` JSON,构建 `SimEvent` 列表。
|
||
* *关键点*:区分 **普通事件 (Input)** 和 **强制覆盖事件 (Override)**。建议在 `SimEvent` 中增加 `isOverride` 标志。
|
||
* **`buildInfluenceNodes`**: 解析 `Project.topology` 中的 `properties` -> `influence` 节点,构建 `SimInfluenceNode` 列表。
|
||
|
||
### 2.3 重构计算引擎 (SimService)
|
||
|
||
这是最核心的部分,必须修正当前的“事件 -> 计算”逻辑,改为 **标准管线**。
|
||
|
||
**建议代码结构:**
|
||
|
||
```java
|
||
public class SimService {
|
||
|
||
public SimContext runSimulation(List<SimUnit> units,
|
||
List<SimEvent> events,
|
||
List<SimInfluenceNode> nodes,
|
||
int steps) {
|
||
SimContext ctx = new SimContext();
|
||
|
||
// 1. 初始化静态基线 (Static)
|
||
// 将 SimUnit 中携带的静态属性写入 ctx (t=0)
|
||
for (SimUnit unit : units) {
|
||
unit.getStaticProperties().forEach((k, v) ->
|
||
ctx.setValue(SimPropertyKey.of(unit.id(), k), v)
|
||
);
|
||
}
|
||
|
||
// 2. 时间步推进
|
||
for (int step = 0; step <= steps; step++) {
|
||
// 2.1 应用输入事件 (Event Input)
|
||
// 筛选当前 step 的普通事件,写入 ctx
|
||
applyEvents(ctx, events, step, false);
|
||
|
||
// 2.2 执行影响计算 (Influence)
|
||
// 基于当前 ctx 状态,计算所有 InfluenceNode
|
||
// 注意:为了避免计算顺序依赖,建议使用双缓冲 (Snapshot) 或 拓扑排序
|
||
// 简单实现可先计算 diff,再统一应用
|
||
applyInfluences(ctx, nodes);
|
||
|
||
// 2.3 应用强制覆盖事件 (Event Override)
|
||
// 筛选当前 step 的强制事件,再次写入 ctx,覆盖计算结果
|
||
applyEvents(ctx, events, step, true);
|
||
|
||
// 2.4 保存快照
|
||
ctx.snapshot(step);
|
||
}
|
||
return ctx;
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2.4 统一 API 接口 (SimController)
|
||
|
||
建议将 `SimController` 作为仿真功能的唯一入口。
|
||
|
||
```java
|
||
@RestController
|
||
@RequestMapping("/sim")
|
||
public class SimController {
|
||
|
||
@PostMapping("/run")
|
||
public Result<SimulationResult> run(@RequestBody SimulationRequest req) {
|
||
// 1. 加载数据
|
||
SimDataPackage data = simDataFacade.loadSimulationData(req.getProjectId(), req.getScenarioId());
|
||
|
||
// 2. 构建模型
|
||
List<SimUnit> units = SimBuilder.buildUnits(data.getProject());
|
||
List<SimEvent> events = SimBuilder.buildEvents(data.getEvents());
|
||
List<SimInfluenceNode> nodes = SimBuilder.buildInfluenceNodes(data.getProject());
|
||
|
||
// 3. 执行仿真
|
||
SimContext ctx = simService.runSimulation(units, events, nodes, req.getSteps());
|
||
|
||
// 4. 结果转换 (适配前端图表或 AI 推理)
|
||
return Result.success(SimResultConverter.convert(ctx));
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 3. 实施路线图
|
||
|
||
1. **基础类准备**:
|
||
* 完善 `SimUnit`: 增加 `Map<String, Double> staticProperties` 字段。
|
||
* 完善 `SimEvent`: 增加 `boolean isOverride` 字段。
|
||
* 创建 `SimDataFacade` 类。
|
||
|
||
2. **迁移解析逻辑**:
|
||
* 将 `ProjectServiceImpl` 中解析 JSON (Topology, Event) 的代码块复制到 `SimBuilder` 中并适配。
|
||
* 确保单元测试覆盖 `SimBuilder`,保证解析正确性。
|
||
|
||
3. **实现计算逻辑**:
|
||
* 编写 `SimService.runSimulation`,严格按照推荐的 4 步管线实现。
|
||
* 编写单元测试,验证“事件覆盖计算”和“计算基于静态值”的场景。
|
||
|
||
4. **接口接入**:
|
||
* 在 `SimController` 中装配上述组件。
|
||
* 前端对接新的 `/sim/run` 接口。
|
||
|
||
5. **清理**:
|
||
* 标记 `ProjectServiceImpl` 中的旧模拟代码为 `@Deprecated`,并在验证新接口无误后删除。
|
||
|
||
通过以上步骤,可以将复杂的仿真逻辑从业务 Service 中解耦,构建一个清晰、可维护、易扩展的仿真引擎。
|