Merge branch 'develop-business-css' of http://121.37.111.42:3000/ThbTech/JavaProjectRepo into develop-business-css
This commit is contained in:
commit
0adfdf748d
@ -66,18 +66,6 @@
|
||||
<classifier>plain</classifier>
|
||||
</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 -->
|
||||
<dependency>
|
||||
@ -101,6 +89,29 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</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>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
|
||||
@ -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 {
|
||||
}
|
||||
@ -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);
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -16,16 +16,6 @@ public class AlgorithmController {
|
||||
@Autowired
|
||||
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}")
|
||||
public Algorithm getAlgorithmById(@PathVariable String id) {
|
||||
@ -48,12 +38,29 @@ public class AlgorithmController {
|
||||
return algorithmService.removeById(id);
|
||||
}
|
||||
|
||||
@DeleteMapping
|
||||
public boolean deleteAlgorithms(@RequestBody List<String> ids) {
|
||||
return algorithmService.removeByIds(ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据算法名称搜索并分页返回
|
||||
* 输入参数:查询参数 name(算法名称关键词,可为空),pageNum(页码,默认1),pageSize(每页条数,默认10)
|
||||
* 输出参数:算法分页列表
|
||||
* @param name 算法名称关键词(可为空)
|
||||
* @param pageNum 页码
|
||||
* @param pageSize 每页条数
|
||||
* @return 算法分页列表
|
||||
*/
|
||||
@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.like("name", keyword)
|
||||
.or()
|
||||
.like("description", keyword);
|
||||
return algorithmService.list(queryWrapper);
|
||||
if (name != null && !name.isEmpty()) {
|
||||
queryWrapper.like("name", name);
|
||||
}
|
||||
Page<Algorithm> page = new Page<>(pageNum, pageSize);
|
||||
return algorithmService.page(page, queryWrapper);
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
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.CriticalData;
|
||||
import com.yfd.business.css.service.CriticalDataService;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
@ -81,15 +82,21 @@ public class CriticalDataController {
|
||||
|
||||
|
||||
/**
|
||||
* 6. 根据设备类型获取临界数据列表
|
||||
* 输入参数:查询参数 deviceType(设备类型)
|
||||
* 输出参数:临界数据列表(按创建时间倒序)
|
||||
* 6. 根据设备类型获取临界数据分页列表
|
||||
* 输入参数:查询参数 deviceType(设备类型),pageNum(页码,默认1),pageSize(每页条数,默认10)
|
||||
* 输出参数:临界数据分页列表(按创建时间倒序)
|
||||
* @param deviceType 设备类型
|
||||
* @return 临界数据列表
|
||||
* @param pageNum 页码
|
||||
* @param pageSize 每页条数
|
||||
* @return 临界数据分页列表
|
||||
*/
|
||||
@GetMapping("/by-device-type")
|
||||
public List<CriticalData> listByDeviceType(@RequestParam String deviceType) {
|
||||
return criticalDataService.list(
|
||||
public Page<CriticalData> listByDeviceType(@RequestParam String deviceType,
|
||||
@RequestParam(defaultValue = "1") long pageNum,
|
||||
@RequestParam(defaultValue = "10") long pageSize) {
|
||||
Page<CriticalData> page = new Page<>(pageNum, pageSize);
|
||||
return criticalDataService.page(
|
||||
page,
|
||||
new QueryWrapper<CriticalData>()
|
||||
.eq("device_type", deviceType)
|
||||
.orderByDesc("created_at")
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
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.Device;
|
||||
import com.yfd.business.css.service.DeviceService;
|
||||
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. 设备查询(类型 + 名称)
|
||||
* 输入参数:查询参数 type(可选),name(可选)
|
||||
* 输出参数:设备列表(按创建时间倒序)
|
||||
* 6. 设备分页查询(类型 可选 + 名称 可选)
|
||||
* 输入参数:查询参数 type(可选),name(可选),pageNum(页码,默认1),pageSize(每页条数,默认10)
|
||||
* 输出参数:设备分页列表(按创建时间倒序)
|
||||
* @param type 设备类型(可选)
|
||||
* @param name 设备名称关键词(可选)
|
||||
* @return 设备列表
|
||||
* @param pageNum 页码
|
||||
* @param pageSize 每页条数
|
||||
* @return 设备分页列表
|
||||
*/
|
||||
@GetMapping("/search")
|
||||
public List<Device> search(@RequestParam(required = false) String type,
|
||||
@RequestParam(required = false) String name) {
|
||||
|
||||
public Page<Device> search(@RequestParam(required = false) String type,
|
||||
@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<>();
|
||||
|
||||
if (type != null && !type.isEmpty()) {
|
||||
@ -133,6 +124,6 @@ public class DeviceController {
|
||||
wrapper.like("name", name);
|
||||
}
|
||||
|
||||
return deviceService.list(wrapper.orderByDesc("created_at"));
|
||||
return deviceService.page(page, wrapper.orderByDesc("created_at"));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -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
|
||||
*/
|
||||
@ -48,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);
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
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.Material;
|
||||
import com.yfd.business.css.service.MaterialService;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
@ -79,18 +80,23 @@ public class MaterialController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 5. 根据物料名称搜索
|
||||
* 输入参数:查询参数 name(物料名称关键词)
|
||||
* 输出参数:物料列表(按创建时间倒序)
|
||||
* @param name 物料名称关键词
|
||||
* @return 物料列表
|
||||
* 5. 根据物料名称搜索(可为空)并分页返回
|
||||
* 输入参数:查询参数 name(物料名称关键词,可为空),pageNum(页码,默认1),pageSize(每页条数,默认10)
|
||||
* 输出参数:物料分页列表(按创建时间倒序)
|
||||
* @param name 物料名称关键词(可为空)
|
||||
* @param pageNum 页码
|
||||
* @param pageSize 每页条数
|
||||
* @return 物料分页列表
|
||||
*/
|
||||
@GetMapping("/search")
|
||||
public List<Material> search(@RequestParam String name) {
|
||||
return materialService.list(
|
||||
new QueryWrapper<Material>()
|
||||
.like("name", name)
|
||||
.orderByDesc("created_at")
|
||||
);
|
||||
public Page<Material> search(@RequestParam(required = false) String name,
|
||||
@RequestParam(defaultValue = "1") long pageNum,
|
||||
@RequestParam(defaultValue = "10") long pageSize) {
|
||||
QueryWrapper<Material> wrapper = new QueryWrapper<>();
|
||||
if (name != null && !name.isEmpty()) {
|
||||
wrapper.like("name", name);
|
||||
}
|
||||
Page<Material> page = new Page<>(pageNum, pageSize);
|
||||
return materialService.page(page, wrapper.orderByDesc("created_at"));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
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.Project;
|
||||
import com.yfd.business.css.service.ProjectService;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
@ -11,6 +12,12 @@ import org.springframework.web.bind.annotation.*;
|
||||
import jakarta.annotation.Resource;
|
||||
import java.util.List;
|
||||
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
|
||||
@RequestMapping("/projects")
|
||||
@ -18,6 +25,8 @@ public class ProjectController {
|
||||
|
||||
@Resource
|
||||
private ProjectService projectService;
|
||||
@Resource
|
||||
private com.fasterxml.jackson.databind.ObjectMapper objectMapper;
|
||||
|
||||
/**
|
||||
* 1. 新增项目
|
||||
@ -67,63 +76,65 @@ public class ProjectController {
|
||||
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()
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION,
|
||||
"attachment; filename=projects.json")
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=projects.xlsx")
|
||||
.contentType(MediaType.APPLICATION_OCTET_STREAM)
|
||||
.body(data);
|
||||
.body(bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 5. 导出项目工程
|
||||
* 5.1 导出项目工程(Excel 多 Sheet)
|
||||
* 输入参数:路径参数项目ID
|
||||
* 导出描述:返回 ZIP 附件 `project_{id}.zip`,包含以下 JSON 文件:
|
||||
* - project.json:项目对象
|
||||
* - devices.json:设备列表
|
||||
* - materials.json:物料列表
|
||||
* - scenarios.json:情景列表
|
||||
* - events.json:事件列表
|
||||
* - scenario-results.json:情景结果列表
|
||||
* 导出描述:返回 Excel 附件 `project_{id}.xlsx`,包含以下 Sheet:
|
||||
* - projects(项目)- project_id, code, name, description, created_at, updated_at
|
||||
* - devices(设备)- device_id, project_id, code, type, name, size, volume, flow_rate, pulse_velocity, created_at, updated_at
|
||||
* - 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(情景)- scenario_id, project_id, name, description, created_at, updated_at
|
||||
* - events(事件)- event_id, scenario_id, device_id, material_id, trigger_time, attr_changes, created_at
|
||||
* - scenario_results(情景结果)- scenario_id, device_id, step, keff_value, attr_state
|
||||
* 输出参数:附件字节流(application/octet-stream)
|
||||
* @param id 项目ID
|
||||
* @return 附件响应,文件名为 project_{id}.zip
|
||||
* @return 附件响应,文件名为 project_{id}.xlsx
|
||||
*/
|
||||
@GetMapping("/{id}/export")
|
||||
public ResponseEntity<byte[]> exportProject(@PathVariable String id) {
|
||||
byte[] data = projectService.exportProjectEngineering(id);
|
||||
|
||||
@GetMapping("/{id}/exportProject")
|
||||
public ResponseEntity<byte[]> exportProjectExcel(@PathVariable String id) {
|
||||
byte[] bytes = projectService.exportProjectEngineeringExcel(id);
|
||||
return ResponseEntity.ok()
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION,
|
||||
"attachment; filename=project_" + id + ".zip")
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=project_" + id + ".xlsx")
|
||||
.contentType(MediaType.APPLICATION_OCTET_STREAM)
|
||||
.body(data);
|
||||
.body(bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 6. 根据项目名称搜索
|
||||
* 输入参数:查询参数 name(项目名称关键词)
|
||||
* 输出参数:项目列表
|
||||
* @param name 项目名称关键词
|
||||
* @return 匹配的项目列表
|
||||
* 6. 根据项目名称搜索(可为空)并分页返回
|
||||
* 输入参数:查询参数 name(项目名称关键词,可为空),pageNum(页码,默认1),pageSize(每页条数,默认10)
|
||||
* 输出参数:项目分页列表(按创建时间倒序)
|
||||
* @param name 项目名称关键词(可为空)
|
||||
* @param pageNum 页码
|
||||
* @param pageSize 每页条数
|
||||
* @return 项目分页列表
|
||||
*/
|
||||
@GetMapping("/search")
|
||||
public List<Project> searchByName(@RequestParam String name) {
|
||||
return projectService.list(
|
||||
new QueryWrapper<Project>()
|
||||
.like("name", name)
|
||||
.orderByDesc("created_at")
|
||||
);
|
||||
public Page<Project> search(@RequestParam(required = false) String name,
|
||||
@RequestParam(defaultValue = "1") long pageNum,
|
||||
@RequestParam(defaultValue = "10") long pageSize) {
|
||||
QueryWrapper<Project> wrapper = new QueryWrapper<>();
|
||||
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不能为空"
|
||||
));
|
||||
}
|
||||
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,
|
||||
@ -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
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
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.Scenario;
|
||||
import com.yfd.business.css.service.ScenarioService;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
@ -63,21 +64,7 @@ public class ScenarioController {
|
||||
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获取情景记录
|
||||
@ -92,18 +79,25 @@ public class ScenarioController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 6. 根据项目ID查询情景列表
|
||||
* 输入参数:查询参数 projectId(项目ID)
|
||||
* 输出参数:情景列表(按创建时间倒序)
|
||||
* 6. 根据项目ID与情景名称查询情景列表(分页)
|
||||
* 输入参数:projectId(项目ID),name(情景名称关键词,可为空),pageNum(默认1),pageSize(默认20)
|
||||
* 输出参数:情景分页列表(按创建时间倒序)
|
||||
* @param projectId 项目ID
|
||||
* @return 情景列表
|
||||
* @param name 情景名称关键词(可为空)
|
||||
* @param pageNum 页码
|
||||
* @param pageSize 每页条数
|
||||
* @return 情景分页列表
|
||||
*/
|
||||
@GetMapping("/by-project")
|
||||
public List<Scenario> listByProject(@RequestParam String projectId) {
|
||||
return scenarioService.list(
|
||||
new QueryWrapper<Scenario>()
|
||||
.eq("project_id", projectId)
|
||||
.orderByDesc("created_at")
|
||||
);
|
||||
public Page<Scenario> listByProject(@RequestParam String projectId,
|
||||
@RequestParam(required = false) String name,
|
||||
@RequestParam(defaultValue = "1") long pageNum,
|
||||
@RequestParam(defaultValue = "20") long pageSize) {
|
||||
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"));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,54 +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 java.util.List;
|
||||
import java.util.Map;
|
||||
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)
|
||||
* 输出参数:情景结果列表(按时间点 step 排序)
|
||||
* @param scenarioId 情景ID
|
||||
* @param deviceId 设备ID
|
||||
* @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 List<ScenarioResult> listByScenario(@RequestParam String scenarioId,
|
||||
@RequestParam String deviceId) {
|
||||
return scenarioResultService.list(
|
||||
new QueryWrapper<ScenarioResult>()
|
||||
.eq("scenario_id", scenarioId)
|
||||
.eq("device_id", deviceId)
|
||||
.orderByAsc("step")
|
||||
);
|
||||
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);
|
||||
}
|
||||
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
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@ -38,4 +38,7 @@ public class Algorithm implements Serializable {
|
||||
|
||||
@TableField("output_params")
|
||||
private String outputParams;
|
||||
|
||||
@TableField("modifier")
|
||||
private String modifier;
|
||||
}
|
||||
@ -45,4 +45,7 @@ public class CriticalData implements Serializable {
|
||||
|
||||
@TableField("updated_at")
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@TableField("modifier")
|
||||
private String modifier;
|
||||
}
|
||||
|
||||
@ -47,4 +47,7 @@ public class Device implements Serializable {
|
||||
|
||||
@TableField("updated_at")
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@TableField("modifier")
|
||||
private String modifier;
|
||||
}
|
||||
|
||||
@ -35,4 +35,7 @@ public class Event implements Serializable {
|
||||
|
||||
@TableField("created_at")
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
@TableField("modifer")
|
||||
private String modifier;
|
||||
}
|
||||
|
||||
@ -63,4 +63,7 @@ public class Material implements Serializable {
|
||||
|
||||
@TableField("updated_at")
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@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,8 +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("modifier")
|
||||
private String modifier;
|
||||
}
|
||||
|
||||
@ -32,4 +32,7 @@ public class Scenario implements Serializable {
|
||||
|
||||
@TableField("updated_at")
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
@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;
|
||||
}
|
||||
@ -4,14 +4,38 @@ import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.yfd.business.css.domain.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);
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -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());
|
||||
}
|
||||
}
|
||||
@ -355,33 +355,6 @@
|
||||
</goals>
|
||||
</execution>
|
||||
</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>
|
||||
<!-- 额外生成 classes JAR,便于其他业务工程作为依赖引用 -->
|
||||
<plugin>
|
||||
|
||||
45
mvn-settings.xml
Normal file
45
mvn-settings.xml
Normal 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
4
scripts/mvn17.cmd
Normal 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 %*
|
||||
@ -162,6 +162,8 @@ src/main/resources
|
||||
# 1. 安装 framework 到本地仓库
|
||||
mvn -DskipTests clean install -pl framework
|
||||
|
||||
|
||||
|
||||
# 2. 启动业务模块(自动依赖 framework)
|
||||
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 做“产品”;平台沉淀,产品迭代,互不污染,横向复制。
|
||||
|
||||
## 快速稳定方案(作用:脚本先设置 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%"
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user