代码更新

This commit is contained in:
wanxiaoli 2025-12-23 14:42:05 +08:00
parent 5fbdb08f67
commit 200222165a
24 changed files with 1413 additions and 118 deletions

View File

@ -0,0 +1,13 @@
package com.yfd.business.css.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Configuration(proxyBeanMethods = false)
@ComponentScan(basePackages = {
"com.yfd.business.css.controller"
})
@Import(OpenApiConfig.class)
public class BusinessCssAutoConfiguration {
}

View File

@ -1,28 +1,64 @@
package com.yfd.business.css.config; package com.yfd.business.css.config;
import io.swagger.v3.oas.models.OpenAPI; import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.v3.oas.models.info.Info; import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.BeanPropertyWriter;
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springdoc.core.models.GroupedOpenApi; import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import java.time.format.DateTimeFormatter;
import java.io.IOException;
@Configuration @Configuration
public class OpenApiConfig { public class OpenApiConfig {
@Bean @Bean
public OpenAPI businessCssOpenAPI() { public GroupedOpenApi businessCssGroup() {
return new OpenAPI() return GroupedOpenApi.builder()
.info(new Info() .group("css-business")
.title("Business CSS API") .packagesToScan("com.yfd.business.css.controller")
.description("临界事故情景分析业务接口") .pathsToMatch("/events/**", "/scenario-results/**", "/projects/**")
.version("v1")); .build();
} }
@Bean @Bean
public GroupedOpenApi businessCssGroup() { public Jackson2ObjectMapperBuilderCustomizer jacksonCustomizer() {
return GroupedOpenApi.builder() return builder -> {
.group("business-css") DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
.packagesToScan("com.yfd.business.css.controller") builder.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
.build(); builder.serializers(new LocalDateTimeSerializer(fmt));
builder.deserializers(new LocalDateTimeDeserializer(fmt));
builder.simpleDateFormat("yyyy-MM-dd HH:mm:ss");
builder.serializationInclusion(JsonInclude.Include.ALWAYS);
SimpleModule module = new SimpleModule();
module.setSerializerModifier(new BeanSerializerModifier() {
@Override
public java.util.List<BeanPropertyWriter> changeProperties(
com.fasterxml.jackson.databind.SerializationConfig config,
com.fasterxml.jackson.databind.BeanDescription beanDesc,
java.util.List<BeanPropertyWriter> beanProperties) {
for (BeanPropertyWriter writer : beanProperties) {
if (writer.getType() != null && writer.getType().getRawClass() == String.class) {
writer.assignNullSerializer(new JsonSerializer<Object>() {
@Override
public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
gen.writeString("");
}
});
}
}
return beanProperties;
}
});
builder.modules(module);
};
} }
} }

View File

@ -8,15 +8,25 @@ import org.springframework.web.bind.annotation.*;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import java.util.Map; import java.util.Map;
import java.util.List; import java.util.List;
import java.util.ArrayList;
import java.util.HashMap;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.JsonNode;
import com.yfd.business.css.dto.EventAttrParseResult;
import com.yfd.business.css.dto.EventAttrSegment;
import com.yfd.business.css.dto.EventAttrPoint;
@RestController @RestController
@RequestMapping("/events") @RequestMapping("/events")
public class EventController { public class EventController {
private final EventService eventService; private final EventService eventService;
private final ObjectMapper objectMapper;
public EventController(EventService eventService) { public EventController(EventService eventService, ObjectMapper objectMapper) {
this.eventService = eventService; this.eventService = eventService;
this.objectMapper = objectMapper;
} }
/** /**
@ -74,6 +84,14 @@ public class EventController {
"msg", "attr_changes不能为空" "msg", "attr_changes不能为空"
)); ));
} }
try {
objectMapper.readTree(String.valueOf(attrChanges));
} catch (Exception e) {
return ResponseEntity.badRequest().body(Map.of(
"code", 1,
"msg", "attr_changes JSON解析失败: " + e.getMessage()
));
}
Event updatedEvent = new Event(); Event updatedEvent = new Event();
updatedEvent.setEventId(eventId); updatedEvent.setEventId(eventId);
@ -124,4 +142,129 @@ public class EventController {
)); ));
} }
} }
/**
* 解析事件 attr_changes按事件ID
* 输入参数路径参数 eventId事件ID
* 输出参数标准响应结构data EventAttrParseResult目标单位分段时间序列派生 schedule 与问题列表
* @param eventId 事件ID
* @return 解析结果
*/
@GetMapping("/{eventId}/attr-parse")
public ResponseEntity<Map<String, Object>> parseAttrChanges(@PathVariable String eventId) {
Event ev = eventService.getById(eventId);
List<String> issues = new ArrayList<>();
EventAttrParseResult result = new EventAttrParseResult();
result.setEventId(eventId);
result.setIssues(issues);
result.setSegments(new ArrayList<>());
result.setSchedule(new ArrayList<>());
if (ev == null) {
issues.add("事件不存在: " + eventId);
return ResponseEntity.ok(Map.of("code", 0, "msg", "解析完成", "data", result));
}
String json = ev.getAttrChanges();
if (json == null || json.isBlank()) {
issues.add("attr_changes为空");
return ResponseEntity.ok(Map.of("code", 0, "msg", "解析完成", "data", result));
}
try {
JsonNode root = objectMapper.readTree(json);
JsonNode target = root.path("target");
String entityType = optText(target, "entityType");
String entityId = optText(target, "entityId");
String property = optText(target, "property");
if (entityType == null || entityId == null || property == null) {
issues.add("target字段缺失");
} else {
Map<String, String> tgt = new HashMap<>();
tgt.put("entityType", entityType);
tgt.put("entityId", entityId);
tgt.put("property", property);
result.setTarget(tgt);
}
String unit = optText(root, "unit");
result.setUnit(unit);
JsonNode segments = root.path("segments");
if (segments.isArray()) {
for (JsonNode seg : segments) {
EventAttrSegment s = new EventAttrSegment();
s.setSegmentId(optText(seg, "segmentId"));
s.setStart(seg.path("start").isNumber() ? seg.path("start").asDouble() : 0.0);
s.setEnd(seg.path("end").isNumber() ? seg.path("end").asDouble() : s.getStart());
s.setInterp(optText(seg, "interp"));
List<EventAttrPoint> pts = new ArrayList<>();
JsonNode timeline = seg.path("timeline");
if (timeline.isArray()) {
for (JsonNode p : timeline) {
EventAttrPoint ep = new EventAttrPoint();
ep.setT(p.path("t").isNumber() ? p.path("t").asDouble() : 0.0);
ep.setValue(p.path("value").isNumber() ? p.path("value").asDouble()
: parseDouble(optText(p, "value"), issues, "value不是数值"));
pts.add(ep);
}
} else {
issues.add("segments.timeline不是数组");
}
s.setTimeline(pts);
result.getSegments().add(s);
// derive schedule (linear ramp between consecutive points)
if ("linear".equalsIgnoreCase(s.getInterp()) && pts.size() >= 2) {
for (int i = 0; i < pts.size() - 1; i++) {
EventAttrPoint a = pts.get(i);
EventAttrPoint b = pts.get(i + 1);
double dt = b.getT() - a.getT();
if (dt <= 0) {
issues.add("timeline时间非升序: " + a.getT() + " -> " + b.getT());
continue;
}
double rate = (b.getValue() - a.getValue()) / dt;
Map<String, Object> ramp = new HashMap<>();
ramp.put("type", "ramp");
ramp.put("startTime", a.getT());
ramp.put("endTime", b.getT());
ramp.put("rate", rate);
ramp.put("unit", unit);
result.getSchedule().add(ramp);
}
} else {
// default step-set at each point
for (EventAttrPoint ep : pts) {
Map<String, Object> step = new HashMap<>();
step.put("type", "step-set");
step.put("time", ep.getT());
step.put("value", ep.getValue());
step.put("unit", unit);
result.getSchedule().add(step);
}
}
}
} else {
issues.add("segments不是数组");
}
return ResponseEntity.ok(Map.of("code", 0, "msg", "解析成功", "data", result));
} catch (Exception e) {
issues.add("解析失败: " + e.getMessage());
return ResponseEntity.ok(Map.of("code", 0, "msg", "解析完成", "data", result));
}
}
private static String optText(JsonNode n, String field) {
JsonNode v = n.path(field);
if (v.isMissingNode() || v.isNull()) return null;
return v.asText(null);
}
private static double parseDouble(String s, List<String> issues, String warn) {
if (s == null) {
issues.add(warn);
return 0.0;
}
try {
return Double.parseDouble(s);
} catch (Exception e) {
issues.add(warn + ": " + s);
return 0.0;
}
}
} }

View File

@ -25,6 +25,8 @@ public class ProjectController {
@Resource @Resource
private ProjectService projectService; private ProjectService projectService;
@Resource
private com.fasterxml.jackson.databind.ObjectMapper objectMapper;
/** /**
* 1. 新增项目 * 1. 新增项目
@ -79,7 +81,7 @@ public class ProjectController {
* 4.1 导出所有项目Excel * 4.1 导出所有项目Excel
* 输入参数 * 输入参数
* 导出描述返回所有项目的 Excel 附件 `projects.xlsx` * 导出描述返回所有项目的 Excel 附件 `projects.xlsx`
* 导出列project_id, code, name, description, created_at, updated_at * 导出列project_id, code, name, description, created_at, updated_at,modifier
* @return 附件响应文件名为 projects.xlsx * @return 附件响应文件名为 projects.xlsx
*/ */
@GetMapping("/exportAllExports") @GetMapping("/exportAllExports")
@ -165,9 +167,24 @@ public class ProjectController {
"msg", "topology不能为空" "msg", "topology不能为空"
)); ));
} }
String json;
try {
if (topology instanceof String) {
var node = objectMapper.readTree((String) topology);
json = objectMapper.writeValueAsString(node);
} else {
json = objectMapper.writeValueAsString(topology);
objectMapper.readTree(json);
}
} catch (Exception e) {
return ResponseEntity.badRequest().body(Map.of(
"code", 1,
"msg", "topology JSON解析失败: " + e.getMessage()
));
}
Project updated = new Project(); Project updated = new Project();
updated.setProjectId(id); updated.setProjectId(id);
updated.setTopology(String.valueOf(topology)); updated.setTopology(json);
projectService.updateById(updated); projectService.updateById(updated);
return ResponseEntity.ok(Map.of( return ResponseEntity.ok(Map.of(
"code", 0, "code", 0,
@ -176,5 +193,63 @@ public class ProjectController {
)); ));
} }
@GetMapping("/{id}/topology/parse")
/**
* 解析指定项目的拓扑结构返回属性节点影响关系边及计算计划
* @param id 项目ID
* @return 标准响应结构data TopologyParseResult
*/
public ResponseEntity<Map<String, Object>> parseTopology(@PathVariable String id) {
var result = projectService.parseTopology(id);
return ResponseEntity.ok(Map.of(
"code", 0,
"msg", "解析成功",
"data", result
));
}
@GetMapping("/{id}/topology/devices")
/**
* 根据项目拓扑中的 devices 节点提取 deviceId 并按出现顺序返回设备列表
* @param id 项目ID
* @return 标准响应结构data List<Device>
*/
public ResponseEntity<Map<String, Object>> parseDeviceOrder(@PathVariable String id) {
var list = projectService.parseDeviceOrder(id);
return ResponseEntity.ok(Map.of(
"code", 0,
"msg", "解析成功",
"data", list
));
}
@GetMapping("/{id}/topology/canvas")
/**
* 解析画布视图数据设备管线边界显示配置供前端 UI 渲染
* @param id 项目ID
* @return 标准响应结构data 为视图对象devices/pipelines/boundaries/display
*/
public ResponseEntity<Map<String, Object>> parseCanvas(@PathVariable String id) {
var view = projectService.parseCanvasView(id);
return ResponseEntity.ok(Map.of(
"code", 0,
"msg", "解析成功",
"data", view
));
}
@PostMapping("/{id}/scenarios/{scenarioId}/simulation/init")
public ResponseEntity<Map<String, Object>> initSimulation(@PathVariable String id,
@PathVariable String scenarioId,
@RequestBody(required = false) Map<String, Object> params) {
var res = projectService.initSimulation(id, scenarioId, params == null ? Map.of() : params);
return ResponseEntity.ok(Map.of(
"code", 0,
"msg", "初始化完成",
"data", res
));
}
} }

View File

@ -1,106 +1,57 @@
package com.yfd.business.css.controller; package com.yfd.business.css.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yfd.business.css.domain.ScenarioResult; import com.yfd.business.css.domain.ScenarioResult;
import com.yfd.business.css.service.ScenarioResultService; import com.yfd.business.css.service.ScenarioResultService;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import jakarta.annotation.Resource;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import java.util.List;
import java.util.Map;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import jakarta.servlet.http.HttpServletResponse;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
@RestController @RestController
@RequestMapping("/scenario-results") @RequestMapping("/scenario-results")
public class ScenarioResultController { public class ScenarioResultController {
private final ScenarioResultService scenarioResultService; @Resource
private ScenarioResultService scenarioResultService;
public ScenarioResultController(ScenarioResultService scenarioResultService) {
this.scenarioResultService = scenarioResultService;
}
/** /**
* 新增情景结果 * 根据场景ID分页查询模拟结果
*/ * @param scenarioId 场景ID
@PostMapping * @param deviceId 设备ID可选
public ResponseEntity<Map<String, Object>> addScenarioResult(@RequestBody ScenarioResult result) { * @param stepFrom 起始步可选
scenarioResultService.save(result); * @param stepTo 结束步可选
ScenarioResult savedResult = result; * @param pageNum 页码默认1
return ResponseEntity.ok(Map.of( * @param pageSize 每页条数默认10
"code", 0, * @return 标准响应结构data Page<ScenarioResult>
"msg", "新增成功",
"data", savedResult
));
}
/**
* 根据情景ID与设备ID获取情景结果分页列表
* 输入参数查询参数 scenarioId情景IDdeviceId设备IDpageNum页码默认1pageSize每页条数默认10
* 输出参数情景结果分页列表按时间点 step 排序
* @param scenarioId 情景ID
* @param deviceId 设备ID
* @param pageNum 页码
* @param pageSize 每页条数
* @return 情景结果分页列表
*/ */
@GetMapping("/by-scenario") @GetMapping("/by-scenario")
public Page<ScenarioResult> listByScenario(@RequestParam String scenarioId, public ResponseEntity<?> listByScenario(
@RequestParam String deviceId, @RequestParam String scenarioId,
@RequestParam(defaultValue = "1") long pageNum, @RequestParam(required = false) String deviceId,
@RequestParam(defaultValue = "10") long pageSize) { @RequestParam(required = false) Integer stepFrom,
Page<ScenarioResult> page = new Page<>(pageNum, pageSize); @RequestParam(required = false) Integer stepTo,
return scenarioResultService.page( @RequestParam(defaultValue = "1") long pageNum,
page, @RequestParam(defaultValue = "10") long pageSize
new QueryWrapper<ScenarioResult>() ) {
.eq("scenario_id", scenarioId) QueryWrapper<ScenarioResult> qw = new QueryWrapper<ScenarioResult>()
.eq("device_id", deviceId) .eq("scenario_id", scenarioId);
.orderByAsc("step") if (deviceId != null && !deviceId.isEmpty()) {
); qw.eq("device_id", deviceId);
}
/**
* 导出情景结果到 Excel
* 输入参数查询参数 scenarioId情景IDdeviceId设备ID
* 输出参数Excel 文件包含 stepdevice_idkeff_valueattr_state
*/
@GetMapping("/export")
public void exportScenarioResults(@RequestParam String scenarioId,
@RequestParam String deviceId,
HttpServletResponse response) throws Exception {
List<ScenarioResult> results = scenarioResultService.list(
new QueryWrapper<ScenarioResult>()
.eq("scenario_id", scenarioId)
.eq("device_id", deviceId)
.orderByAsc("step")
);
Workbook workbook = new XSSFWorkbook();
Sheet sheet = workbook.createSheet("ScenarioResults");
Row header = sheet.createRow(0);
header.createCell(0).setCellValue("step");
header.createCell(1).setCellValue("device_id");
header.createCell(2).setCellValue("keff_value");
header.createCell(3).setCellValue("attr_state");
for (int i = 0; i < results.size(); i++) {
ScenarioResult r = results.get(i);
Row row = sheet.createRow(i + 1);
row.createCell(0).setCellValue(r.getStep() == null ? 0 : r.getStep());
row.createCell(1).setCellValue(r.getDeviceId() == null ? "" : r.getDeviceId());
row.createCell(2).setCellValue(r.getKeffValue() == null ? "" : r.getKeffValue().toPlainString());
row.createCell(3).setCellValue(r.getAttrState() == null ? "" : r.getAttrState());
} }
String fileName = "scenario_results_" + scenarioId + "_" + deviceId + ".xlsx"; if (stepFrom != null) {
String encoded = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()).replaceAll("\\+", "%20"); qw.ge("step", stepFrom);
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); }
response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + encoded); if (stepTo != null) {
workbook.write(response.getOutputStream()); qw.le("step", stepTo);
workbook.close(); }
Page<ScenarioResult> page = new Page<>(pageNum, pageSize);
Page<ScenarioResult> data = scenarioResultService.page(page, qw.orderByAsc("step").orderByAsc("device_id"));
return ResponseEntity.ok(java.util.Map.of(
"code", 0,
"msg", "查询成功",
"data", data
));
} }
} }

View File

@ -39,6 +39,6 @@ public class Algorithm implements Serializable {
@TableField("output_params") @TableField("output_params")
private String outputParams; private String outputParams;
@TableField("modifer") @TableField("modifier")
private String modifier; private String modifier;
} }

View File

@ -46,6 +46,6 @@ public class CriticalData implements Serializable {
@TableField("updated_at") @TableField("updated_at")
private LocalDateTime updatedAt; private LocalDateTime updatedAt;
@TableField("modifer") @TableField("modifier")
private String modifier; private String modifier;
} }

View File

@ -48,6 +48,6 @@ public class Device implements Serializable {
@TableField("updated_at") @TableField("updated_at")
private LocalDateTime updatedAt; private LocalDateTime updatedAt;
@TableField("modifer") @TableField("modifier")
private String modifier; private String modifier;
} }

View File

@ -64,6 +64,6 @@ public class Material implements Serializable {
@TableField("updated_at") @TableField("updated_at")
private LocalDateTime updatedAt; private LocalDateTime updatedAt;
@TableField("modifer") @TableField("modifier")
private String modifier; private String modifier;
} }

View File

@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data; import lombok.Data;
import java.io.Serializable; import java.io.Serializable;
@ -31,11 +32,13 @@ public class Project implements Serializable {
private String topology; private String topology;
@TableField("created_at") @TableField("created_at")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createdAt; private LocalDateTime createdAt;
@TableField("updated_at") @TableField("updated_at")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updatedAt; private LocalDateTime updatedAt;
@TableField("modifer") @TableField("modifier")
private String modifier; private String modifier;
} }

View File

@ -33,6 +33,6 @@ public class Scenario implements Serializable {
@TableField("updated_at") @TableField("updated_at")
private LocalDateTime updatedAt; private LocalDateTime updatedAt;
@TableField("modifer") @TableField("modifier")
private String modifier; private String modifier;
} }

View File

@ -0,0 +1,16 @@
package com.yfd.business.css.dto;
import lombok.Data;
import java.util.List;
import java.util.Map;
@Data
public class EventAttrParseResult {
private String eventId;
private Map<String, String> target;
private String unit;
private List<EventAttrSegment> segments;
private List<Map<String, Object>> schedule; // optional derived ramp/step
private List<String> issues;
}

View File

@ -0,0 +1,9 @@
package com.yfd.business.css.dto;
import lombok.Data;
@Data
public class EventAttrPoint {
private double t;
private double value;
}

View File

@ -0,0 +1,14 @@
package com.yfd.business.css.dto;
import lombok.Data;
import java.util.List;
@Data
public class EventAttrSegment {
private String segmentId;
private double start;
private double end;
private String interp;
private List<EventAttrPoint> timeline;
}

View File

@ -0,0 +1,15 @@
package com.yfd.business.css.dto;
import lombok.Data;
@Data
public class TopoEdge {
private String fromEntityType;
private String fromEntityId;
private String fromProperty;
private String toEntityType;
private String toEntityId;
private String toProperty;
private Double coefficient;
private Long delayMs;
}

View File

@ -0,0 +1,11 @@
package com.yfd.business.css.dto;
import lombok.Data;
@Data
public class TopoNode {
private String entityType;
private String entityId;
private String property;
private String unit;
}

View File

@ -0,0 +1,18 @@
package com.yfd.business.css.dto;
import lombok.Data;
import java.util.List;
import java.util.Map;
@Data
public class TopologyParseResult {
private String projectId;
private Integer deviceCount;
private Integer nodeCount;
private Integer edgeCount;
private List<TopoNode> nodes;
private List<TopoEdge> edges;
private List<Map<String, Object>> plans;
private List<String> issues;
}

View File

@ -11,8 +11,31 @@ public interface ProjectService extends IService<Project> {
byte[] exportAllProjectsExcel(); byte[] exportAllProjectsExcel();
/** /**
* 导出项目工程 Excel Sheet * 导出所有项目为 Excel Sheet
*/ */
byte[] exportProjectEngineeringExcel(String projectId); byte[] exportProjectEngineeringExcel(String projectId);
/**
* 解析指定项目的拓扑结构
* @param projectId 项目ID
* @return TopologyParseResult节点计算计划与问题列表
*/
com.yfd.business.css.dto.TopologyParseResult parseTopology(String projectId);
/**
* 解析指定项目的设备有序列表
* @param projectId 项目ID
* @return List<Device>按拓扑 devices 出现顺序返回
*/
java.util.List<com.yfd.business.css.domain.Device> parseDeviceOrder(String projectId);
/**
* 解析画布视图数据设备管线边界显示配置
* @param projectId 项目ID
* @return Map 视图对象devices/pipelines/boundaries/display
*/
java.util.Map<String, Object> parseCanvasView(String projectId);
java.util.Map<String, Object> initSimulation(String projectId, String scenarioId, java.util.Map<String, Object> params);
} }

View File

@ -6,6 +6,7 @@ import com.yfd.business.css.mapper.ProjectMapper;
import com.yfd.business.css.service.ProjectService; import com.yfd.business.css.service.ProjectService;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.JsonNode;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.yfd.business.css.domain.Device; import com.yfd.business.css.domain.Device;
@ -18,8 +19,20 @@ import com.yfd.business.css.service.MaterialService;
import com.yfd.business.css.service.ScenarioService; import com.yfd.business.css.service.ScenarioService;
import com.yfd.business.css.service.EventService; import com.yfd.business.css.service.EventService;
import com.yfd.business.css.service.ScenarioResultService; import com.yfd.business.css.service.ScenarioResultService;
import com.yfd.business.css.dto.TopologyParseResult;
import com.yfd.business.css.dto.TopoNode;
import com.yfd.business.css.dto.TopoEdge;
import java.util.List; import java.util.List;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import java.util.Objects;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.Queue;
import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.usermodel.Workbook;
@ -50,7 +63,7 @@ public class ProjectServiceImpl
Sheet sheet = wb.createSheet("projects"); Sheet sheet = wb.createSheet("projects");
int r = 0; int r = 0;
Row header = sheet.createRow(r++); Row header = sheet.createRow(r++);
String[] cols = {"project_id","code","name","description","created_at","updated_at"}; String[] cols = {"project_id","code","name","description","created_at","updated_at","modifier"};
for (int i = 0; i < cols.length; i++) header.createCell(i).setCellValue(cols[i]); for (int i = 0; i < cols.length; i++) header.createCell(i).setCellValue(cols[i]);
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
for (Project p : list) { for (Project p : list) {
@ -61,6 +74,7 @@ public class ProjectServiceImpl
row.createCell(3).setCellValue(p.getDescription() == null ? "" : p.getDescription()); row.createCell(3).setCellValue(p.getDescription() == null ? "" : p.getDescription());
row.createCell(4).setCellValue(p.getCreatedAt() == null ? "" : fmt.format(p.getCreatedAt())); row.createCell(4).setCellValue(p.getCreatedAt() == null ? "" : fmt.format(p.getCreatedAt()));
row.createCell(5).setCellValue(p.getUpdatedAt() == null ? "" : fmt.format(p.getUpdatedAt())); row.createCell(5).setCellValue(p.getUpdatedAt() == null ? "" : fmt.format(p.getUpdatedAt()));
row.createCell(6).setCellValue(p.getModifier() == null ? "" : p.getModifier());
} }
for (int i = 0; i < cols.length; i++) sheet.autoSizeColumn(i); for (int i = 0; i < cols.length; i++) sheet.autoSizeColumn(i);
wb.write(out); wb.write(out);
@ -70,6 +84,774 @@ public class ProjectServiceImpl
} }
} }
@Override
/**
* 解析指定项目的拓扑结构生成节点边与线性计算计划
* @param projectId 项目ID
* @return TopologyParseResult
*/
public TopologyParseResult parseTopology(String projectId) {
try {
Project p = this.getById(projectId);
TopologyParseResult r = new TopologyParseResult();
r.setProjectId(projectId);
List<TopoNode> nodes = new ArrayList<>();
List<TopoEdge> edges = new ArrayList<>();
List<Map<String, Object>> plans = new ArrayList<>();
List<String> issues = new ArrayList<>();
if (p == null) {
issues.add("项目不存在");
r.setNodes(nodes);
r.setEdges(edges);
r.setPlans(plans);
r.setIssues(issues);
r.setDeviceCount(0);
r.setNodeCount(0);
r.setEdgeCount(0);
return r;
}
if (p.getTopology() == null || p.getTopology().isBlank()) {
issues.add("topology为空");
r.setNodes(nodes);
r.setEdges(edges);
r.setPlans(plans);
r.setIssues(issues);
r.setDeviceCount(0);
r.setNodeCount(0);
r.setEdgeCount(0);
return r;
}
JsonNode root = objectMapper.readTree(p.getTopology());
JsonNode devicesNode = root.path("devices");
Set<String> deviceIds = new HashSet<>();
if (devicesNode.isArray()) {
for (JsonNode dn : devicesNode) {
String deviceId = optText(dn, "deviceId");
if (deviceId == null || deviceId.isEmpty()) {
issues.add("设备缺少deviceId");
continue;
}
deviceIds.add(deviceId);
JsonNode staticNode = dn.path("static");
if (staticNode.isObject()) {
staticNode.fieldNames().forEachRemaining(k -> {
TopoNode n = new TopoNode();
n.setEntityType("device");
n.setEntityId(deviceId);
n.setProperty(k);
n.setUnit(optText(staticNode, "unit"));
nodes.add(n);
});
}
JsonNode props = dn.path("properties");
if (props.isObject()) {
props.fieldNames().forEachRemaining(propName -> {
JsonNode prop = props.path(propName);
TopoNode n = new TopoNode();
n.setEntityType("device");
n.setEntityId(deviceId);
n.setProperty(propName);
n.setUnit(optText(prop, "unit"));
nodes.add(n);
if ("influence".equalsIgnoreCase(optText(prop, "type"))) {
List<Map<String, Object>> srcList = new ArrayList<>();
JsonNode sources = prop.path("sources");
if (sources.isArray()) {
for (JsonNode s : sources) {
String seType = optText(s, "entityType");
String seId = optText(s, "entityId");
String seProp = optText(s, "property");
Double coef = s.path("coefficient").isNumber() ? s.path("coefficient").asDouble() : 1.0;
Long delayMs = 0L;
JsonNode delay = s.path("delay");
if (delay.path("enabled").asBoolean(false)) {
long t = delay.path("time").isNumber() ? delay.path("time").asLong() : 0L;
String u = optText(delay, "unit");
delayMs = toMillis(t, u);
}
if (seType == null || seId == null || seProp == null) {
issues.add("sources缺少引用字段:" + deviceId + "." + propName);
} else {
TopoEdge e = new TopoEdge();
e.setFromEntityType(seType);
e.setFromEntityId(seId);
e.setFromProperty(seProp);
e.setToEntityType("device");
e.setToEntityId(deviceId);
e.setToProperty(propName);
e.setCoefficient(coef);
e.setDelayMs(delayMs);
edges.add(e);
Map<String, Object> src = new HashMap<>();
src.put("entityType", seType);
src.put("entityId", seId);
src.put("property", seProp);
src.put("coefficient", coef);
src.put("delayMs", delayMs);
srcList.add(src);
}
}
}
Map<String, Object> plan = new HashMap<>();
plan.put("target", Map.of("entityType","device","entityId",deviceId,"property",propName));
plan.put("bias", prop.path("bias").isNumber() ? prop.path("bias").asDouble() : 0.0);
plan.put("sources", srcList);
plans.add(plan);
}
});
}
// materials can be array or single object; also some payloads use "material" key
JsonNode mats = dn.path("materials");
if (mats.isMissingNode() || mats.isNull()) {
mats = dn.path("material");
}
if (mats.isArray()) {
for (JsonNode mn : mats) {
parseMaterialNode(mn, deviceId, nodes, edges, plans, issues);
}
} else if (mats.isObject()) {
parseMaterialNode(mats, deviceId, nodes, edges, plans, issues);
}
}
} else {
issues.add("devices缺失或不是数组");
}
r.setDeviceCount(deviceIds.size());
r.setNodes(nodes);
r.setEdges(edges);
r.setPlans(plans);
r.setIssues(issues);
r.setNodeCount(nodes.size());
r.setEdgeCount(edges.size());
return r;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private void parseMaterialNode(JsonNode mn,
String deviceId,
List<TopoNode> nodes,
List<TopoEdge> edges,
List<Map<String, Object>> plans,
List<String> issues) {
String mid = optText(mn, "materialId");
if (mid == null || mid.isEmpty()) {
issues.add("材料缺少materialId: device=" + deviceId);
return;
}
JsonNode mstatic = mn.path("static");
if (mstatic.isObject()) {
mstatic.fieldNames().forEachRemaining(k -> {
TopoNode n = new TopoNode();
n.setEntityType("material");
n.setEntityId(mid);
n.setProperty(k);
n.setUnit(optText(mstatic, "unit"));
nodes.add(n);
});
}
JsonNode mprops = mn.path("properties");
if (mprops.isObject()) {
mprops.fieldNames().forEachRemaining(propName -> {
JsonNode prop = mprops.path(propName);
TopoNode n = new TopoNode();
n.setEntityType("material");
n.setEntityId(mid);
n.setProperty(propName);
n.setUnit(optText(prop, "unit"));
nodes.add(n);
if ("influence".equalsIgnoreCase(optText(prop, "type"))) {
List<Map<String, Object>> srcList = new ArrayList<>();
JsonNode sources = prop.path("sources");
if (sources.isArray()) {
for (JsonNode s : sources) {
String seType = optText(s, "entityType");
String seId = optText(s, "entityId");
String seProp = optText(s, "property");
Double coef = s.path("coefficient").isNumber() ? s.path("coefficient").asDouble() : 1.0;
Long delayMs = 0L;
JsonNode delay = s.path("delay");
if (delay.path("enabled").asBoolean(false)) {
long t = delay.path("time").isNumber() ? delay.path("time").asLong() : 0L;
String u = optText(delay, "unit");
delayMs = toMillis(t, u);
}
if (seType == null || seId == null || seProp == null) {
issues.add("materials.sources缺少引用字段:" + mid + "." + propName);
} else {
TopoEdge e = new TopoEdge();
e.setFromEntityType(seType);
e.setFromEntityId(seId);
e.setFromProperty(seProp);
e.setToEntityType("material");
e.setToEntityId(mid);
e.setToProperty(propName);
e.setCoefficient(coef);
e.setDelayMs(delayMs);
edges.add(e);
Map<String, Object> src = new HashMap<>();
src.put("entityType", seType);
src.put("entityId", seId);
src.put("property", seProp);
src.put("coefficient", coef);
src.put("delayMs", delayMs);
srcList.add(src);
}
}
}
Map<String, Object> plan = new HashMap<>();
plan.put("target", Map.of("entityType","material","entityId",mid,"property",propName));
plan.put("bias", prop.path("bias").isNumber() ? prop.path("bias").asDouble() : 0.0);
plan.put("sources", srcList);
plans.add(plan);
}
});
}
}
private static String optText(JsonNode n, String field) {
JsonNode v = n.path(field);
if (v.isMissingNode() || v.isNull()) return null;
if (v.isTextual()) return v.asText();
if (v.isNumber() || v.isBoolean()) return String.valueOf(v.asText());
return v.asText(null);
}
private static long toMillis(long t, String unit) {
if (unit == null) return t;
String u = unit.toLowerCase();
if (Objects.equals(u, "ms")) return t;
if (Objects.equals(u, "s")) return t * 1000L;
if (Objects.equals(u, "min")) return t * 60_000L;
return t;
}
@Override
/**
* 基于项目拓扑 devices收集 deviceId 并返回对应设备列表按出现顺序
* @param projectId 项目ID
* @return List<Device>
*/
public List<Device> parseDeviceOrder(String projectId) {
try {
Project p = this.getById(projectId);
List<Device> devices = new ArrayList<>();
if (p == null || p.getTopology() == null || p.getTopology().isBlank()) {
return devices;
}
JsonNode root = objectMapper.readTree(p.getTopology());
JsonNode devicesNode = root.path("devices");
List<String> ids = new ArrayList<>();
if (devicesNode.isArray()) {
for (JsonNode dn : devicesNode) {
String deviceId = optText(dn, "deviceId");
if (deviceId != null && !deviceId.isEmpty()) ids.add(deviceId);
}
}
if (ids.isEmpty()) return devices;
List<Device> fetched = deviceService.list(new QueryWrapper<Device>().in("device_id", ids));
Map<String, Device> devMap = new HashMap<>();
for (Device d : fetched) devMap.put(d.getDeviceId(), d);
for (String id : ids) {
Device d = devMap.get(id);
if (d != null) devices.add(d);
}
return devices;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
/**
* 提取画布视图所需数据设备管线边界显示配置
* @param projectId 项目ID
* @return Map 视图对象
*/
public Map<String, Object> parseCanvasView(String projectId) {
try {
Project p = this.getById(projectId);
Map<String, Object> res = new HashMap<>();
List<Map<String, Object>> devicesView = new ArrayList<>();
List<Map<String, Object>> pipelinesView = new ArrayList<>();
List<Map<String, Object>> boundariesView = new ArrayList<>();
Map<String, Object> display = new HashMap<>();
res.put("devices", devicesView);
res.put("pipelines", pipelinesView);
res.put("boundaries", boundariesView);
res.put("display", display);
if (p == null || p.getTopology() == null || p.getTopology().isBlank()) return res;
JsonNode root = objectMapper.readTree(p.getTopology());
JsonNode devicesNode = root.path("devices");
if (devicesNode.isArray()) {
for (JsonNode dn : devicesNode) {
String deviceId = optText(dn, "deviceId");
Map<String, Object> d = new HashMap<>();
d.put("deviceId", deviceId);
d.put("name", optText(dn, "name"));
d.put("type", optText(dn, "type"));
JsonNode ui = dn.path("ui");
JsonNode pos = ui.path("position");
JsonNode size = ui.path("size");
d.put("positionX", pos.path("x").isNumber() ? pos.path("x").asInt() : 0);
d.put("positionY", pos.path("y").isNumber() ? pos.path("y").asInt() : 0);
d.put("width", size.path("width").isNumber() ? size.path("width").asInt() : 0);
d.put("height", size.path("height").isNumber() ? size.path("height").asInt() : 0);
JsonNode mats = dn.path("materials");
if (mats.isMissingNode() || mats.isNull()) mats = dn.path("material");
List<Map<String, Object>> matList = new ArrayList<>();
if (mats.isArray()) {
for (JsonNode mn : mats) {
String mid = optText(mn, "materialId");
if (mid != null && !mid.isEmpty()) {
Map<String, Object> m = new HashMap<>();
m.put("materialId", mid);
m.put("name", optText(mn, "name"));
matList.add(m);
}
}
} else if (mats.isObject()) {
String mid = optText(mats, "materialId");
if (mid != null && !mid.isEmpty()) {
Map<String, Object> m = new HashMap<>();
m.put("materialId", mid);
m.put("name", optText(mats, "name"));
matList.add(m);
}
}
d.put("materials", matList);
devicesView.add(d);
}
}
JsonNode pipelines = root.path("pipelines");
if (pipelines.isArray()) {
for (JsonNode pn : pipelines) {
Map<String, Object> pl = new HashMap<>();
pl.put("pipelineId", optText(pn, "pipelineId"));
pl.put("from", optText(pn, "from"));
pl.put("to", optText(pn, "to"));
pl.put("line_type", optText(pn, "line_type"));
List<Map<String, Object>> path = new ArrayList<>();
JsonNode pathNode = pn.path("path");
if (pathNode.isArray()) {
for (JsonNode pt : pathNode) {
Map<String, Object> xy = new HashMap<>();
xy.put("x", pt.path("x").isNumber() ? pt.path("x").asInt() : 0);
xy.put("y", pt.path("y").isNumber() ? pt.path("y").asInt() : 0);
path.add(xy);
}
}
pl.put("path", path);
pipelinesView.add(pl);
}
}
JsonNode boundaries = root.path("systemboundaries");
if (boundaries.isArray()) {
for (JsonNode bn : boundaries) {
Map<String, Object> b = new HashMap<>();
b.put("boundary_id", optText(bn, "boundary_id"));
b.put("name", optText(bn, "name"));
b.put("type", optText(bn, "type"));
Map<String, Object> geometry = new HashMap<>();
JsonNode geom = bn.path("geometry");
geometry.put("x", geom.path("x").isNumber() ? geom.path("x").asInt() : 0);
geometry.put("y", geom.path("y").isNumber() ? geom.path("y").asInt() : 0);
geometry.put("width", geom.path("width").isNumber() ? geom.path("width").asInt() : 0);
geometry.put("height", geom.path("height").isNumber() ? geom.path("height").asInt() : 0);
b.put("geometry", geometry);
Map<String, Object> line = new HashMap<>();
JsonNode ls = bn.path("line_style");
line.put("type", optText(ls, "type"));
line.put("width", ls.path("width").isNumber() ? ls.path("width").asInt() : 0);
line.put("color", optText(ls, "color"));
b.put("line_style", line);
boundariesView.add(b);
}
}
JsonNode gd = root.path("globalDisplay");
if (gd.isObject()) {
display.put("device", objectMapper.convertValue(gd.path("device"), Map.class));
display.put("material", objectMapper.convertValue(gd.path("material"), Map.class));
}
return res;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public Map<String, Object> initSimulation(String projectId, String scenarioId, Map<String, Object> params) {
Map<String, Object> out = new HashMap<>();
out.put("projectId", projectId);
out.put("scenarioId", scenarioId);
List<String> issues = new ArrayList<>();
out.put("issues", issues);
try {
Project p = this.getById(projectId);
if (p == null || p.getTopology() == null || p.getTopology().isBlank()) {
issues.add("topology为空");
out.put("generated", Map.of("events", 0, "snapshots", 0));
return out;
}
JsonNode root = objectMapper.readTree(p.getTopology());
long start = getLongParam(params, "startTime", 0L);
long end = getLongParam(params, "endTime", 60L);
long stepSec = getLongParam(params, "step", 1L);
if (end < start) end = start;
List<Device> devs = parseDeviceOrder(projectId);
if (devs.isEmpty()) {
issues.add("devices为空");
out.put("generated", Map.of("events", 0, "snapshots", 0));
return out;
}
Map<String, Map<String, Double>> devStatic = new HashMap<>();
Map<String, Map<String, Object>> devInfluence = new HashMap<>();
parseDeviceStaticsAndInfluences(root, devStatic, devInfluence, issues);
Map<String, Map<String, Double>> matStatic = new HashMap<>();
Map<String, Map<String, Object>> matInfluence = new HashMap<>();
parseMaterialStaticsAndInfluences(root, matStatic, matInfluence, issues);
List<Event> events = eventService.list(
new QueryWrapper<Event>()
.select("event_id","scenario_id","device_id","material_id","attr_changes","trigger_time","created_at","modifier")
.eq("scenario_id", scenarioId)
);
Map<String, Object> valueProviders = buildValueProviders(events, issues);
List<ScenarioResult> all = new ArrayList<>();
int snapshots = 0;
for (long t = start; t <= end; t += stepSec) {
int stepIndex = (int) ((t - start) / stepSec);
Map<String, Map<String, Object>> deviceStates = new HashMap<>();
for (Device d : devs) {
String did = d.getDeviceId();
Map<String, Object> state = new HashMap<>();
Map<String, Double> s = devStatic.getOrDefault(did, Map.of());
for (Map.Entry<String, Double> e : s.entrySet()) state.put(e.getKey(), e.getValue());
Map<String, Object> infl = devInfluence.getOrDefault(did, Map.of());
for (Map.Entry<String, Object> e : infl.entrySet()) {
String prop = e.getKey();
@SuppressWarnings("unchecked")
Map<String, Object> plan = (Map<String, Object>) e.getValue();
double bias = toDouble(plan.get("bias"));
double sum = 0.0;
@SuppressWarnings("unchecked")
List<Map<String, Object>> sources = (List<Map<String, Object>>) plan.get("sources");
if (sources != null) {
for (Map<String, Object> src : sources) {
String seType = String.valueOf(src.get("entityType"));
String seId = String.valueOf(src.get("entityId"));
String seProp = String.valueOf(src.get("property"));
double coef = toDouble(src.get("coefficient"));
long delayMs = ((Number) src.getOrDefault("delayMs", 0L)).longValue();
long dt = t - delayMs / 1000L;
double val = readValue(seType, seId, seProp, dt, devStatic, matStatic, valueProviders);
sum += coef * val;
}
}
state.put(prop, sum + bias);
}
Map<String, Object> materialsState = new HashMap<>();
for (Map.Entry<String, Map<String, Double>> me : matStatic.entrySet()) {
String mid = me.getKey();
Map<String, Object> mstate = new HashMap<>();
for (Map.Entry<String, Double> e : me.getValue().entrySet()) mstate.put(e.getKey(), e.getValue());
Map<String, Object> minfl = matInfluence.getOrDefault(mid, Map.of());
for (Map.Entry<String, Object> e : minfl.entrySet()) {
String prop = e.getKey();
@SuppressWarnings("unchecked")
Map<String, Object> plan = (Map<String, Object>) e.getValue();
double bias = toDouble(plan.get("bias"));
double sum = 0.0;
@SuppressWarnings("unchecked")
List<Map<String, Object>> sources = (List<Map<String, Object>>) plan.get("sources");
if (sources != null) {
for (Map<String, Object> src : sources) {
String seType = String.valueOf(src.get("entityType"));
String seId = String.valueOf(src.get("entityId"));
String seProp = String.valueOf(src.get("property"));
double coef = toDouble(src.get("coefficient"));
long delayMs = ((Number) src.getOrDefault("delayMs", 0L)).longValue();
long dt = t - delayMs / 1000L;
double val = readValue(seType, seId, seProp, dt, devStatic, matStatic, valueProviders);
sum += coef * val;
}
}
mstate.put(prop, sum + bias);
}
materialsState.put(mid, mstate);
}
state.put("materials", materialsState);
deviceStates.put(did, state);
ScenarioResult sr = new ScenarioResult();
sr.setScenarioId(scenarioId);
sr.setDeviceId(did);
sr.setStep(stepIndex);
sr.setAttrState(objectMapper.writeValueAsString(state));
sr.setKeffValue(null);
all.add(sr);
}
snapshots += devs.size();
}
if (!all.isEmpty()) scenarioResultService.saveBatch(all);
out.put("generated", Map.of("events", events.size(), "snapshots", snapshots));
return out;
} catch (Exception ex) {
issues.add("初始化失败:" + ex.getMessage());
out.put("generated", Map.of("events", 0, "snapshots", 0));
return out;
}
}
private void parseDeviceStaticsAndInfluences(JsonNode root,
Map<String, Map<String, Double>> devStatic,
Map<String, Map<String, Object>> devInfluence,
List<String> issues) {
JsonNode devicesNode = root.path("devices");
if (!devicesNode.isArray()) return;
for (JsonNode dn : devicesNode) {
String deviceId = optText(dn, "deviceId");
if (deviceId == null || deviceId.isEmpty()) continue;
Map<String, Double> s = devStatic.computeIfAbsent(deviceId, k -> new HashMap<>());
JsonNode st = dn.path("static");
if (st.isObject()) {
st.fieldNames().forEachRemaining(k -> {
if (!"unit".equals(k)) {
JsonNode v = st.path(k);
if (v.isNumber() || v.isTextual()) s.put(k, v.isNumber() ? v.asDouble() : parseDouble(v.asText(), issues));
}
});
}
Map<String, Object> infl = devInfluence.computeIfAbsent(deviceId, k -> new HashMap<>());
JsonNode props = dn.path("properties");
if (props.isObject()) {
props.fieldNames().forEachRemaining(propName -> {
JsonNode prop = props.path(propName);
if ("influence".equalsIgnoreCase(optText(prop, "type"))) {
Map<String, Object> plan = new HashMap<>();
plan.put("bias", prop.path("bias").isNumber() ? prop.path("bias").asDouble() : 0.0);
List<Map<String, Object>> sources = new ArrayList<>();
JsonNode srcs = prop.path("sources");
if (srcs.isArray()) {
for (JsonNode sNode : srcs) {
Map<String, Object> src = new HashMap<>();
src.put("entityType", optText(sNode, "entityType"));
src.put("entityId", optText(sNode, "entityId"));
src.put("property", optText(sNode, "property"));
src.put("coefficient", sNode.path("coefficient").isNumber() ? sNode.path("coefficient").asDouble() : 1.0);
long delayMs = 0L;
JsonNode delay = sNode.path("delay");
if (delay.path("enabled").asBoolean(false)) {
long t = delay.path("time").isNumber() ? delay.path("time").asLong() : 0L;
String u = optText(delay, "unit");
delayMs = toMillis(t, u);
}
src.put("delayMs", delayMs);
sources.add(src);
}
}
plan.put("sources", sources);
infl.put(propName, plan);
}
});
}
}
}
private void parseMaterialStaticsAndInfluences(JsonNode root,
Map<String, Map<String, Double>> matStatic,
Map<String, Map<String, Object>> matInfluence,
List<String> issues) {
JsonNode devicesNode = root.path("devices");
if (!devicesNode.isArray()) return;
for (JsonNode dn : devicesNode) {
JsonNode mats = dn.path("materials");
if (mats.isMissingNode() || mats.isNull()) mats = dn.path("material");
if (mats.isArray()) {
for (JsonNode mn : mats) parseOneMaterial(mn, matStatic, matInfluence, issues);
} else if (mats.isObject()) {
parseOneMaterial(mats, matStatic, matInfluence, issues);
}
}
}
private void parseOneMaterial(JsonNode mn,
Map<String, Map<String, Double>> matStatic,
Map<String, Map<String, Object>> matInfluence,
List<String> issues) {
String mid = optText(mn, "materialId");
if (mid == null || mid.isEmpty()) return;
Map<String, Double> s = matStatic.computeIfAbsent(mid, k -> new HashMap<>());
JsonNode st = mn.path("static");
if (st.isObject()) {
st.fieldNames().forEachRemaining(k -> {
if (!"unit".equals(k)) {
JsonNode v = st.path(k);
if (v.isNumber() || v.isTextual()) s.put(k, v.isNumber() ? v.asDouble() : parseDouble(v.asText(), issues));
}
});
}
Map<String, Object> infl = matInfluence.computeIfAbsent(mid, k -> new HashMap<>());
JsonNode props = mn.path("properties");
if (props.isObject()) {
props.fieldNames().forEachRemaining(propName -> {
JsonNode prop = props.path(propName);
if ("influence".equalsIgnoreCase(optText(prop, "type"))) {
Map<String, Object> plan = new HashMap<>();
plan.put("bias", prop.path("bias").isNumber() ? prop.path("bias").asDouble() : 0.0);
List<Map<String, Object>> sources = new ArrayList<>();
JsonNode srcs = prop.path("sources");
if (srcs.isArray()) {
for (JsonNode sNode : srcs) {
Map<String, Object> src = new HashMap<>();
src.put("entityType", optText(sNode, "entityType"));
src.put("entityId", optText(sNode, "entityId"));
src.put("property", optText(sNode, "property"));
src.put("coefficient", sNode.path("coefficient").isNumber() ? sNode.path("coefficient").asDouble() : 1.0);
long delayMs = 0L;
JsonNode delay = sNode.path("delay");
if (delay.path("enabled").asBoolean(false)) {
long t = delay.path("time").isNumber() ? delay.path("time").asLong() : 0L;
String u = optText(delay, "unit");
delayMs = toMillis(t, u);
}
src.put("delayMs", delayMs);
sources.add(src);
}
}
plan.put("sources", sources);
infl.put(propName, plan);
}
});
}
}
private Map<String, Object> buildValueProviders(List<Event> events, List<String> issues) {
Map<String, Object> providers = new HashMap<>();
for (Event ev : events) {
String json = ev.getAttrChanges();
if (json == null || json.isBlank()) continue;
try {
JsonNode root = objectMapper.readTree(json);
String unit = optText(root, "unit");
JsonNode target = root.path("target");
String entityType = optText(target, "entityType");
String entityId = optText(target, "entityId");
String property = optText(target, "property");
if (entityType == null || entityId == null || property == null) continue;
String key = entityType + ":" + entityId + ":" + property;
List<Map<String, Object>> schedule = new ArrayList<>();
JsonNode segments = root.path("segments");
if (segments.isArray()) {
for (JsonNode seg : segments) {
String interp = optText(seg, "interp");
JsonNode timeline = seg.path("timeline");
if (timeline.isArray()) {
List<Map<String, Object>> pts = new ArrayList<>();
for (JsonNode p : timeline) {
Map<String, Object> pt = new HashMap<>();
double tt = p.path("t").isNumber() ? p.path("t").asDouble() : parseDouble(optText(p, "t"), issues);
double val = p.path("value").isNumber() ? p.path("value").asDouble() : parseDouble(optText(p, "value"), issues);
pt.put("t", tt);
pt.put("value", val);
pts.add(pt);
}
if ("linear".equalsIgnoreCase(interp) && pts.size() >= 2) {
for (int i = 0; i < pts.size() - 1; i++) {
double t1 = toDouble(pts.get(i).get("t"));
double v1 = toDouble(pts.get(i).get("value"));
double t2 = toDouble(pts.get(i + 1).get("t"));
double v2 = toDouble(pts.get(i + 1).get("value"));
double dt = t2 - t1;
if (dt <= 0) continue;
double rate = (v2 - v1) / dt;
Map<String, Object> ramp = new HashMap<>();
ramp.put("type", "ramp");
ramp.put("startTime", t1);
ramp.put("endTime", t2);
ramp.put("rate", rate);
ramp.put("unit", unit);
ramp.put("startValue", v1);
schedule.add(ramp);
}
} else {
for (Map<String, Object> pt : pts) {
Map<String, Object> step = new HashMap<>();
step.put("type", "step-set");
step.put("time", pt.get("t"));
step.put("value", pt.get("value"));
step.put("unit", unit);
schedule.add(step);
}
}
}
}
}
providers.put(key, Map.of("unit", unit, "schedule", schedule));
} catch (Exception e) {
issues.add("事件解析失败:" + e.getMessage());
}
}
return providers;
}
private double readValue(String entityType,
String entityId,
String property,
long t,
Map<String, Map<String, Double>> devStatic,
Map<String, Map<String, Double>> matStatic,
Map<String, Object> providers) {
String key = entityType + ":" + entityId + ":" + property;
@SuppressWarnings("unchecked")
Map<String, Object> pv = (Map<String, Object>) providers.get(key);
if (pv != null) {
@SuppressWarnings("unchecked")
List<Map<String, Object>> schedule = (List<Map<String, Object>>) pv.get("schedule");
double last = Double.NaN;
for (Map<String, Object> s : schedule) {
String type = String.valueOf(s.get("type"));
if ("step-set".equals(type)) {
double time = toDouble(s.get("time"));
if (t >= (long) time) last = toDouble(s.get("value"));
} else if ("ramp".equals(type)) {
double st = toDouble(s.get("startTime"));
double et = toDouble(s.get("endTime"));
if (t >= st && t <= et) {
double rate = toDouble(s.get("rate"));
double sv = toDouble(s.get("startValue"));
last = sv + rate * (t - st);
} else if (t > et) {
double rate = toDouble(s.get("rate"));
double sv = toDouble(s.get("startValue"));
last = sv + rate * (et - st);
}
}
}
if (!Double.isNaN(last)) return last;
}
Map<String, Double> s = "device".equals(entityType) ? devStatic.getOrDefault(entityId, Map.of()) : matStatic.getOrDefault(entityId, Map.of());
return s.getOrDefault(property, 0.0);
}
private long getLongParam(Map<String, Object> params, String key, long def) {
if (params == null) return def;
Object v = params.get(key);
if (v == null) return def;
if (v instanceof Number) return ((Number) v).longValue();
try { return Long.parseLong(String.valueOf(v)); } catch (Exception e) { return def; }
}
private double parseDouble(String s, List<String> issues) {
if (s == null) return 0.0;
try { return Double.parseDouble(s); } catch (Exception e) { issues.add("数值解析失败:" + s); return 0.0; }
}
private double toDouble(Object o) {
if (o == null) return 0.0;
if (o instanceof Number) return ((Number) o).doubleValue();
try { return Double.parseDouble(String.valueOf(o)); } catch (Exception e) { return 0.0; }
}
@Override @Override
public byte[] exportProjectEngineeringExcel(String projectId) { public byte[] exportProjectEngineeringExcel(String projectId) {
try (Workbook wb = new XSSFWorkbook(); ByteArrayOutputStream out = new ByteArrayOutputStream()) { try (Workbook wb = new XSSFWorkbook(); ByteArrayOutputStream out = new ByteArrayOutputStream()) {
@ -82,13 +864,15 @@ public class ProjectServiceImpl
List<String> scenarioIds = scenarios.stream().map(Scenario::getScenarioId).toList(); List<String> scenarioIds = scenarios.stream().map(Scenario::getScenarioId).toList();
List<Event> events = scenarioIds.isEmpty() List<Event> events = scenarioIds.isEmpty()
? List.of() ? List.of()
: eventService.list(new QueryWrapper<Event>().in("scenario_id", scenarioIds)); : eventService.list(new QueryWrapper<Event>()
.select("event_id","scenario_id","device_id","material_id","attr_changes","trigger_time","created_at","modifier")
.in("scenario_id", scenarioIds));
List<ScenarioResult> results = scenarioIds.isEmpty() List<ScenarioResult> results = scenarioIds.isEmpty()
? List.of() ? List.of()
: scenarioResultService.list(new QueryWrapper<ScenarioResult>().in("scenario_id", scenarioIds)); : scenarioResultService.list(new QueryWrapper<ScenarioResult>().in("scenario_id", scenarioIds));
Sheet s1 = wb.createSheet("projects"); Sheet s1 = wb.createSheet("projects");
String[] h1 = {"project_id","code","name","description","created_at","updated_at"}; String[] h1 = {"project_id","code","name","description","created_at","updated_at","modifier"};
int r = 0; Row rh1 = s1.createRow(r++); for (int i=0;i<h1.length;i++) rh1.createCell(i).setCellValue(h1[i]); int r = 0; Row rh1 = s1.createRow(r++); for (int i=0;i<h1.length;i++) rh1.createCell(i).setCellValue(h1[i]);
for (Project p : projects) { for (Project p : projects) {
Row row = s1.createRow(r++); Row row = s1.createRow(r++);
@ -98,11 +882,12 @@ public class ProjectServiceImpl
row.createCell(3).setCellValue(p.getDescription()==null?"":p.getDescription()); row.createCell(3).setCellValue(p.getDescription()==null?"":p.getDescription());
row.createCell(4).setCellValue(p.getCreatedAt()==null?"":fmt.format(p.getCreatedAt())); row.createCell(4).setCellValue(p.getCreatedAt()==null?"":fmt.format(p.getCreatedAt()));
row.createCell(5).setCellValue(p.getUpdatedAt()==null?"":fmt.format(p.getUpdatedAt())); row.createCell(5).setCellValue(p.getUpdatedAt()==null?"":fmt.format(p.getUpdatedAt()));
row.createCell(6).setCellValue(p.getModifier()==null?"":p.getModifier());
} }
for (int i=0;i<h1.length;i++) s1.autoSizeColumn(i); for (int i=0;i<h1.length;i++) s1.autoSizeColumn(i);
Sheet s2 = wb.createSheet("devices"); Sheet s2 = wb.createSheet("devices");
String[] h2 = {"device_id","project_id","code","type","name","size","volume","flow_rate","pulse_velocity","created_at","updated_at"}; String[] h2 = {"device_id","project_id","code","type","name","size","volume","flow_rate","pulse_velocity","created_at","updated_at","modifier"};
r = 0; Row rh2 = s2.createRow(r++); for (int i=0;i<h2.length;i++) rh2.createCell(i).setCellValue(h2[i]); r = 0; Row rh2 = s2.createRow(r++); for (int i=0;i<h2.length;i++) rh2.createCell(i).setCellValue(h2[i]);
for (Device d : devices) { for (Device d : devices) {
Row row = s2.createRow(r++); Row row = s2.createRow(r++);
@ -117,11 +902,12 @@ public class ProjectServiceImpl
row.createCell(8).setCellValue(d.getPulseVelocity()==null?0:d.getPulseVelocity()); row.createCell(8).setCellValue(d.getPulseVelocity()==null?0:d.getPulseVelocity());
row.createCell(9).setCellValue(d.getCreatedAt()==null?"":fmt.format(d.getCreatedAt())); row.createCell(9).setCellValue(d.getCreatedAt()==null?"":fmt.format(d.getCreatedAt()));
row.createCell(10).setCellValue(d.getUpdatedAt()==null?"":fmt.format(d.getUpdatedAt())); row.createCell(10).setCellValue(d.getUpdatedAt()==null?"":fmt.format(d.getUpdatedAt()));
row.createCell(11).setCellValue(d.getModifier()==null?"":d.getModifier());
} }
for (int i=0;i<h2.length;i++) s2.autoSizeColumn(i); for (int i=0;i<h2.length;i++) s2.autoSizeColumn(i);
Sheet s3 = wb.createSheet("materials"); Sheet s3 = wb.createSheet("materials");
String[] h3 = {"material_id","project_id","name","u_concentration","uo2_density","u_enrichment","pu_concentration","puo2_density","pu_isotope","hno3_acidity","h2c2o4_concentration","organic_ratio","moisture_content","custom_attrs","created_at","updated_at"}; String[] h3 = {"material_id","project_id","name","u_concentration","uo2_density","u_enrichment","pu_concentration","puo2_density","pu_isotope","hno3_acidity","h2c2o4_concentration","organic_ratio","moisture_content","custom_attrs","created_at","updated_at","modifier"};
r = 0; Row rh3 = s3.createRow(r++); for (int i=0;i<h3.length;i++) rh3.createCell(i).setCellValue(h3[i]); r = 0; Row rh3 = s3.createRow(r++); for (int i=0;i<h3.length;i++) rh3.createCell(i).setCellValue(h3[i]);
for (Material m : materials) { for (Material m : materials) {
Row row = s3.createRow(r++); Row row = s3.createRow(r++);
@ -141,11 +927,12 @@ public class ProjectServiceImpl
row.createCell(13).setCellValue(m.getCustomAttrs()==null?"":m.getCustomAttrs()); row.createCell(13).setCellValue(m.getCustomAttrs()==null?"":m.getCustomAttrs());
row.createCell(14).setCellValue(m.getCreatedAt()==null?"":fmt.format(m.getCreatedAt())); row.createCell(14).setCellValue(m.getCreatedAt()==null?"":fmt.format(m.getCreatedAt()));
row.createCell(15).setCellValue(m.getUpdatedAt()==null?"":fmt.format(m.getUpdatedAt())); row.createCell(15).setCellValue(m.getUpdatedAt()==null?"":fmt.format(m.getUpdatedAt()));
row.createCell(16).setCellValue(m.getModifier()==null?"":m.getModifier());
} }
for (int i=0;i<h3.length;i++) s3.autoSizeColumn(i); for (int i=0;i<h3.length;i++) s3.autoSizeColumn(i);
Sheet s4 = wb.createSheet("scenarios"); Sheet s4 = wb.createSheet("scenarios");
String[] h4 = {"scenario_id","project_id","name","description","created_at","updated_at"}; String[] h4 = {"scenario_id","project_id","name","description","created_at","updated_at","modifier"};
r = 0; Row rh4 = s4.createRow(r++); for (int i=0;i<h4.length;i++) rh4.createCell(i).setCellValue(h4[i]); r = 0; Row rh4 = s4.createRow(r++); for (int i=0;i<h4.length;i++) rh4.createCell(i).setCellValue(h4[i]);
for (Scenario sc : scenarios) { for (Scenario sc : scenarios) {
Row row = s4.createRow(r++); Row row = s4.createRow(r++);
@ -155,11 +942,12 @@ public class ProjectServiceImpl
row.createCell(3).setCellValue(sc.getDescription()==null?"":sc.getDescription()); row.createCell(3).setCellValue(sc.getDescription()==null?"":sc.getDescription());
row.createCell(4).setCellValue(sc.getCreatedAt()==null?"":fmt.format(sc.getCreatedAt())); row.createCell(4).setCellValue(sc.getCreatedAt()==null?"":fmt.format(sc.getCreatedAt()));
row.createCell(5).setCellValue(sc.getUpdatedAt()==null?"":fmt.format(sc.getUpdatedAt())); row.createCell(5).setCellValue(sc.getUpdatedAt()==null?"":fmt.format(sc.getUpdatedAt()));
row.createCell(6).setCellValue(sc.getModifier()==null?"":sc.getModifier());
} }
for (int i=0;i<h4.length;i++) s4.autoSizeColumn(i); for (int i=0;i<h4.length;i++) s4.autoSizeColumn(i);
Sheet s5 = wb.createSheet("events"); Sheet s5 = wb.createSheet("events");
String[] h5 = {"event_id","scenario_id","device_id","material_id","trigger_time","attr_changes","created_at"}; String[] h5 = {"event_id","scenario_id","device_id","material_id","trigger_time","attr_changes","created_at","modifier"};
r = 0; Row rh5 = s5.createRow(r++); for (int i=0;i<h5.length;i++) rh5.createCell(i).setCellValue(h5[i]); r = 0; Row rh5 = s5.createRow(r++); for (int i=0;i<h5.length;i++) rh5.createCell(i).setCellValue(h5[i]);
for (Event ev : events) { for (Event ev : events) {
Row row = s5.createRow(r++); Row row = s5.createRow(r++);
@ -170,6 +958,7 @@ public class ProjectServiceImpl
row.createCell(4).setCellValue(ev.getTriggerTime()==null?0:ev.getTriggerTime()); row.createCell(4).setCellValue(ev.getTriggerTime()==null?0:ev.getTriggerTime());
row.createCell(5).setCellValue(ev.getAttrChanges()==null?"":ev.getAttrChanges()); row.createCell(5).setCellValue(ev.getAttrChanges()==null?"":ev.getAttrChanges());
row.createCell(6).setCellValue(ev.getCreatedAt()==null?"":fmt.format(ev.getCreatedAt())); row.createCell(6).setCellValue(ev.getCreatedAt()==null?"":fmt.format(ev.getCreatedAt()));
row.createCell(7).setCellValue(ev.getModifier()==null?"":ev.getModifier());
} }
for (int i=0;i<h5.length;i++) s5.autoSizeColumn(i); for (int i=0;i<h5.length;i++) s5.autoSizeColumn(i);

View File

@ -0,0 +1 @@
com.yfd.business.css.config.BusinessCssAutoConfiguration

View File

@ -18,4 +18,7 @@ spring:
file-space: #项目文档空间 file-space: #项目文档空间
files: D:\css\files\ #单独上传的文件附件 files: D:\css\files\ #单独上传的文件附件
system: D:\css\system\ #单独上传的文件 system: D:\css\system\ #单独上传的文件
security:
dev:
permit: true

View File

@ -0,0 +1,20 @@
server:
port: 8091
spring:
application:
name: business-css
datasource:
driver-class-name: org.h2.Driver
url: jdbc:h2:mem:cssdb;MODE=MySQL;DB_CLOSE_DELAY=-1
username: sa
password:
sql:
init:
mode: never
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
cache-enabled: false
global-config:
db-config:
id-type: assign_uuid

View File

@ -0,0 +1,146 @@
package com.yfd.business.css;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Assertions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
@SpringBootTest
@AutoConfigureMockMvc
public class SimulationInitTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@Test
public void initSimulation_and_queryResults() throws Exception {
String projectId = "proj-0001-uuid";
String scenarioId = "scen-001-uuid";
String projectJson = "{\"projectId\":\"" + projectId + "\",\"name\":\"测试项目\"}";
mockMvc.perform(MockMvcRequestBuilders.post("/projects")
.contentType(MediaType.APPLICATION_JSON)
.content(projectJson))
.andExpect(MockMvcResultMatchers.status().isOk());
String topology = "{\"devices\":[{\"deviceId\":\"dev-002-uuid\",\"name\":\"设备01\",\"type\":\"CylindricalTank\",\"static\":{\"diameter\":120,\"unit\":\"cm\"},\"properties\":{\"height\":{\"type\":\"influence\",\"unit\":\"cm\",\"bias\":0,\"sources\":[{\"entityType\":\"device\",\"entityId\":\"dev-002-uuid\",\"property\":\"diameter\",\"coefficient\":1,\"delay\":{\"enabled\":true,\"time\":0,\"unit\":\"s\"}}]}}},"
+ "{\"deviceId\":\"dev-003-uuid\",\"name\":\"设备02\",\"type\":\"AnnularTank\",\"static\":{\"diameter\":130,\"unit\":\"cm\"},\"properties\":{\"height\":{\"type\":\"influence\",\"unit\":\"cm\",\"bias\":0,\"sources\":[{\"entityType\":\"device\",\"entityId\":\"dev-003-uuid\",\"property\":\"diameter\",\"coefficient\":1,\"delay\":{\"enabled\":true,\"time\":0,\"unit\":\"s\"}}]}}}],\"pipelines\":[]}";
String topologyBody = "{\"topology\":" + objectMapper.writeValueAsString(objectMapper.readTree(topology)) + "}";
mockMvc.perform(MockMvcRequestBuilders.put("/projects/" + projectId + "/topology")
.contentType(MediaType.APPLICATION_JSON)
.content(topologyBody))
.andExpect(MockMvcResultMatchers.status().isOk());
String createEventBody = "{\"scenarioId\":\"" + scenarioId + "\"}";
String eventResp = mockMvc.perform(MockMvcRequestBuilders.post("/events")
.contentType(MediaType.APPLICATION_JSON)
.content(createEventBody))
.andExpect(MockMvcResultMatchers.status().isOk())
.andReturn().getResponse().getContentAsString();
JsonNode eventNode = objectMapper.readTree(eventResp).path("data");
String eventId = eventNode.path("eventId").asText();
String attrChanges = "{"
+ "\"attr_changes\":{"
+ "\"target\":{\"entityType\":\"device\",\"entityId\":\"dev-002-uuid\",\"property\":\"diameter\"},"
+ "\"unit\":\"cm\","
+ "\"segments\":[{"
+ "\"segmentId\":\"segment-1\",\"start\":0,\"end\":18,"
+ "\"timeline\":["
+ "{\"t\":0,\"value\":20},{\"t\":2,\"value\":50},{\"t\":4,\"value\":70},{\"t\":6,\"value\":100},"
+ "{\"t\":8,\"value\":120},{\"t\":10,\"value\":150},{\"t\":12,\"value\":200},{\"t\":14,\"value\":220},"
+ "{\"t\":16,\"value\":250},{\"t\":18,\"value\":300}"
+ "]"
+ "}]"
+ "}"
+ "}";
mockMvc.perform(MockMvcRequestBuilders.put("/events/" + eventId + "/attr-changes")
.contentType(MediaType.APPLICATION_JSON)
.content(attrChanges))
.andExpect(MockMvcResultMatchers.status().isOk());
String initBody = "{\"startTime\":0,\"endTime\":18,\"step\":2}";
String initResp = mockMvc.perform(MockMvcRequestBuilders.post("/projects/" + projectId + "/scenarios/" + scenarioId + "/simulation/init")
.contentType(MediaType.APPLICATION_JSON)
.content(initBody))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.code").value(0))
.andReturn().getResponse().getContentAsString();
JsonNode initNode = objectMapper.readTree(initResp).path("data");
int generatedEvents = initNode.path("generated").path("events").asInt();
int generatedSnapshots = initNode.path("generated").path("snapshots").asInt();
Assertions.assertTrue(generatedEvents >= 0);
Assertions.assertTrue(generatedSnapshots >= 0);
mockMvc.perform(MockMvcRequestBuilders.get("/scenario-results/by-scenario")
.param("scenarioId", scenarioId)
.param("pageNum", "1")
.param("pageSize", "50"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.code").value(0))
.andExpect(MockMvcResultMatchers.jsonPath("$.data.records").isArray());
}
@Test
public void initSimulation_direct() throws Exception {
String projectId = "proj-0001-uuid";
String scenarioId = "scen-001-uuid";
String initBody = "{\"startTime\":0,\"endTime\":10,\"step\":2}";
String initResp = mockMvc.perform(MockMvcRequestBuilders.post("/projects/" + projectId + "/scenarios/" + scenarioId + "/simulation/init")
.contentType(MediaType.APPLICATION_JSON)
.content(initBody))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.code").value(0))
.andReturn().getResponse().getContentAsString();
JsonNode initNode = objectMapper.readTree(initResp).path("data");
int generatedEvents = initNode.path("generated").path("events").asInt();
int generatedSnapshots = initNode.path("generated").path("snapshots").asInt();
Assertions.assertTrue(generatedEvents >= 0);
Assertions.assertTrue(generatedSnapshots >= 0);
mockMvc.perform(MockMvcRequestBuilders.get("/scenario-results/by-scenario")
.param("scenarioId", scenarioId)
.param("pageNum", "1")
.param("pageSize", "50"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.code").value(0))
.andExpect(MockMvcResultMatchers.jsonPath("$.data.records").isArray());
}
@Test
public void initSimulation_returnData() throws Exception {
String projectId = "proj-0001-uuid";
String scenarioId = "scen-001-uuid";
String initBody = "{\"startTime\":0,\"endTime\":12,\"step\":3}";
String initResp = mockMvc.perform(MockMvcRequestBuilders.post("/projects/" + projectId + "/scenarios/" + scenarioId + "/simulation/init")
.contentType(MediaType.APPLICATION_JSON)
.content(initBody))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.code").value(0))
.andReturn().getResponse().getContentAsString();
System.out.println("initResp=" + initResp);
JsonNode initNode = objectMapper.readTree(initResp).path("data");
int eventsCount = initNode.path("generated").path("events").asInt();
int snapshotsCount = initNode.path("generated").path("snapshots").asInt();
System.out.println("eventsCount=" + eventsCount + ", snapshotsCount=" + snapshotsCount);
String resultResp = mockMvc.perform(MockMvcRequestBuilders.get("/scenario-results/by-scenario")
.param("scenarioId", scenarioId)
.param("pageNum", "1")
.param("pageSize", "20"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.code").value(0))
.andReturn().getResponse().getContentAsString();
System.out.println("resultResp=" + resultResp);
JsonNode resultNode = objectMapper.readTree(resultResp).path("data").path("records");
Assertions.assertTrue(resultNode.isArray());
System.out.println("recordsSize=" + resultNode.size());
}
}

View File

@ -226,7 +226,7 @@ business-css/target/business-css-1.0-SNAPSHOT.jar # 业务服务(含内嵌 To
使用项目内脚本强制 JDK 17已为你添加 使用项目内脚本强制 JDK 17已为你添加
scripts\mvn17.cmd -s mvn-settings.xml -DskipTests -pl framework -am install scripts\mvn17.cmd -s mvn-settings.xml -DskipTests -pl framework -am install
scripts\mvn17.cmd -s mvn-settings.xml -DskipTests -f business-css\pom.xml spring-boot:run -Dspring-boot.run.profiles=local scripts\mvn17.cmd -s mvn-settings.xml -DskipTests -f business-css\pom.xml spring-boot:run -Dspring-boot.run.profiles=business
## 在工具内终端验证并调整到 17 ## 在工具内终端验证并调整到 17
- 验证: mvn -version 、 where java 、 where mvn - 验证: mvn -version 、 where java 、 where mvn
@ -235,3 +235,12 @@ scripts\mvn17.cmd -s mvn-settings.xml -DskipTests -f business-css\pom.xml spring
- set "PATH=%JAVA_HOME%\bin;%PATH%" - set "PATH=%JAVA_HOME%\bin;%PATH%"
- 再 mvn -version 应为 17 - 再 mvn -version 应为 17
## 开发模式
- 进入前端目录: business-css/frontend
- 安装依赖: npm install
- 启动开发服务器: npm run dev
- 访问: http://localhost:3000/
set "PATH=d:/Program Files/nodejs;%PATH%"