Merge branch 'develop-business-css' of http://121.37.111.42:3000/ThbTech/JavaProjectRepo into develop-business-css

This commit is contained in:
limengnan 2025-12-23 14:54:03 +08:00
commit 0adfdf748d
34 changed files with 1824 additions and 258 deletions

View File

@ -66,18 +66,6 @@
<classifier>plain</classifier> <classifier>plain</classifier>
</dependency> </dependency>
<!-- Apache POI 用于 Excel 导入 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.1.2</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.2</version>
</dependency>
<!-- Lombok --> <!-- Lombok -->
<dependency> <dependency>
@ -101,6 +89,29 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId> <artifactId>spring-boot-maven-plugin</artifactId>
</plugin> </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.4.1</version>
<executions>
<execution>
<id>enforce-java</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<requireJavaVersion>
<version>[17,)</version>
</requireJavaVersion>
<requireMavenVersion>
<version>[3.6.3,)</version>
</requireMavenVersion>
</rules>
</configuration>
</execution>
</executions>
</plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>

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

@ -0,0 +1,64 @@
package com.yfd.business.css.config;
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 GroupedOpenApi businessCssGroup() {
return GroupedOpenApi.builder()
.group("css-business")
.packagesToScan("com.yfd.business.css.controller")
.pathsToMatch("/events/**", "/scenario-results/**", "/projects/**")
.build();
}
@Bean
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);
};
}
}

View File

@ -16,16 +16,6 @@ public class AlgorithmController {
@Autowired @Autowired
private AlgorithmService algorithmService; private AlgorithmService algorithmService;
@GetMapping
public List<Algorithm> getAllAlgorithms() {
return algorithmService.list();
}
@GetMapping("/page")
public Page<Algorithm> getAlgorithmsPage(@RequestParam(defaultValue = "1") int current,
@RequestParam(defaultValue = "10") int size) {
return algorithmService.page(new Page<>(current, size));
}
@GetMapping("/{id}") @GetMapping("/{id}")
public Algorithm getAlgorithmById(@PathVariable String id) { public Algorithm getAlgorithmById(@PathVariable String id) {
@ -48,12 +38,29 @@ public class AlgorithmController {
return algorithmService.removeById(id); return algorithmService.removeById(id);
} }
@DeleteMapping
public boolean deleteAlgorithms(@RequestBody List<String> ids) {
return algorithmService.removeByIds(ids);
}
/**
* 根据算法名称搜索并分页返回
* 输入参数查询参数 name算法名称关键词可为空pageNum页码默认1pageSize每页条数默认10
* 输出参数算法分页列表
* @param name 算法名称关键词可为空
* @param pageNum 页码
* @param pageSize 每页条数
* @return 算法分页列表
*/
@GetMapping("/search") @GetMapping("/search")
public List<Algorithm> searchAlgorithms(@RequestParam String keyword) { public Page<Algorithm> searchAlgorithms(@RequestParam(required = false) String name,
@RequestParam(defaultValue = "1") long pageNum,
@RequestParam(defaultValue = "10") long pageSize) {
QueryWrapper<Algorithm> queryWrapper = new QueryWrapper<>(); QueryWrapper<Algorithm> queryWrapper = new QueryWrapper<>();
queryWrapper.like("name", keyword) if (name != null && !name.isEmpty()) {
.or() queryWrapper.like("name", name);
.like("description", keyword); }
return algorithmService.list(queryWrapper); Page<Algorithm> page = new Page<>(pageNum, pageSize);
return algorithmService.page(page, queryWrapper);
} }
} }

View File

@ -1,6 +1,7 @@
package com.yfd.business.css.controller; package com.yfd.business.css.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yfd.business.css.domain.CriticalData; import com.yfd.business.css.domain.CriticalData;
import com.yfd.business.css.service.CriticalDataService; import com.yfd.business.css.service.CriticalDataService;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@ -81,15 +82,21 @@ public class CriticalDataController {
/** /**
* 6. 根据设备类型获取临界数据列表 * 6. 根据设备类型获取临界数据分页列表
* 输入参数查询参数 deviceType设备类型 * 输入参数查询参数 deviceType设备类型pageNum页码默认1pageSize每页条数默认10
* 输出参数临界数据列表按创建时间倒序 * 输出参数临界数据分页列表按创建时间倒序
* @param deviceType 设备类型 * @param deviceType 设备类型
* @return 临界数据列表 * @param pageNum 页码
* @param pageSize 每页条数
* @return 临界数据分页列表
*/ */
@GetMapping("/by-device-type") @GetMapping("/by-device-type")
public List<CriticalData> listByDeviceType(@RequestParam String deviceType) { public Page<CriticalData> listByDeviceType(@RequestParam String deviceType,
return criticalDataService.list( @RequestParam(defaultValue = "1") long pageNum,
@RequestParam(defaultValue = "10") long pageSize) {
Page<CriticalData> page = new Page<>(pageNum, pageSize);
return criticalDataService.page(
page,
new QueryWrapper<CriticalData>() new QueryWrapper<CriticalData>()
.eq("device_type", deviceType) .eq("device_type", deviceType)
.orderByDesc("created_at") .orderByDesc("created_at")

View File

@ -1,6 +1,7 @@
package com.yfd.business.css.controller; package com.yfd.business.css.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yfd.business.css.domain.Device; import com.yfd.business.css.domain.Device;
import com.yfd.business.css.service.DeviceService; import com.yfd.business.css.service.DeviceService;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@ -96,34 +97,24 @@ public class DeviceController {
} }
/**
* 6. 根据设备类型获取设备列表
* 输入参数查询参数 type设备类型
* 输出参数设备列表按创建时间倒序
* @param type 设备类型
* @return 设备列表
*/
@GetMapping("/by-type")
public List<Device> listByType(@RequestParam String type) {
return deviceService.list(
new QueryWrapper<Device>()
.eq("type", type)
.orderByDesc("created_at")
);
}
/** /**
* 7. 设备查询类型 + 名称 * 6. 设备分页查询类型 可选 + 名称 可选
* 输入参数查询参数 type可选name可选 * 输入参数查询参数 type可选name可选pageNum页码默认1pageSize每页条数默认10
* 输出参数设备列表按创建时间倒序 * 输出参数设备分页列表按创建时间倒序
* @param type 设备类型可选 * @param type 设备类型可选
* @param name 设备名称关键词可选 * @param name 设备名称关键词可选
* @return 设备列表 * @param pageNum 页码
* @param pageSize 每页条数
* @return 设备分页列表
*/ */
@GetMapping("/search") @GetMapping("/search")
public List<Device> search(@RequestParam(required = false) String type, public Page<Device> search(@RequestParam(required = false) String type,
@RequestParam(required = false) String name) { @RequestParam(required = false) String name,
@RequestParam(defaultValue = "1") long pageNum,
@RequestParam(defaultValue = "20") long pageSize) {
Page<Device> page = new Page<>(pageNum, pageSize);
QueryWrapper<Device> wrapper = new QueryWrapper<>(); QueryWrapper<Device> wrapper = new QueryWrapper<>();
if (type != null && !type.isEmpty()) { if (type != null && !type.isEmpty()) {
@ -133,6 +124,6 @@ public class DeviceController {
wrapper.like("name", name); wrapper.like("name", name);
} }
return deviceService.list(wrapper.orderByDesc("created_at")); return deviceService.page(page, wrapper.orderByDesc("created_at"));
} }
} }

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;
} }
/** /**
@ -33,6 +43,32 @@ public class EventController {
)); ));
} }
/**
* 修改始发事件
* 输入参数路径参数 eventId事件ID请求体中的事件对象
* 输出参数标准响应结构包含修改后的事件对象
* @param eventId 事件ID
* @param event 事件对象
* @return 修改结果
*/
@PutMapping("/{eventId}")
public ResponseEntity<Map<String, Object>> updateEvent(@PathVariable String eventId,
@RequestBody Event event) {
event.setEventId(eventId);
boolean ok = eventService.updateById(event);
if (!ok) {
return ResponseEntity.badRequest().body(Map.of(
"code", 1,
"msg", "修改失败"
));
}
return ResponseEntity.ok(Map.of(
"code", 0,
"msg", "修改成功",
"data", event
));
}
/** /**
* 修改 Event attr_changes * 修改 Event attr_changes
*/ */
@ -48,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);
@ -98,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

@ -1,6 +1,7 @@
package com.yfd.business.css.controller; package com.yfd.business.css.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yfd.business.css.domain.Material; import com.yfd.business.css.domain.Material;
import com.yfd.business.css.service.MaterialService; import com.yfd.business.css.service.MaterialService;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@ -79,18 +80,23 @@ public class MaterialController {
} }
/** /**
* 5. 根据物料名称搜索 * 5. 根据物料名称搜索可为空并分页返回
* 输入参数查询参数 name物料名称关键词 * 输入参数查询参数 name物料名称关键词可为空pageNum页码默认1pageSize每页条数默认10
* 输出参数物料列表按创建时间倒序 * 输出参数物料分页列表按创建时间倒序
* @param name 物料名称关键词 * @param name 物料名称关键词可为空
* @return 物料列表 * @param pageNum 页码
* @param pageSize 每页条数
* @return 物料分页列表
*/ */
@GetMapping("/search") @GetMapping("/search")
public List<Material> search(@RequestParam String name) { public Page<Material> search(@RequestParam(required = false) String name,
return materialService.list( @RequestParam(defaultValue = "1") long pageNum,
new QueryWrapper<Material>() @RequestParam(defaultValue = "10") long pageSize) {
.like("name", name) QueryWrapper<Material> wrapper = new QueryWrapper<>();
.orderByDesc("created_at") if (name != null && !name.isEmpty()) {
); wrapper.like("name", name);
}
Page<Material> page = new Page<>(pageNum, pageSize);
return materialService.page(page, wrapper.orderByDesc("created_at"));
} }
} }

View File

@ -1,6 +1,7 @@
package com.yfd.business.css.controller; package com.yfd.business.css.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yfd.business.css.domain.Project; import com.yfd.business.css.domain.Project;
import com.yfd.business.css.service.ProjectService; import com.yfd.business.css.service.ProjectService;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
@ -11,6 +12,12 @@ import org.springframework.web.bind.annotation.*;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.io.ByteArrayOutputStream;
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 java.time.format.DateTimeFormatter;
@RestController @RestController
@RequestMapping("/projects") @RequestMapping("/projects")
@ -18,6 +25,8 @@ public class ProjectController {
@Resource @Resource
private ProjectService projectService; private ProjectService projectService;
@Resource
private com.fasterxml.jackson.databind.ObjectMapper objectMapper;
/** /**
* 1. 新增项目 * 1. 新增项目
@ -67,63 +76,65 @@ public class ProjectController {
return projectService.removeByIds(ids); return projectService.removeByIds(ids);
} }
/**
* 4. 导出所有项目
* 输入参数
* 导出描述返回所有项目的 JSON 数组作为附件 `projects.json`
* 输出参数附件字节流application/octet-stream
* @return 附件响应文件名为 projects.json
*/
@GetMapping("/export/all")
public ResponseEntity<byte[]> exportAll() {
byte[] data = projectService.exportAllProjects();
/**
* 4.1 导出所有项目Excel
* 输入参数
* 导出描述返回所有项目的 Excel 附件 `projects.xlsx`
* 导出列project_id, code, name, description, created_at, updated_at,modifier
* @return 附件响应文件名为 projects.xlsx
*/
@GetMapping("/exportAllExports")
public ResponseEntity<byte[]> exportAllExports() {
byte[] bytes = projectService.exportAllProjectsExcel();
return ResponseEntity.ok() return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=projects.xlsx")
"attachment; filename=projects.json")
.contentType(MediaType.APPLICATION_OCTET_STREAM) .contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(data); .body(bytes);
} }
/** /**
* 5. 导出项目工程 * 5.1 导出项目工程Excel Sheet
* 输入参数路径参数项目ID * 输入参数路径参数项目ID
* 导出描述返回 ZIP 附件 `project_{id}.zip`包含以下 JSON 文件 * 导出描述返回 Excel 附件 `project_{id}.xlsx`包含以下 Sheet
* - project.json项目对象 * - projects项目- project_id, code, name, description, created_at, updated_at
* - devices.json设备列表 * - devices设备- device_id, project_id, code, type, name, size, volume, flow_rate, pulse_velocity, created_at, updated_at
* - materials.json物料列表 * - materials物料- 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
* - scenarios.json情景列表 * - scenarios情景- scenario_id, project_id, name, description, created_at, updated_at
* - events.json事件列表 * - events事件- event_id, scenario_id, device_id, material_id, trigger_time, attr_changes, created_at
* - scenario-results.json情景结果列表 * - scenario_results情景结果- scenario_id, device_id, step, keff_value, attr_state
* 输出参数附件字节流application/octet-stream * 输出参数附件字节流application/octet-stream
* @param id 项目ID * @param id 项目ID
* @return 附件响应文件名为 project_{id}.zip * @return 附件响应文件名为 project_{id}.xlsx
*/ */
@GetMapping("/{id}/export") @GetMapping("/{id}/exportProject")
public ResponseEntity<byte[]> exportProject(@PathVariable String id) { public ResponseEntity<byte[]> exportProjectExcel(@PathVariable String id) {
byte[] data = projectService.exportProjectEngineering(id); byte[] bytes = projectService.exportProjectEngineeringExcel(id);
return ResponseEntity.ok() return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=project_" + id + ".xlsx")
"attachment; filename=project_" + id + ".zip")
.contentType(MediaType.APPLICATION_OCTET_STREAM) .contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(data); .body(bytes);
} }
/** /**
* 6. 根据项目名称搜索 * 6. 根据项目名称搜索可为空并分页返回
* 输入参数查询参数 name项目名称关键词 * 输入参数查询参数 name项目名称关键词可为空pageNum页码默认1pageSize每页条数默认10
* 输出参数项目列表 * 输出参数项目分页列表按创建时间倒序
* @param name 项目名称关键词 * @param name 项目名称关键词可为空
* @return 匹配的项目列表 * @param pageNum 页码
* @param pageSize 每页条数
* @return 项目分页列表
*/ */
@GetMapping("/search") @GetMapping("/search")
public List<Project> searchByName(@RequestParam String name) { public Page<Project> search(@RequestParam(required = false) String name,
return projectService.list( @RequestParam(defaultValue = "1") long pageNum,
new QueryWrapper<Project>() @RequestParam(defaultValue = "10") long pageSize) {
.like("name", name) QueryWrapper<Project> wrapper = new QueryWrapper<>();
.orderByDesc("created_at") if (name != null && !name.isEmpty()) {
); wrapper.like("name", name);
}
Page<Project> page = new Page<>(pageNum, pageSize);
return projectService.page(page, wrapper.orderByDesc("created_at"));
} }
/** /**
@ -156,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,
@ -167,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,6 +1,7 @@
package com.yfd.business.css.controller; package com.yfd.business.css.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yfd.business.css.domain.Scenario; import com.yfd.business.css.domain.Scenario;
import com.yfd.business.css.service.ScenarioService; import com.yfd.business.css.service.ScenarioService;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@ -63,21 +64,7 @@ public class ScenarioController {
return scenarioService.removeByIds(ids); return scenarioService.removeByIds(ids);
} }
/**
* 4. 根据情景名称搜索
* 输入参数查询参数 name情景名称关键词
* 输出参数情景列表按创建时间倒序
* @param name 情景名称关键词
* @return 情景列表
*/
@GetMapping("/search")
public List<Scenario> searchByName(@RequestParam String name) {
return scenarioService.list(
new QueryWrapper<Scenario>()
.like("name", name)
.orderByDesc("created_at")
);
}
/** /**
* 5. 根据情景ID获取情景记录 * 5. 根据情景ID获取情景记录
@ -92,18 +79,25 @@ public class ScenarioController {
} }
/** /**
* 6. 根据项目ID查询情景列表 * 6. 根据项目ID与情景名称查询情景列表分页
* 输入参数查询参数 projectId项目ID * 输入参数projectId项目IDname情景名称关键词可为空pageNum默认1pageSize默认20
* 输出参数情景列表按创建时间倒序 * 输出参数情景分页列表按创建时间倒序
* @param projectId 项目ID * @param projectId 项目ID
* @return 情景列表 * @param name 情景名称关键词可为空
* @param pageNum 页码
* @param pageSize 每页条数
* @return 情景分页列表
*/ */
@GetMapping("/by-project") @GetMapping("/by-project")
public List<Scenario> listByProject(@RequestParam String projectId) { public Page<Scenario> listByProject(@RequestParam String projectId,
return scenarioService.list( @RequestParam(required = false) String name,
new QueryWrapper<Scenario>() @RequestParam(defaultValue = "1") long pageNum,
.eq("project_id", projectId) @RequestParam(defaultValue = "20") long pageSize) {
.orderByDesc("created_at") Page<Scenario> page = new Page<>(pageNum, pageSize);
); QueryWrapper<Scenario> wrapper = new QueryWrapper<Scenario>().eq("project_id", projectId);
if (name != null && !name.isEmpty()) {
wrapper.like("name", name);
}
return scenarioService.page(page, wrapper.orderByDesc("created_at"));
} }
} }

View File

@ -1,54 +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 java.util.List;
import java.util.Map;
@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设备ID
* 输出参数情景结果列表按时间点 step 排序
* @param scenarioId 情景ID
* @param deviceId 设备ID
* @return 情景结果列表
*/ */
@GetMapping("/by-scenario") @GetMapping("/by-scenario")
public List<ScenarioResult> listByScenario(@RequestParam String scenarioId, public ResponseEntity<?> listByScenario(
@RequestParam String deviceId) { @RequestParam String scenarioId,
return scenarioResultService.list( @RequestParam(required = false) String deviceId,
new QueryWrapper<ScenarioResult>() @RequestParam(required = false) Integer stepFrom,
.eq("scenario_id", scenarioId) @RequestParam(required = false) Integer stepTo,
.eq("device_id", deviceId) @RequestParam(defaultValue = "1") long pageNum,
.orderByAsc("step") @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);
}
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
));
} }
} }

View File

@ -38,4 +38,7 @@ public class Algorithm implements Serializable {
@TableField("output_params") @TableField("output_params")
private String outputParams; private String outputParams;
@TableField("modifier")
private String modifier;
} }

View File

@ -45,4 +45,7 @@ public class CriticalData implements Serializable {
@TableField("updated_at") @TableField("updated_at")
private LocalDateTime updatedAt; private LocalDateTime updatedAt;
@TableField("modifier")
private String modifier;
} }

View File

@ -47,4 +47,7 @@ public class Device implements Serializable {
@TableField("updated_at") @TableField("updated_at")
private LocalDateTime updatedAt; private LocalDateTime updatedAt;
@TableField("modifier")
private String modifier;
} }

View File

@ -35,4 +35,7 @@ public class Event implements Serializable {
@TableField("created_at") @TableField("created_at")
private LocalDateTime createdAt; private LocalDateTime createdAt;
@TableField("modifer")
private String modifier;
} }

View File

@ -63,4 +63,7 @@ public class Material implements Serializable {
@TableField("updated_at") @TableField("updated_at")
private LocalDateTime updatedAt; private LocalDateTime updatedAt;
@TableField("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,8 +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("modifier")
private String modifier;
} }

View File

@ -32,4 +32,7 @@ public class Scenario implements Serializable {
@TableField("updated_at") @TableField("updated_at")
private LocalDateTime updatedAt; private LocalDateTime updatedAt;
@TableField("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

@ -4,14 +4,38 @@ import com.baomidou.mybatisplus.extension.service.IService;
import com.yfd.business.css.domain.Project; import com.yfd.business.css.domain.Project;
public interface ProjectService extends IService<Project> { public interface ProjectService extends IService<Project> {
/**
* 导出所有项目
*/
byte[] exportAllProjects();
/** /**
* 导出项目工程设备 + 物料 + 场景 + 事件 + 结果 * 导出所有项目为 Excel
*/ */
byte[] exportProjectEngineering(String projectId); byte[] exportAllProjectsExcel();
/**
* 导出所有项目为 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);
} }

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

@ -355,33 +355,6 @@
</goals> </goals>
</execution> </execution>
</executions> </executions>
</plugin>
<!-- 固化构建规则Maven Enforcer -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.4.1</version>
<executions>
<execution>
<id>enforce-rules</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<!-- 推荐升级 Maven 到 3.8+;临时放宽版本以便构建 -->
<requireMavenVersion>
<version>[3.6.3,)</version>
</requireMavenVersion>
<requireJavaVersion>
<version>[17,)</version>
</requireJavaVersion>
<dependencyConvergence/>
<!-- 如需严格校验插件版本,可在升级 Maven 后再启用 -->
</rules>
</configuration>
</execution>
</executions>
</plugin> </plugin>
<!-- 额外生成 classes JAR便于其他业务工程作为依赖引用 --> <!-- 额外生成 classes JAR便于其他业务工程作为依赖引用 -->
<plugin> <plugin>

45
mvn-settings.xml Normal file
View File

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.2.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.2.0 https://maven.apache.org/xsd/settings-1.2.0.xsd">
<mirrors>
<mirror>
<id>aliyunmaven</id>
<name>Aliyun Maven</name>
<mirrorOf>central</mirrorOf>
<url>https://maven.aliyun.com/repository/public</url>
</mirror>
</mirrors>
<profiles>
<profile>
<id>aliyun</id>
<repositories>
<repository>
<id>central</id>
<url>https://repo.maven.apache.org/maven2</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>central</id>
<url>https://repo.maven.apache.org/maven2</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</profile>
</profiles>
<activeProfiles>
<activeProfile>aliyun</activeProfile>
</activeProfiles>
</settings>

4
scripts/mvn17.cmd Normal file
View File

@ -0,0 +1,4 @@
@echo off
set "JAVA_HOME=C:\Program Files\Eclipse Adoptium\jdk-17.0.17.10-hotspot"
set "PATH=%JAVA_HOME%\bin;%PATH%"
mvn %*

View File

@ -162,6 +162,8 @@ src/main/resources
# 1. 安装 framework 到本地仓库 # 1. 安装 framework 到本地仓库
mvn -DskipTests clean install -pl framework mvn -DskipTests clean install -pl framework
# 2. 启动业务模块(自动依赖 framework # 2. 启动业务模块(自动依赖 framework
mvn -DskipTests spring-boot:run -pl business-css mvn -DskipTests spring-boot:run -pl business-css
``` ```
@ -219,3 +221,26 @@ business-css/target/business-css-1.0-SNAPSHOT.jar # 业务服务(含内嵌 To
--- ---
> **一句话总结**framework 做“平台”business-css 做“产品”;平台沉淀,产品迭代,互不污染,横向复制。 > **一句话总结**framework 做“平台”business-css 做“产品”;平台沉淀,产品迭代,互不污染,横向复制。
## 快速稳定方案(作用:脚本先设置 JAVA_HOME 与 PATH 指向 17再调用 Maven确保所有构建/运行都用 JDK 17不受工具自带 JDK 8 影响。)
使用项目内脚本强制 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=business
## 在工具内终端验证并调整到 17
- 验证: mvn -version 、 where java 、 where mvn
- 临时修正当前终端(一次性手动):
- set "JAVA_HOME=C:\Program Files\Eclipse Adoptium\jdk-17.0.17.10-hotspot"
- 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%"