代码更新
This commit is contained in:
parent
5fbdb08f67
commit
200222165a
@ -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 {
|
||||
}
|
||||
@ -1,28 +1,64 @@
|
||||
package com.yfd.business.css.config;
|
||||
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.info.Info;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
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.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.io.IOException;
|
||||
|
||||
@Configuration
|
||||
public class OpenApiConfig {
|
||||
|
||||
@Bean
|
||||
public OpenAPI businessCssOpenAPI() {
|
||||
return new OpenAPI()
|
||||
.info(new Info()
|
||||
.title("Business CSS API")
|
||||
.description("临界事故情景分析业务接口")
|
||||
.version("v1"));
|
||||
public GroupedOpenApi businessCssGroup() {
|
||||
return GroupedOpenApi.builder()
|
||||
.group("css-business")
|
||||
.packagesToScan("com.yfd.business.css.controller")
|
||||
.pathsToMatch("/events/**", "/scenario-results/**", "/projects/**")
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public GroupedOpenApi businessCssGroup() {
|
||||
return GroupedOpenApi.builder()
|
||||
.group("business-css")
|
||||
.packagesToScan("com.yfd.business.css.controller")
|
||||
.build();
|
||||
public Jackson2ObjectMapperBuilderCustomizer jacksonCustomizer() {
|
||||
return builder -> {
|
||||
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
builder.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
||||
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);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,15 +8,25 @@ import org.springframework.web.bind.annotation.*;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import java.util.Map;
|
||||
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
|
||||
@RequestMapping("/events")
|
||||
public class EventController {
|
||||
|
||||
private final EventService eventService;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
public EventController(EventService eventService) {
|
||||
public EventController(EventService eventService, ObjectMapper objectMapper) {
|
||||
this.eventService = eventService;
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -74,6 +84,14 @@ public class EventController {
|
||||
"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();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,6 +25,8 @@ public class ProjectController {
|
||||
|
||||
@Resource
|
||||
private ProjectService projectService;
|
||||
@Resource
|
||||
private com.fasterxml.jackson.databind.ObjectMapper objectMapper;
|
||||
|
||||
/**
|
||||
* 1. 新增项目
|
||||
@ -79,7 +81,7 @@ public class ProjectController {
|
||||
* 4.1 导出所有项目(Excel)
|
||||
* 输入参数:无
|
||||
* 导出描述:返回所有项目的 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
|
||||
*/
|
||||
@GetMapping("/exportAllExports")
|
||||
@ -165,9 +167,24 @@ public class ProjectController {
|
||||
"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();
|
||||
updated.setProjectId(id);
|
||||
updated.setTopology(String.valueOf(topology));
|
||||
updated.setTopology(json);
|
||||
projectService.updateById(updated);
|
||||
return ResponseEntity.ok(Map.of(
|
||||
"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
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -1,106 +1,57 @@
|
||||
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.service.ScenarioResultService;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
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;
|
||||
import jakarta.annotation.Resource;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/scenario-results")
|
||||
public class ScenarioResultController {
|
||||
|
||||
private final ScenarioResultService scenarioResultService;
|
||||
|
||||
public ScenarioResultController(ScenarioResultService scenarioResultService) {
|
||||
this.scenarioResultService = scenarioResultService;
|
||||
}
|
||||
@Resource
|
||||
private ScenarioResultService scenarioResultService;
|
||||
|
||||
/**
|
||||
* 新增情景结果
|
||||
*/
|
||||
@PostMapping
|
||||
public ResponseEntity<Map<String, Object>> addScenarioResult(@RequestBody ScenarioResult result) {
|
||||
scenarioResultService.save(result);
|
||||
ScenarioResult savedResult = result;
|
||||
return ResponseEntity.ok(Map.of(
|
||||
"code", 0,
|
||||
"msg", "新增成功",
|
||||
"data", savedResult
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据情景ID与设备ID获取情景结果分页列表
|
||||
* 输入参数:查询参数 scenarioId(情景ID)、deviceId(设备ID),pageNum(页码,默认1),pageSize(每页条数,默认10)
|
||||
* 输出参数:情景结果分页列表(按时间点 step 排序)
|
||||
* @param scenarioId 情景ID
|
||||
* @param deviceId 设备ID
|
||||
* @param pageNum 页码
|
||||
* @param pageSize 每页条数
|
||||
* @return 情景结果分页列表
|
||||
* 根据场景ID分页查询模拟结果
|
||||
* @param scenarioId 场景ID
|
||||
* @param deviceId 设备ID(可选)
|
||||
* @param stepFrom 起始步(可选)
|
||||
* @param stepTo 结束步(可选)
|
||||
* @param pageNum 页码(默认1)
|
||||
* @param pageSize 每页条数(默认10)
|
||||
* @return 标准响应结构,data 为 Page<ScenarioResult>
|
||||
*/
|
||||
@GetMapping("/by-scenario")
|
||||
public Page<ScenarioResult> listByScenario(@RequestParam String scenarioId,
|
||||
@RequestParam String deviceId,
|
||||
@RequestParam(defaultValue = "1") long pageNum,
|
||||
@RequestParam(defaultValue = "10") long pageSize) {
|
||||
Page<ScenarioResult> page = new Page<>(pageNum, pageSize);
|
||||
return scenarioResultService.page(
|
||||
page,
|
||||
new QueryWrapper<ScenarioResult>()
|
||||
.eq("scenario_id", scenarioId)
|
||||
.eq("device_id", deviceId)
|
||||
.orderByAsc("step")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出情景结果到 Excel
|
||||
* 输入参数:查询参数 scenarioId(情景ID)、deviceId(设备ID)
|
||||
* 输出参数:Excel 文件(包含 step、device_id、keff_value、attr_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());
|
||||
public ResponseEntity<?> listByScenario(
|
||||
@RequestParam String scenarioId,
|
||||
@RequestParam(required = false) String deviceId,
|
||||
@RequestParam(required = false) Integer stepFrom,
|
||||
@RequestParam(required = false) Integer stepTo,
|
||||
@RequestParam(defaultValue = "1") long pageNum,
|
||||
@RequestParam(defaultValue = "10") long pageSize
|
||||
) {
|
||||
QueryWrapper<ScenarioResult> qw = new QueryWrapper<ScenarioResult>()
|
||||
.eq("scenario_id", scenarioId);
|
||||
if (deviceId != null && !deviceId.isEmpty()) {
|
||||
qw.eq("device_id", deviceId);
|
||||
}
|
||||
String fileName = "scenario_results_" + scenarioId + "_" + deviceId + ".xlsx";
|
||||
String encoded = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()).replaceAll("\\+", "%20");
|
||||
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
|
||||
response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + encoded);
|
||||
workbook.write(response.getOutputStream());
|
||||
workbook.close();
|
||||
if (stepFrom != null) {
|
||||
qw.ge("step", stepFrom);
|
||||
}
|
||||
if (stepTo != null) {
|
||||
qw.le("step", stepTo);
|
||||
}
|
||||
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
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@ -39,6 +39,6 @@ public class Algorithm implements Serializable {
|
||||
@TableField("output_params")
|
||||
private String outputParams;
|
||||
|
||||
@TableField("modifer")
|
||||
@TableField("modifier")
|
||||
private String modifier;
|
||||
}
|
||||
|
||||
@ -46,6 +46,6 @@ public class CriticalData implements Serializable {
|
||||
@TableField("updated_at")
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@TableField("modifer")
|
||||
@TableField("modifier")
|
||||
private String modifier;
|
||||
}
|
||||
|
||||
@ -48,6 +48,6 @@ public class Device implements Serializable {
|
||||
@TableField("updated_at")
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@TableField("modifer")
|
||||
@TableField("modifier")
|
||||
private String modifier;
|
||||
}
|
||||
|
||||
@ -64,6 +64,6 @@ public class Material implements Serializable {
|
||||
@TableField("updated_at")
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@TableField("modifer")
|
||||
@TableField("modifier")
|
||||
private String modifier;
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
@ -31,11 +32,13 @@ public class Project implements Serializable {
|
||||
private String topology;
|
||||
|
||||
@TableField("created_at")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@TableField("updated_at")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@TableField("modifer")
|
||||
@TableField("modifier")
|
||||
private String modifier;
|
||||
}
|
||||
|
||||
@ -33,6 +33,6 @@ public class Scenario implements Serializable {
|
||||
@TableField("updated_at")
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@TableField("modifer")
|
||||
@TableField("modifier")
|
||||
private String modifier;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
package com.yfd.business.css.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class EventAttrPoint {
|
||||
private double t;
|
||||
private double value;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -11,8 +11,31 @@ public interface ProjectService extends IService<Project> {
|
||||
byte[] exportAllProjectsExcel();
|
||||
|
||||
/**
|
||||
* 导出项目工程为 Excel(多 Sheet)
|
||||
* 导出所有项目为 Excel(多 Sheet)
|
||||
*/
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ import com.yfd.business.css.mapper.ProjectMapper;
|
||||
import com.yfd.business.css.service.ProjectService;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import jakarta.annotation.Resource;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
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.EventService;
|
||||
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.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.Sheet;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
@ -50,7 +63,7 @@ public class ProjectServiceImpl
|
||||
Sheet sheet = wb.createSheet("projects");
|
||||
int r = 0;
|
||||
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]);
|
||||
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
for (Project p : list) {
|
||||
@ -61,6 +74,7 @@ public class ProjectServiceImpl
|
||||
row.createCell(3).setCellValue(p.getDescription() == null ? "" : p.getDescription());
|
||||
row.createCell(4).setCellValue(p.getCreatedAt() == null ? "" : fmt.format(p.getCreatedAt()));
|
||||
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);
|
||||
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
|
||||
public byte[] exportProjectEngineeringExcel(String projectId) {
|
||||
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<Event> events = scenarioIds.isEmpty()
|
||||
? 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.of()
|
||||
: scenarioResultService.list(new QueryWrapper<ScenarioResult>().in("scenario_id", scenarioIds));
|
||||
|
||||
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]);
|
||||
for (Project p : projects) {
|
||||
Row row = s1.createRow(r++);
|
||||
@ -98,11 +882,12 @@ public class ProjectServiceImpl
|
||||
row.createCell(3).setCellValue(p.getDescription()==null?"":p.getDescription());
|
||||
row.createCell(4).setCellValue(p.getCreatedAt()==null?"":fmt.format(p.getCreatedAt()));
|
||||
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);
|
||||
|
||||
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]);
|
||||
for (Device d : devices) {
|
||||
Row row = s2.createRow(r++);
|
||||
@ -117,11 +902,12 @@ public class ProjectServiceImpl
|
||||
row.createCell(8).setCellValue(d.getPulseVelocity()==null?0:d.getPulseVelocity());
|
||||
row.createCell(9).setCellValue(d.getCreatedAt()==null?"":fmt.format(d.getCreatedAt()));
|
||||
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);
|
||||
|
||||
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]);
|
||||
for (Material m : materials) {
|
||||
Row row = s3.createRow(r++);
|
||||
@ -141,11 +927,12 @@ public class ProjectServiceImpl
|
||||
row.createCell(13).setCellValue(m.getCustomAttrs()==null?"":m.getCustomAttrs());
|
||||
row.createCell(14).setCellValue(m.getCreatedAt()==null?"":fmt.format(m.getCreatedAt()));
|
||||
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);
|
||||
|
||||
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]);
|
||||
for (Scenario sc : scenarios) {
|
||||
Row row = s4.createRow(r++);
|
||||
@ -155,11 +942,12 @@ public class ProjectServiceImpl
|
||||
row.createCell(3).setCellValue(sc.getDescription()==null?"":sc.getDescription());
|
||||
row.createCell(4).setCellValue(sc.getCreatedAt()==null?"":fmt.format(sc.getCreatedAt()));
|
||||
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);
|
||||
|
||||
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]);
|
||||
for (Event ev : events) {
|
||||
Row row = s5.createRow(r++);
|
||||
@ -170,6 +958,7 @@ public class ProjectServiceImpl
|
||||
row.createCell(4).setCellValue(ev.getTriggerTime()==null?0:ev.getTriggerTime());
|
||||
row.createCell(5).setCellValue(ev.getAttrChanges()==null?"":ev.getAttrChanges());
|
||||
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);
|
||||
|
||||
|
||||
@ -0,0 +1 @@
|
||||
com.yfd.business.css.config.BusinessCssAutoConfiguration
|
||||
@ -18,4 +18,7 @@ spring:
|
||||
file-space: #项目文档空间
|
||||
files: D:\css\files\ #单独上传的文件附件
|
||||
system: D:\css\system\ #单独上传的文件
|
||||
security:
|
||||
dev:
|
||||
permit: true
|
||||
|
||||
|
||||
20
business-css/src/main/resources/application-local.yml
Normal file
20
business-css/src/main/resources/application-local.yml
Normal 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
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
@ -226,7 +226,7 @@ business-css/target/business-css-1.0-SNAPSHOT.jar # 业务服务(含内嵌 To
|
||||
使用项目内脚本强制 JDK 17(已为你添加):
|
||||
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:
|
||||
- 验证: 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%"
|
||||
- 再 mvn -version 应为 17
|
||||
|
||||
## 开发模式
|
||||
|
||||
- 进入前端目录: business-css/frontend
|
||||
- 安装依赖: npm install
|
||||
- 启动开发服务器: npm run dev
|
||||
- 访问: http://localhost:3000/
|
||||
|
||||
set "PATH=d:/Program Files/nodejs;%PATH%"
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user