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

This commit is contained in:
limengnan 2026-04-02 16:09:35 +08:00
commit da87415599
16 changed files with 763 additions and 6 deletions

View File

@ -9,6 +9,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.Authentication;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import com.yfd.platform.annotation.Log;
@ -45,6 +46,7 @@ public class AlgorithmController {
}
@Log(value = "新增算法", module = "算法管理")
// @PreAuthorize("hasAuthority('algorithm:add')")
@PostMapping
@Operation(summary = "新增算法", description = "请求体传入算法对象,返回是否新增成功")
public boolean createAlgorithm(@RequestBody Algorithm algorithm) {
@ -55,6 +57,7 @@ public class AlgorithmController {
}
@Log(value = "修改算法", module = "算法管理")
// @PreAuthorize("hasAuthority('algorithm:update')")
@PutMapping
@Operation(summary = "修改算法", description = "请求体传入算法对象(需包含主键),返回是否修改成功")
public boolean updateAlgorithm(@RequestBody Algorithm algorithm) {
@ -64,6 +67,7 @@ public class AlgorithmController {
}
@Log(value = "删除算法", module = "算法管理")
// @PreAuthorize("hasAuthority('algorithm:delete')")
@DeleteMapping("/{id}")
@Operation(summary = "删除算法(单条)", description = "根据算法ID删除算法")
public boolean deleteAlgorithm(@PathVariable String id) {
@ -71,6 +75,7 @@ public class AlgorithmController {
}
@Log(value = "批量删除算法", module = "算法管理")
// @PreAuthorize("hasAuthority('algorithm:deleteBatch')")
@DeleteMapping
@Operation(summary = "删除算法(批量)", description = "请求体传入算法ID列表批量删除算法")
public boolean deleteAlgorithms(@RequestBody List<String> ids) {
@ -78,7 +83,7 @@ public class AlgorithmController {
}
@Log(value = "激活算法", module = "算法管理")
//算法类型激活
// @PreAuthorize("hasAuthority('algorithm:activate')")
@PostMapping("/activate")
@Operation(summary = "激活算法", description = "激活当前算法类型")
public boolean activate(@RequestParam String algorithmId) {
@ -92,7 +97,7 @@ public class AlgorithmController {
}
@Log(value = "关闭算法", module = "算法管理")
//算法类型关闭
// @PreAuthorize("hasAuthority('algorithm:unactivate')")
@PostMapping("/unactivate")
@Operation(summary = "关闭算法", description = "关闭当前算法类型")
public boolean unactivate(@RequestParam String algorithmId) {

View File

@ -11,6 +11,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.Authentication;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import com.yfd.platform.annotation.Log;
@ -53,6 +54,7 @@ public class AlgorithmModelController {
}
@Log(value = "新增模型版本", module = "算法模型管理")
// @PreAuthorize("hasAuthority('algorithmModel:add')")
@PostMapping
@Operation(summary = "新增模型版本", description = "请求体传入模型版本对象,返回是否新增成功")
public boolean create(@RequestBody AlgorithmModel model) {
@ -63,6 +65,7 @@ public class AlgorithmModelController {
}
@Log(value = "修改模型版本", module = "算法模型管理")
// @PreAuthorize("hasAuthority('algorithmModel:update')")
@PutMapping
@Operation(summary = "修改模型版本", description = "请求体传入模型版本对象(需包含主键),返回是否修改成功")
public boolean update(@RequestBody AlgorithmModel model) {
@ -72,6 +75,7 @@ public class AlgorithmModelController {
}
@Log(value = "删除模型版本", module = "算法模型管理")
// @PreAuthorize("hasAuthority('algorithmModel:delete')")
@DeleteMapping("/{id}")
@Operation(summary = "删除模型版本(单条)", description = "根据模型ID删除模型版本")
public boolean delete(@PathVariable String id) {
@ -79,6 +83,7 @@ public class AlgorithmModelController {
}
@Log(value = "批量删除模型版本", module = "算法模型管理")
// @PreAuthorize("hasAuthority('algorithmModel:deleteBatch')")
@DeleteMapping
@Operation(summary = "删除模型版本(批量)", description = "请求体传入模型ID列表批量删除模型版本")
public boolean deleteBatch(@RequestBody List<String> ids) {
@ -124,7 +129,7 @@ public class AlgorithmModelController {
}
@Log(value = "激活模型版本", module = "算法模型管理")
//版本激活
@PreAuthorize("hasAuthority('algorithmModel:activate')")
@PostMapping("/activate")
@Operation(summary = "激活模型版本", description = "将目标模型版本设为当前,并将同组(算法+设备+材料)其他版本设为非当前")
public boolean activate(@RequestParam String algorithmModelId) {

View File

@ -3,6 +3,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.security.ProjectAccessHelper;
import com.yfd.business.css.service.DeviceService;
import com.yfd.platform.system.service.IUserService;
import org.springframework.web.bind.annotation.*;
@ -24,6 +25,8 @@ public class DeviceController {
private DeviceService deviceService;
@Resource
private IUserService userService;
@Resource
private ProjectAccessHelper projectAccessHelper;
/**
* 1. 新增设备
@ -35,6 +38,9 @@ public class DeviceController {
@Log(value = "新增设备", module = "设备管理")
@PostMapping
public boolean create(@RequestBody Device device) {
if (device.getProjectId() != null && !device.getProjectId().isBlank() && !"-1".equals(device.getProjectId())) {
projectAccessHelper.assertCanWriteProject(device.getProjectId());
}
device.setModifier(currentUsername());
return deviceService.createDevice(device);
}
@ -42,6 +48,9 @@ public class DeviceController {
@Log(value = "保存或更新设备", module = "设备管理")
@PostMapping("/saveOrUpdate")
public boolean saveOrUpdate(@RequestBody Device device) {
if (device.getProjectId() != null && !device.getProjectId().isBlank() && !"-1".equals(device.getProjectId())) {
projectAccessHelper.assertCanWriteProject(device.getProjectId());
}
device.setModifier(currentUsername());
return deviceService.saveOrUpdateByBusiness(device);
}
@ -56,6 +65,12 @@ public class DeviceController {
@Log(value = "编辑设备", module = "设备管理")
@PutMapping
public boolean update(@RequestBody Device device) {
if (device.getDeviceId() != null && !device.getDeviceId().isBlank()) {
Device db = deviceService.getById(device.getDeviceId());
if (db != null && db.getProjectId() != null && !db.getProjectId().isBlank() && !"-1".equals(db.getProjectId())) {
projectAccessHelper.assertCanWriteProject(db.getProjectId());
}
}
device.setModifier(currentUsername());
device.setUpdatedAt(LocalDateTime.now());
return deviceService.updateById(device);
@ -71,6 +86,10 @@ public class DeviceController {
@Log(value = "删除设备", module = "设备管理")
@DeleteMapping("/{id}")
public boolean delete(@PathVariable String id) {
Device db = deviceService.getById(id);
if (db != null && db.getProjectId() != null && !db.getProjectId().isBlank() && !"-1".equals(db.getProjectId())) {
projectAccessHelper.assertCanWriteProject(db.getProjectId());
}
return deviceService.removeById(id);
}
@ -84,6 +103,14 @@ public class DeviceController {
@Log(value = "批量删除设备", module = "设备管理")
@DeleteMapping
public boolean deleteBatch(@RequestBody List<String> ids) {
if (ids != null && !ids.isEmpty()) {
List<Device> list = deviceService.list(new QueryWrapper<Device>().in("device_id", ids));
for (Device d : list) {
if (d.getProjectId() != null && !d.getProjectId().isBlank() && !"-1".equals(d.getProjectId())) {
projectAccessHelper.assertCanWriteProject(d.getProjectId());
}
}
}
return deviceService.removeByIds(ids);
}
@ -113,6 +140,9 @@ public class DeviceController {
*/
@GetMapping("/by-project")
public List<Device> listByProject(@RequestParam String projectId) {
if (projectId != null && !projectId.isBlank() && !"-1".equals(projectId)) {
projectAccessHelper.assertCanReadProject(projectId);
}
return deviceService.list(
new QueryWrapper<Device>()
.eq("project_id", projectId)

View File

@ -2,7 +2,10 @@ package com.yfd.business.css.controller;
import com.yfd.business.css.domain.Device;
import com.yfd.business.css.domain.Event;
import com.yfd.business.css.domain.Scenario;
import com.yfd.business.css.security.ProjectAccessHelper;
import com.yfd.business.css.service.EventService;
import com.yfd.business.css.service.ScenarioService;
import com.yfd.platform.system.service.IUserService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@ -40,6 +43,10 @@ public class EventController {
private ObjectMapper objectMapper;
@Resource
private IUserService userService;
@Resource
private ScenarioService scenarioService;
@Resource
private ProjectAccessHelper projectAccessHelper;
/**
@ -48,6 +55,7 @@ public class EventController {
@Log(value = "新增始发事件", module = "事件管理")
@PostMapping
public ResponseEntity<Map<String, Object>> addEvent(@RequestBody Event event) {
assertCanWriteByScenarioId(event.getScenarioId());
event.setModifier(currentUsername());
eventService.save(event);
Event savedEvent = event;
@ -69,6 +77,14 @@ public class EventController {
@Log(value = "修改始发事件", module = "事件管理")
@PutMapping
public boolean update(@RequestBody Event event) {
if (event.getScenarioId() != null && !event.getScenarioId().isBlank()) {
assertCanWriteByScenarioId(event.getScenarioId());
} else if (event.getEventId() != null && !event.getEventId().isBlank()) {
Event db = eventService.getById(event.getEventId());
if (db != null) {
assertCanWriteByScenarioId(db.getScenarioId());
}
}
event.setModifier(currentUsername());
event.setUpdatedAt(LocalDateTime.now());
return eventService.updateById(event);
@ -89,6 +105,14 @@ public class EventController {
List<Event> savedEvents = new ArrayList<>();
for (Event event : events) {
if (event.getScenarioId() != null && !event.getScenarioId().isBlank()) {
assertCanWriteByScenarioId(event.getScenarioId());
} else if (event.getEventId() != null && !event.getEventId().isBlank()) {
Event db = eventService.getById(event.getEventId());
if (db != null) {
assertCanWriteByScenarioId(db.getScenarioId());
}
}
if (event.getEventId() != null && eventService.getById(event.getEventId()) != null) {
// 更新逻辑
event.setModifier(currentUser);
@ -122,6 +146,10 @@ public class EventController {
@PathVariable String eventId,
@RequestBody Map<String, Object> requestBody
) {
Event db = eventService.getById(eventId);
if (db != null) {
assertCanWriteByScenarioId(db.getScenarioId());
}
Object attrChanges = requestBody.get("attr_changes");
if (attrChanges == null) {
return ResponseEntity.badRequest().body(Map.of(
@ -159,6 +187,7 @@ public class EventController {
*/
@GetMapping("/by-scenario")
public List<Event> listByScenario(@RequestParam String scenarioId) {
assertCanReadByScenarioId(scenarioId);
return eventService.list(
new QueryWrapper<Event>()
.select("event_id","scenario_id","device_id","material_id","attr_changes","trigger_time","created_at","modifier")
@ -177,6 +206,10 @@ public class EventController {
@Log(value = "删除始发事件", module = "事件管理")
@DeleteMapping("/{eventId}")
public ResponseEntity<Map<String, Object>> deleteEvent(@PathVariable String eventId) {
Event db = eventService.getById(eventId);
if (db != null) {
assertCanWriteByScenarioId(db.getScenarioId());
}
boolean ok = eventService.removeById(eventId);
if (ok) {
return ResponseEntity.ok(Map.of(
@ -201,6 +234,9 @@ public class EventController {
@GetMapping("/{eventId}/attr-parse")
public ResponseEntity<Map<String, Object>> parseAttrChanges(@PathVariable String eventId) {
Event ev = eventService.getById(eventId);
if (ev != null) {
assertCanReadByScenarioId(ev.getScenarioId());
}
List<String> issues = new ArrayList<>();
EventAttrParseResult result = new EventAttrParseResult();
result.setEventId(eventId);
@ -384,6 +420,22 @@ public class EventController {
return 0.0;
}
private void assertCanReadByScenarioId(String scenarioId) {
if (scenarioId == null || scenarioId.isBlank()) return;
Scenario sc = scenarioService.getById(scenarioId);
if (sc == null) return;
if (sc.getProjectId() == null || sc.getProjectId().isBlank()) return;
projectAccessHelper.assertCanReadProject(sc.getProjectId());
}
private void assertCanWriteByScenarioId(String scenarioId) {
if (scenarioId == null || scenarioId.isBlank()) return;
Scenario sc = scenarioService.getById(scenarioId);
if (sc == null) return;
if (sc.getProjectId() == null || sc.getProjectId().isBlank()) return;
projectAccessHelper.assertCanWriteProject(sc.getProjectId());
}
private String currentUsername() {
try {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();

View File

@ -4,6 +4,7 @@ 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.Material;
import com.yfd.business.css.security.ProjectAccessHelper;
import com.yfd.business.css.service.MaterialService;
import com.yfd.platform.system.service.IUserService;
import org.springframework.web.bind.annotation.*;
@ -25,6 +26,8 @@ public class MaterialController {
private MaterialService materialService;
@Resource
private IUserService userService;
@Resource
private ProjectAccessHelper projectAccessHelper;
/**
* 1. 新增物料
@ -36,6 +39,9 @@ public class MaterialController {
@Log(value = "新增物料", module = "物料管理")
@PostMapping
public boolean create(@RequestBody Material material) {
if (material.getProjectId() != null && !material.getProjectId().isBlank() && !"-1".equals(material.getProjectId())) {
projectAccessHelper.assertCanWriteProject(material.getProjectId());
}
material.setModifier(currentUsername());
return materialService.saveMaterial(material);
}
@ -43,6 +49,9 @@ public class MaterialController {
@Log(value = "保存或更新物料", module = "物料管理")
@PostMapping("/saveOrUpdate")
public boolean saveOrUpdate(@RequestBody Material material) {
if (material.getProjectId() != null && !material.getProjectId().isBlank() && !"-1".equals(material.getProjectId())) {
projectAccessHelper.assertCanWriteProject(material.getProjectId());
}
material.setModifier(currentUsername());
return materialService.saveOrUpdateByBusiness(material);
}
@ -57,6 +66,12 @@ public class MaterialController {
@Log(value = "编辑物料", module = "物料管理")
@PutMapping
public boolean update(@RequestBody Material material) {
if (material.getMaterialId() != null && !material.getMaterialId().isBlank()) {
Material db = materialService.getById(material.getMaterialId());
if (db != null && db.getProjectId() != null && !db.getProjectId().isBlank() && !"-1".equals(db.getProjectId())) {
projectAccessHelper.assertCanWriteProject(db.getProjectId());
}
}
material.setModifier(currentUsername());
material.setUpdatedAt(LocalDateTime.now());
return materialService.updateById(material);
@ -72,6 +87,10 @@ public class MaterialController {
@Log(value = "删除物料", module = "物料管理")
@DeleteMapping("/{id}")
public boolean delete(@PathVariable String id) {
Material db = materialService.getById(id);
if (db != null && db.getProjectId() != null && !db.getProjectId().isBlank() && !"-1".equals(db.getProjectId())) {
projectAccessHelper.assertCanWriteProject(db.getProjectId());
}
return materialService.removeById(id);
}
@ -85,6 +104,14 @@ public class MaterialController {
@Log(value = "批量删除物料", module = "物料管理")
@DeleteMapping
public boolean deleteBatch(@RequestBody List<String> ids) {
if (ids != null && !ids.isEmpty()) {
List<Material> list = materialService.list(new QueryWrapper<Material>().in("material_id", ids));
for (Material m : list) {
if (m.getProjectId() != null && !m.getProjectId().isBlank() && !"-1".equals(m.getProjectId())) {
projectAccessHelper.assertCanWriteProject(m.getProjectId());
}
}
}
return materialService.removeByIds(ids);
}
@ -130,6 +157,9 @@ public class MaterialController {
public Page<Material> pageByProject(@RequestParam String projectId,
@RequestParam(defaultValue = "1") long pageNum,
@RequestParam(defaultValue = "20") long pageSize) {
if (projectId != null && !projectId.isBlank() && !"-1".equals(projectId)) {
projectAccessHelper.assertCanReadProject(projectId);
}
QueryWrapper<Material> qw = new QueryWrapper<Material>()
.eq("project_id", projectId)
.orderByDesc("created_at");

View File

@ -9,6 +9,7 @@ import com.yfd.business.css.service.ModelTrainService;
import com.yfd.business.css.service.TrainWebSocketService;
import com.yfd.platform.config.ResponseResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import com.yfd.platform.annotation.Log;
import org.springframework.web.multipart.MultipartFile;
@ -57,13 +58,17 @@ public class ModelTrainController {
@PostMapping("/upload")
public ResponseResult upload(@RequestParam("file") MultipartFile file) {
String path = modelTrainService.uploadDataset(file);
return ResponseResult.successData(path);
return ResponseResult.successData(Map.of(
"path", path,
"columns", modelTrainService.parseDatasetColumns(path)
));
}
/**
* 提交训练任务 (支持文件上传和 JSON 参数)
*/
@Log(value = "提交训练任务", module = "模型训练")
// @PreAuthorize("hasAuthority('modelTrain:submit')")
@PostMapping("/submit")
public ResponseResult submit(@RequestPart("task") String taskJson,
@RequestPart(value = "file", required = false) MultipartFile file) {
@ -137,6 +142,7 @@ public class ModelTrainController {
* 发布模型
*/
@Log(value = "发布训练模型", module = "模型训练")
// @PreAuthorize("hasAuthority('modelTrain:publish')")
@PostMapping("/publish")
public ResponseResult publish(@RequestBody Map<String, String> body) {
String taskId = body.get("taskId");
@ -149,6 +155,8 @@ public class ModelTrainController {
* 删除训练任务
*/
@Log(value = "删除训练任务", module = "模型训练")
@PreAuthorize("hasAuthority('modelTrain:delete')")
//删除训练任务
@DeleteMapping("/{taskId}")
public ResponseResult delete(@PathVariable String taskId) {
boolean success = modelTrainService.removeById(taskId);

View File

@ -3,6 +3,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.security.ProjectAccessHelper;
import com.yfd.business.css.service.ProjectService;
import com.yfd.platform.system.service.IUserService;
import io.swagger.v3.oas.annotations.Operation;
@ -16,6 +17,7 @@ import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.Authentication;
import com.yfd.platform.annotation.Log;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.web.multipart.MultipartFile;
import jakarta.annotation.Resource;
import java.util.List;
@ -33,6 +35,8 @@ public class ProjectController {
private com.fasterxml.jackson.databind.ObjectMapper objectMapper;
@Resource
private IUserService userService;
@Resource
private ProjectAccessHelper projectAccessHelper;
/**
* 1. 新增项目
@ -45,6 +49,15 @@ public class ProjectController {
@PostMapping
@Operation(summary = "新增项目", description = "请求体传入项目对象,返回是否新增成功")
public boolean create(@RequestBody Project project) {
if (project.getVisibility() == null || project.getVisibility().isBlank()) {
project.setVisibility("PRIVATE");
} else {
project.setVisibility(projectAccessHelper.normalizeVisibility(project.getVisibility()));
}
if (project.getCreator() == null || project.getCreator().isBlank()) {
var u = userService.getUserInfo();
if (u != null) project.setCreator(u.getId());
}
project.setModifier(currentUsername());
return projectService.save(project);
}
@ -60,11 +73,32 @@ public class ProjectController {
@PutMapping
@Operation(summary = "修改项目", description = "请求体传入项目对象(需包含主键),返回是否修改成功")
public boolean update(@RequestBody Project project) {
if (project.getProjectId() != null) {
projectAccessHelper.assertCanWriteProject(project.getProjectId());
}
project.setModifier(currentUsername());
project.setUpdatedAt(LocalDateTime.now());
return projectService.updateById(project);
}
@Log(value = "修改项目可见性", module = "项目管理")
@PutMapping("/{id}/visibility")
@Operation(summary = "修改项目可见性", description = "仅项目所有者或管理员可修改PRIVATE/READONLY/PUBLIC")
public boolean updateVisibility(@PathVariable @Parameter(description = "项目ID", required = true) String id,
@RequestBody Map<String, String> body) {
projectAccessHelper.assertCanManageVisibility(id);
String visibility = body == null ? null : body.get("visibility");
if (visibility == null || visibility.isBlank()) {
return false;
}
Project p = new Project();
p.setProjectId(id);
p.setVisibility(projectAccessHelper.normalizeVisibility(visibility));
p.setModifier(currentUsername());
p.setUpdatedAt(LocalDateTime.now());
return projectService.updateById(p);
}
private String currentUsername() {
try {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
@ -88,6 +122,7 @@ public class ProjectController {
@DeleteMapping("/{id}")
@Operation(summary = "删除项目(单条)", description = "根据项目ID删除项目")
public boolean delete(@PathVariable @Parameter(description = "项目ID", required = true) String id) {
projectAccessHelper.assertCanWriteProject(id);
return projectService.removeById(id);
}
@ -102,6 +137,11 @@ public class ProjectController {
@DeleteMapping
@Operation(summary = "删除项目(批量)", description = "请求体传入项目ID列表批量删除项目")
public boolean deleteBatch(@RequestBody List<String> ids) {
if (ids != null) {
for (String id : ids) {
projectAccessHelper.assertCanWriteProject(id);
}
}
return projectService.removeByIds(ids);
}
@ -142,6 +182,7 @@ public class ProjectController {
@GetMapping("/{id}/exportProject")
@Operation(summary = "导出项目工程多Sheet", description = "根据项目ID导出工程数据返回 Excel 附件 project_{id}.xlsx")
public ResponseEntity<byte[]> exportProjectExcel(@PathVariable @Parameter(description = "项目ID", required = true) String id) {
projectAccessHelper.assertCanReadProject(id);
byte[] bytes = projectService.exportProjectEngineeringExcel(id);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=project_" + id + ".xlsx")
@ -149,6 +190,15 @@ public class ProjectController {
.body(bytes);
}
@Log(value = "导入项目工程", module = "项目管理")
@PostMapping("/importProject")
@Operation(summary = "导入项目工程多Sheet", description = "上传 Excel 工程文件若项目ID存在且 overwrite=false 则返回提示overwrite=true 将先删除再导入")
public ResponseEntity<Map<String, Object>> importProject(@RequestParam("file") MultipartFile file,
@RequestParam(defaultValue = "false") boolean overwrite) {
var res = projectService.importProjectEngineeringExcel(file, overwrite);
return ResponseEntity.ok(res);
}
/**
* 6. 根据项目名称搜索可为空并分页返回
* 输入参数查询参数 name项目名称关键词可为空pageNum页码默认1pageSize每页条数默认10
@ -167,6 +217,14 @@ public class ProjectController {
if (name != null && !name.isEmpty()) {
qw.like("name", name);
}
if (!projectAccessHelper.isAdmin()) {
String uid = projectAccessHelper.currentUserId();
qw.and(w -> w.isNull("visibility")
.or().eq("visibility", "")
.or().eq("visibility", "PUBLIC")
.or().eq("visibility", "READONLY")
.or().eq("creator", uid));
}
Page<Project> page = new Page<>(pageNum, pageSize, true);
return projectService.page(page, qw);
}
@ -181,7 +239,13 @@ public class ProjectController {
@GetMapping("/{id}")
@Operation(summary = "根据项目ID获取项目信息", description = "项目不存在时返回空对象")
public Project getById(@PathVariable @Parameter(description = "项目ID", required = true) String id) {
return projectService.getById(id);
Project p = projectService.getById(id);
if (p != null) {
if (!projectAccessHelper.canRead(p)) {
return null;
}
}
return p;
}
/**
@ -197,6 +261,7 @@ public class ProjectController {
@Operation(summary = "更新项目拓扑结构", description = "请求体需包含合法的 topology JSON对象或字符串")
public ResponseEntity<Map<String, Object>> updateTopology(@PathVariable @Parameter(description = "项目ID", required = true) String id,
@RequestBody Map<String, Object> requestBody) {
projectAccessHelper.assertCanWriteProject(id);
Object topology = requestBody.get("topology");
if (topology == null) {
return ResponseEntity.badRequest().body(Map.of(
@ -240,6 +305,7 @@ public class ProjectController {
*/
@Operation(summary = "解析项目拓扑结构", description = "返回属性节点、影响边与线性计算计划")
public ResponseEntity<Map<String, Object>> parseTopology(@PathVariable @Parameter(description = "项目ID", required = true) String id) {
projectAccessHelper.assertCanReadProject(id);
var result = projectService.parseTopology(id);
return ResponseEntity.ok(Map.of(
"code", 0,
@ -256,6 +322,7 @@ public class ProjectController {
*/
@Operation(summary = "解析拓扑设备顺序", description = "根据 devices 出现顺序返回设备与物料属性视图")
public ResponseEntity<Map<String, Object>> parseDeviceOrder(@PathVariable @Parameter(description = "项目ID", required = true) String id) {
projectAccessHelper.assertCanReadProject(id);
Object list = projectService.parseDeviceOrderWithMaterials(id);
return ResponseEntity.ok(Map.of(
"code", 0,
@ -272,6 +339,7 @@ public class ProjectController {
*/
@Operation(summary = "解析画布视图数据", description = "返回设备/管线/边界与显示配置")
public ResponseEntity<Map<String, Object>> parseCanvas(@PathVariable @Parameter(description = "项目ID", required = true) String id) {
projectAccessHelper.assertCanReadProject(id);
var view = projectService.parseCanvasView(id);
return ResponseEntity.ok(Map.of(
"code", 0,
@ -285,6 +353,7 @@ public class ProjectController {
public ResponseEntity<Map<String, Object>> initSimulation(@RequestParam @Parameter(description = "项目ID", required = true) String projectId,
@RequestParam @Parameter(description = "情景ID", required = true) String scenarioId,
@RequestBody(required = false) Map<String, Object> params) {
projectAccessHelper.assertCanWriteProject(projectId);
var res = projectService.initSimulation(projectId, scenarioId, params == null ? Map.of() : params);
return ResponseEntity.ok(Map.of(
"code", 0,
@ -299,6 +368,7 @@ public class ProjectController {
public ResponseEntity<Map<String, Object>> runSimulation(@RequestParam @Parameter(description = "项目ID", required = true) String projectId,
@RequestParam @Parameter(description = "情景ID", required = true) String scenarioId,
@RequestBody(required = false) Map<String, Object> params) {
projectAccessHelper.assertCanWriteProject(projectId);
var res = projectService.runSimulation(projectId, scenarioId, params == null ? Map.of() : params);
return ResponseEntity.ok(Map.of(
"code", 0,

View File

@ -3,6 +3,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.security.ProjectAccessHelper;
import com.yfd.business.css.service.ScenarioService;
import com.yfd.platform.system.service.IUserService;
import org.springframework.web.bind.annotation.*;
@ -23,6 +24,8 @@ public class ScenarioController {
private ScenarioService scenarioService;
@Resource
private IUserService userService;
@Resource
private ProjectAccessHelper projectAccessHelper;
/**
* 1. 新增情景
@ -34,6 +37,9 @@ public class ScenarioController {
@Log(value = "新增情景", module = "情景管理")
@PostMapping
public boolean create(@RequestBody Scenario scenario) {
if (scenario.getProjectId() != null && !scenario.getProjectId().isBlank()) {
projectAccessHelper.assertCanWriteProject(scenario.getProjectId());
}
scenario.setModifier(currentUsername());
scenario.setCreatedAt(LocalDateTime.now());
scenario.setUpdatedAt(LocalDateTime.now());
@ -51,6 +57,9 @@ public class ScenarioController {
@Log(value = "新增情景并返回", module = "情景管理")
@PostMapping("/createAndReturn")
public java.util.Map<String, Object> createAndReturn(@RequestBody Scenario scenario) {
if (scenario.getProjectId() != null && !scenario.getProjectId().isBlank()) {
projectAccessHelper.assertCanWriteProject(scenario.getProjectId());
}
scenario.setModifier(currentUsername());
scenario.setCreatedAt(LocalDateTime.now());
scenario.setUpdatedAt(LocalDateTime.now());
@ -72,6 +81,12 @@ public class ScenarioController {
@Log(value = "修改情景", module = "情景管理")
@PutMapping
public boolean update(@RequestBody Scenario scenario) {
if (scenario.getScenarioId() != null && !scenario.getScenarioId().isBlank()) {
Scenario db = scenarioService.getById(scenario.getScenarioId());
if (db != null && db.getProjectId() != null && !db.getProjectId().isBlank()) {
projectAccessHelper.assertCanWriteProject(db.getProjectId());
}
}
scenario.setModifier(currentUsername());
scenario.setUpdatedAt(LocalDateTime.now());
return scenarioService.updateById(scenario);
@ -87,6 +102,10 @@ public class ScenarioController {
@Log(value = "删除情景", module = "情景管理")
@DeleteMapping("/{id}")
public boolean delete(@PathVariable String id) {
Scenario db = scenarioService.getById(id);
if (db != null && db.getProjectId() != null && !db.getProjectId().isBlank()) {
projectAccessHelper.assertCanWriteProject(db.getProjectId());
}
return scenarioService.removeById(id);
}
@ -100,6 +119,14 @@ public class ScenarioController {
@Log(value = "批量删除情景", module = "情景管理")
@DeleteMapping
public boolean deleteBatch(@RequestBody List<String> ids) {
if (ids != null && !ids.isEmpty()) {
List<Scenario> list = scenarioService.list(new QueryWrapper<Scenario>().in("scenario_id", ids));
for (Scenario sc : list) {
if (sc.getProjectId() != null && !sc.getProjectId().isBlank()) {
projectAccessHelper.assertCanWriteProject(sc.getProjectId());
}
}
}
return scenarioService.removeByIds(ids);
}
@ -114,7 +141,11 @@ public class ScenarioController {
*/
@GetMapping("/{id}")
public Scenario getById(@PathVariable String id) {
return scenarioService.getById(id);
Scenario sc = scenarioService.getById(id);
if (sc != null && sc.getProjectId() != null && !sc.getProjectId().isBlank()) {
projectAccessHelper.assertCanReadProject(sc.getProjectId());
}
return sc;
}
/**
@ -132,6 +163,9 @@ public class ScenarioController {
@RequestParam(required = false) String name,
@RequestParam(defaultValue = "1") long pageNum,
@RequestParam(defaultValue = "20") long pageSize) {
if (projectId != null && !projectId.isBlank()) {
projectAccessHelper.assertCanReadProject(projectId);
}
QueryWrapper<Scenario> qw = new QueryWrapper<Scenario>().eq("project_id", projectId).orderByDesc("created_at");
if (name != null && !name.isEmpty()) {
qw.like("name", name);

View File

@ -2,8 +2,11 @@ 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.domain.ScenarioResult;
import com.yfd.business.css.security.ProjectAccessHelper;
import com.yfd.business.css.service.ScenarioResultService;
import com.yfd.business.css.service.ScenarioService;
import java.io.IOException;
@ -23,6 +26,10 @@ public class ScenarioResultController {
@Resource
private ScenarioResultService scenarioResultService;
@Resource
private ScenarioService scenarioService;
@Resource
private ProjectAccessHelper projectAccessHelper;
/**
* 根据场景ID分页查询模拟结果
@ -43,6 +50,10 @@ public class ScenarioResultController {
@RequestParam(defaultValue = "1") long pageNum,
@RequestParam(defaultValue = "10") long pageSize
) {
Scenario sc = scenarioService.getById(scenarioId);
if (sc != null && sc.getProjectId() != null && !sc.getProjectId().isBlank()) {
projectAccessHelper.assertCanReadProject(sc.getProjectId());
}
QueryWrapper<ScenarioResult> qw = new QueryWrapper<ScenarioResult>()
.eq("scenario_id", scenarioId);
if (deviceId != null && !deviceId.isEmpty()) {
@ -79,6 +90,10 @@ public class ScenarioResultController {
@RequestParam(required = false) String deviceId,
@RequestParam(required = false) Integer stepFrom,
@RequestParam(required = false) Integer stepTo) throws IOException {
Scenario sc = scenarioService.getById(scenarioId);
if (sc != null && sc.getProjectId() != null && !sc.getProjectId().isBlank()) {
projectAccessHelper.assertCanReadProject(sc.getProjectId());
}
// 设置响应头信息以便浏览器识别并触发下载行为
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");

View File

@ -5,6 +5,7 @@ import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.Map;
@Data
@TableName(value = "model_train_task", autoResultMap = true)
@ -47,6 +48,9 @@ public class ModelTrainTask implements Serializable {
@TableField("error_log")
private String errorLog;
@TableField(exist = false)
private Map<String, Object> featureMapConfig;
@TableField(value = "created_at")
private LocalDateTime createdAt;

View File

@ -30,6 +30,12 @@ public class Project implements Serializable {
@TableField("topology")
private String topology;
@TableField("visibility")
private String visibility;
@TableField("creator")
private String creator;
@TableField("created_at")
private LocalDateTime createdAt;

View File

@ -0,0 +1,94 @@
package com.yfd.business.css.security;
import com.yfd.business.css.common.exception.BizException;
import com.yfd.business.css.domain.Project;
import com.yfd.business.css.service.ProjectService;
import com.yfd.platform.system.domain.SysUser;
import com.yfd.platform.system.service.IUserService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Component;
@Component
public class ProjectAccessHelper {
@Resource
private ProjectService projectService;
@Resource
private IUserService userService;
public boolean isAdmin() {
SysUser u = userService.getUserInfo();
return u != null && u.getUsertype() != null && u.getUsertype() == 0;
}
public String currentUserId() {
SysUser u = userService.getUserInfo();
return u == null ? null : u.getId();
}
public void assertCanReadProject(String projectId) {
Project p = projectService.getById(projectId);
if (p == null) {
throw new BizException("项目不存在");
}
if (!canRead(p)) {
throw new BizException("无权限访问该项目");
}
}
public void assertCanWriteProject(String projectId) {
Project p = projectService.getById(projectId);
if (p == null) {
throw new BizException("项目不存在");
}
if (!canWrite(p)) {
throw new BizException("无权限修改该项目");
}
}
public void assertCanManageVisibility(String projectId) {
Project p = projectService.getById(projectId);
if (p == null) {
throw new BizException("项目不存在");
}
if (!canManageVisibility(p)) {
throw new BizException("无权限修改项目可见性");
}
}
public boolean canRead(Project p) {
if (p == null) return false;
if (isAdmin()) return true;
String visibility = normalizeVisibility(p.getVisibility());
if ("PUBLIC".equals(visibility) || "READONLY".equals(visibility)) return true;
if ("PRIVATE".equals(visibility)) {
String uid = currentUserId();
return uid != null && uid.equals(p.getCreator());
}
return true;
}
public boolean canWrite(Project p) {
if (p == null) return false;
if (isAdmin()) return true;
String uid = currentUserId();
if (uid != null && uid.equals(p.getCreator())) return true;
String visibility = normalizeVisibility(p.getVisibility());
return "PUBLIC".equals(visibility);
}
public boolean canManageVisibility(Project p) {
if (p == null) return false;
if (isAdmin()) return true;
String uid = currentUserId();
return uid != null && uid.equals(p.getCreator());
}
public String normalizeVisibility(String visibility) {
if (visibility == null || visibility.isBlank()) return "PUBLIC";
String v = visibility.trim().toUpperCase();
if ("PRIVATE".equals(v) || "READONLY".equals(v) || "PUBLIC".equals(v)) return v;
return "PUBLIC";
}
}

View File

@ -4,6 +4,8 @@ import com.baomidou.mybatisplus.extension.service.IService;
import com.yfd.business.css.domain.ModelTrainTask;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
public interface ModelTrainService extends IService<ModelTrainTask> {
/**
* 上传数据集
@ -12,6 +14,8 @@ public interface ModelTrainService extends IService<ModelTrainTask> {
*/
String uploadDataset(MultipartFile file);
List<String> parseDatasetColumns(String datasetPath);
/**
* 提交训练任务
* @param task 任务信息

View File

@ -18,6 +18,8 @@ public interface ProjectService extends IService<Project> {
*/
byte[] exportProjectEngineeringExcel(String projectId);
java.util.Map<String, Object> importProjectEngineeringExcel(org.springframework.web.multipart.MultipartFile file, boolean overwrite);
/**
* 解析指定项目的拓扑结构
* @param projectId 项目ID

View File

@ -23,9 +23,17 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;
import org.apache.poi.ss.usermodel.DataFormatter;
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.ss.usermodel.WorkbookFactory;
import java.io.File;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@ -33,6 +41,9 @@ import java.nio.file.StandardCopyOption;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Map;
import java.util.UUID;
import java.util.regex.Pattern;
@ -108,6 +119,25 @@ public class ModelTrainServiceImpl extends ServiceImpl<ModelTrainTaskMapper, Mod
}
}
@Override
public List<String> parseDatasetColumns(String datasetPath) {
if (datasetPath == null || datasetPath.isBlank()) {
throw new BizException("数据集路径不能为空");
}
Path p = Paths.get(datasetPath);
if (!Files.exists(p)) {
throw new BizException("数据集文件不存在: " + datasetPath);
}
String lower = p.getFileName().toString().toLowerCase();
if (lower.endsWith(".xlsx") || lower.endsWith(".xls")) {
return parseExcelHeader(p.toFile());
}
if (lower.endsWith(".csv")) {
return parseCsvHeader(p);
}
throw new BizException("不支持的数据集格式: " + p.getFileName());
}
@Override
@Transactional
public String submitTask(ModelTrainTask task) {
@ -116,6 +146,20 @@ public class ModelTrainServiceImpl extends ServiceImpl<ModelTrainTaskMapper, Mod
if (task.getTaskId() == null) {
task.setTaskId(UUID.randomUUID().toString());
}
if (task.getFeatureMapConfig() != null && !task.getFeatureMapConfig().isEmpty()) {
Map<String, Object> params = new HashMap<>();
if (task.getTrainParams() != null && !task.getTrainParams().isBlank()) {
try {
params.putAll(objectMapper.readValue(task.getTrainParams(), new TypeReference<Map<String, Object>>() {}));
} catch (Exception ignored) {
}
}
params.put("feature_map", task.getFeatureMapConfig());
try {
task.setTrainParams(objectMapper.writeValueAsString(params));
} catch (JsonProcessingException ignored) {
}
}
this.save(task);
// 2. 异步调用 Python 训练
@ -145,6 +189,12 @@ public class ModelTrainServiceImpl extends ServiceImpl<ModelTrainTaskMapper, Mod
try {
Map<String, Object> params = objectMapper.readValue(task.getTrainParams(), new TypeReference<Map<String, Object>>() {});
request.put("hyperparameters", params);
if (task.getFeatureMapConfig() == null) {
Object fm = params.get("feature_map");
if (fm instanceof Map) {
request.put("feature_map", fm);
}
}
} catch (Exception e) {
log.error("解析训练参数失败,将作为原始字符串发送: {}", e.getMessage());
request.put("hyperparameters", task.getTrainParams());
@ -152,6 +202,9 @@ public class ModelTrainServiceImpl extends ServiceImpl<ModelTrainTaskMapper, Mod
} else {
request.put("hyperparameters", new HashMap<>());
}
if (task.getFeatureMapConfig() != null && !task.getFeatureMapConfig().isEmpty()) {
request.put("feature_map", task.getFeatureMapConfig());
}
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
@ -200,6 +253,69 @@ public class ModelTrainServiceImpl extends ServiceImpl<ModelTrainTaskMapper, Mod
}
}
private List<String> parseExcelHeader(File file) {
DataFormatter df = new DataFormatter();
try (Workbook wb = WorkbookFactory.create(file)) {
Sheet sheet = wb.getNumberOfSheets() > 0 ? wb.getSheetAt(0) : null;
if (sheet == null) return List.of();
Row row = sheet.getRow(sheet.getFirstRowNum());
if (row == null) return List.of();
short last = row.getLastCellNum();
List<String> cols = new ArrayList<>();
for (int i = 0; i < last; i++) {
String v = df.formatCellValue(row.getCell(i));
if (v != null) {
String t = v.trim();
if (!t.isBlank()) cols.add(t);
}
}
return cols;
} catch (Exception e) {
throw new BizException("解析Excel表头失败: " + e.getMessage());
}
}
private List<String> parseCsvHeader(Path p) {
try (BufferedReader br = new BufferedReader(new InputStreamReader(Files.newInputStream(p), StandardCharsets.UTF_8))) {
String line = br.readLine();
if (line == null) return List.of();
List<String> cols = parseCsvLine(line).stream()
.map(s -> s == null ? "" : s.trim())
.filter(s -> !s.isBlank())
.toList();
return cols;
} catch (Exception e) {
throw new BizException("解析CSV表头失败: " + e.getMessage());
}
}
private List<String> parseCsvLine(String line) {
List<String> out = new ArrayList<>();
if (line == null) return out;
StringBuilder sb = new StringBuilder();
boolean inQuotes = false;
for (int i = 0; i < line.length(); i++) {
char c = line.charAt(i);
if (c == '"') {
if (inQuotes && i + 1 < line.length() && line.charAt(i + 1) == '"') {
sb.append('"');
i++;
} else {
inQuotes = !inQuotes;
}
continue;
}
if (c == ',' && !inQuotes) {
out.add(sb.toString());
sb.setLength(0);
continue;
}
sb.append(c);
}
out.add(sb.toString());
return out;
}
@Override
public ModelTrainTask syncTaskStatus(String taskId) {
ModelTrainTask task = this.getById(taskId);

View File

@ -43,8 +43,13 @@ import java.util.Comparator;
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.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.DataFormatter;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.springframework.web.multipart.MultipartFile;
import java.time.format.DateTimeFormatter;
import java.time.LocalDateTime;
import java.math.BigDecimal;
@Slf4j
@Service
@ -1308,6 +1313,283 @@ public class ProjectServiceImpl
}
}
@Override
public Map<String, Object> importProjectEngineeringExcel(MultipartFile file, boolean overwrite) {
if (file == null || file.isEmpty()) {
return Map.of("code", 1, "msg", "文件不能为空");
}
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
DataFormatter df = new DataFormatter();
try (Workbook wb = new XSSFWorkbook(file.getInputStream())) {
Sheet projectsSheet = wb.getSheet("projects");
if (projectsSheet == null) {
return Map.of("code", 1, "msg", "缺少 projects Sheet");
}
Row header = projectsSheet.getRow(0);
Row dataRow = projectsSheet.getRow(1);
if (header == null || dataRow == null) {
return Map.of("code", 1, "msg", "projects Sheet 内容为空");
}
Map<String, Integer> h = headerIndex(header, df);
String projectId = cell(df, dataRow, h.get("project_id"));
if (projectId == null || projectId.isBlank()) {
return Map.of("code", 1, "msg", "project_id 不能为空");
}
Project exists = this.getById(projectId);
if (exists != null && !overwrite) {
return Map.of("code", 2, "msg", "项目ID已存在", "data", Map.of("projectId", projectId));
}
if (exists != null) {
deleteProjectCascade(projectId);
}
Project p = new Project();
p.setProjectId(projectId);
p.setCode(cell(df, dataRow, h.get("code")));
p.setName(cell(df, dataRow, h.get("name")));
p.setDescription(cell(df, dataRow, h.get("description")));
p.setTopology(cell(df, dataRow, h.get("topology")));
p.setModifier(cell(df, dataRow, h.get("modifier")));
String visibility = cell(df, dataRow, h.get("visibility"));
if (visibility != null && !visibility.isBlank()) {
p.setVisibility(visibility);
}
String creator = cell(df, dataRow, h.get("creator"));
if (creator != null && !creator.isBlank()) {
p.setCreator(creator);
}
String createdAt = cell(df, dataRow, h.get("created_at"));
if (createdAt != null && !createdAt.isBlank()) {
try { p.setCreatedAt(LocalDateTime.parse(createdAt, fmt)); } catch (Exception ignored) { }
}
String updatedAt = cell(df, dataRow, h.get("updated_at"));
if (updatedAt != null && !updatedAt.isBlank()) {
try { p.setUpdatedAt(LocalDateTime.parse(updatedAt, fmt)); } catch (Exception ignored) { }
}
this.save(p);
Sheet devicesSheet = wb.getSheet("devices");
if (devicesSheet != null) {
List<Device> devices = new ArrayList<>();
Map<String, Integer> dh = headerIndex(devicesSheet.getRow(0), df);
int last = devicesSheet.getLastRowNum();
for (int i = 1; i <= last; i++) {
Row r = devicesSheet.getRow(i);
if (r == null) continue;
String deviceId = cell(df, r, dh.get("device_id"));
if (deviceId == null || deviceId.isBlank()) continue;
Device d = new Device();
d.setDeviceId(deviceId);
d.setProjectId(projectId);
d.setCode(cell(df, r, dh.get("code")));
d.setType(cell(df, r, dh.get("type")));
d.setName(cell(df, r, dh.get("name")));
d.setSize(cell(df, r, dh.get("size")));
d.setVolume(parseDoubleObj(cell(df, r, dh.get("volume"))));
d.setFlowRate(parseDoubleObj(cell(df, r, dh.get("flow_rate"))));
d.setPulseVelocity(parseDoubleObj(cell(df, r, dh.get("pulse_velocity"))));
d.setModifier(cell(df, r, dh.get("modifier")));
String dCreated = cell(df, r, dh.get("created_at"));
if (dCreated != null && !dCreated.isBlank()) {
try { d.setCreatedAt(LocalDateTime.parse(dCreated, fmt)); } catch (Exception ignored) { }
}
String dUpdated = cell(df, r, dh.get("updated_at"));
if (dUpdated != null && !dUpdated.isBlank()) {
try { d.setUpdatedAt(LocalDateTime.parse(dUpdated, fmt)); } catch (Exception ignored) { }
}
devices.add(d);
}
if (!devices.isEmpty()) {
deviceService.saveBatch(devices);
}
}
Sheet materialsSheet = wb.getSheet("materials");
if (materialsSheet != null) {
List<Material> materials = new ArrayList<>();
Map<String, Integer> mh = headerIndex(materialsSheet.getRow(0), df);
int last = materialsSheet.getLastRowNum();
for (int i = 1; i <= last; i++) {
Row r = materialsSheet.getRow(i);
if (r == null) continue;
String materialId = cell(df, r, mh.get("material_id"));
if (materialId == null || materialId.isBlank()) continue;
Material m = new Material();
m.setMaterialId(materialId);
m.setProjectId(projectId);
m.setName(cell(df, r, mh.get("name")));
m.setUConcentration(parseBigDecimal(cell(df, r, mh.get("u_concentration"))));
m.setUo2Density(parseBigDecimal(cell(df, r, mh.get("uo2_density"))));
m.setUEnrichment(parseBigDecimal(cell(df, r, mh.get("u_enrichment"))));
m.setPuConcentration(parseBigDecimal(cell(df, r, mh.get("pu_concentration"))));
m.setPuo2Density(parseBigDecimal(cell(df, r, mh.get("puo2_density"))));
m.setPuIsotope(parseBigDecimal(cell(df, r, mh.get("pu_isotope"))));
m.setHno3Acidity(parseBigDecimal(cell(df, r, mh.get("hno3_acidity"))));
m.setH2c2o4Concentration(parseBigDecimal(cell(df, r, mh.get("h2c2o4_concentration"))));
m.setOrganicRatio(parseBigDecimal(cell(df, r, mh.get("organic_ratio"))));
m.setMoistureContent(parseBigDecimal(cell(df, r, mh.get("moisture_content"))));
m.setCustomAttrs(cell(df, r, mh.get("custom_attrs")));
m.setModifier(cell(df, r, mh.get("modifier")));
String mCreated = cell(df, r, mh.get("created_at"));
if (mCreated != null && !mCreated.isBlank()) {
try { m.setCreatedAt(LocalDateTime.parse(mCreated, fmt)); } catch (Exception ignored) { }
}
String mUpdated = cell(df, r, mh.get("updated_at"));
if (mUpdated != null && !mUpdated.isBlank()) {
try { m.setUpdatedAt(LocalDateTime.parse(mUpdated, fmt)); } catch (Exception ignored) { }
}
materials.add(m);
}
if (!materials.isEmpty()) {
materialService.saveBatch(materials);
}
}
Sheet scenariosSheet = wb.getSheet("scenarios");
List<String> scenarioIds = new ArrayList<>();
if (scenariosSheet != null) {
List<Scenario> scenarios = new ArrayList<>();
Map<String, Integer> sh = headerIndex(scenariosSheet.getRow(0), df);
int last = scenariosSheet.getLastRowNum();
for (int i = 1; i <= last; i++) {
Row r = scenariosSheet.getRow(i);
if (r == null) continue;
String scenarioId = cell(df, r, sh.get("scenario_id"));
if (scenarioId == null || scenarioId.isBlank()) continue;
Scenario sc = new Scenario();
sc.setScenarioId(scenarioId);
sc.setProjectId(projectId);
sc.setName(cell(df, r, sh.get("name")));
sc.setDescription(cell(df, r, sh.get("description")));
sc.setModifier(cell(df, r, sh.get("modifier")));
String sCreated = cell(df, r, sh.get("created_at"));
if (sCreated != null && !sCreated.isBlank()) {
try { sc.setCreatedAt(LocalDateTime.parse(sCreated, fmt)); } catch (Exception ignored) { }
}
String sUpdated = cell(df, r, sh.get("updated_at"));
if (sUpdated != null && !sUpdated.isBlank()) {
try { sc.setUpdatedAt(LocalDateTime.parse(sUpdated, fmt)); } catch (Exception ignored) { }
}
scenarios.add(sc);
scenarioIds.add(scenarioId);
}
if (!scenarios.isEmpty()) {
scenarioService.saveBatch(scenarios);
}
}
Sheet eventsSheet = wb.getSheet("events");
if (eventsSheet != null) {
List<Event> events = new ArrayList<>();
Map<String, Integer> eh = headerIndex(eventsSheet.getRow(0), df);
int last = eventsSheet.getLastRowNum();
for (int i = 1; i <= last; i++) {
Row r = eventsSheet.getRow(i);
if (r == null) continue;
String eventId = cell(df, r, eh.get("event_id"));
if (eventId == null || eventId.isBlank()) continue;
Event ev = new Event();
ev.setEventId(eventId);
ev.setScenarioId(cell(df, r, eh.get("scenario_id")));
ev.setDeviceId(cell(df, r, eh.get("device_id")));
ev.setMaterialId(cell(df, r, eh.get("material_id")));
ev.setTriggerTime(parseDoubleObj(cell(df, r, eh.get("trigger_time"))));
ev.setAttrChanges(cell(df, r, eh.get("attr_changes")));
ev.setModifier(cell(df, r, eh.get("modifier")));
String eCreated = cell(df, r, eh.get("created_at"));
if (eCreated != null && !eCreated.isBlank()) {
try { ev.setCreatedAt(LocalDateTime.parse(eCreated, fmt)); } catch (Exception ignored) { }
}
events.add(ev);
}
if (!events.isEmpty()) {
eventService.saveBatch(events);
}
}
Sheet resultsSheet = wb.getSheet("scenario_results");
if (resultsSheet != null) {
List<ScenarioResult> results = new ArrayList<>();
Map<String, Integer> rh = headerIndex(resultsSheet.getRow(0), df);
int last = resultsSheet.getLastRowNum();
for (int i = 1; i <= last; i++) {
Row r = resultsSheet.getRow(i);
if (r == null) continue;
String sid = cell(df, r, rh.get("scenario_id"));
if (sid == null || sid.isBlank()) continue;
ScenarioResult sr = new ScenarioResult();
sr.setScenarioId(sid);
sr.setDeviceId(cell(df, r, rh.get("device_id")));
String step = cell(df, r, rh.get("step"));
if (step != null && !step.isBlank()) {
try { sr.setStep((int) Double.parseDouble(step)); } catch (Exception ignored) { }
}
sr.setKeffValue(parseBigDecimal(cell(df, r, rh.get("keff_value"))));
sr.setAttrState(cell(df, r, rh.get("attr_state")));
results.add(sr);
}
if (!results.isEmpty()) {
scenarioResultService.saveBatch(results);
}
}
return Map.of("code", 0, "msg", "导入成功", "data", Map.of("projectId", projectId));
} catch (Exception e) {
return Map.of("code", 1, "msg", "导入失败: " + e.getMessage());
}
}
private void deleteProjectCascade(String projectId) {
List<Scenario> scenarios = scenarioService.list(new QueryWrapper<Scenario>().select("scenario_id").eq("project_id", projectId));
List<String> scenarioIds = scenarios.stream().map(Scenario::getScenarioId).filter(Objects::nonNull).toList();
if (!scenarioIds.isEmpty()) {
scenarioResultService.remove(new QueryWrapper<ScenarioResult>().in("scenario_id", scenarioIds));
eventService.remove(new QueryWrapper<Event>().in("scenario_id", scenarioIds));
}
scenarioService.remove(new QueryWrapper<Scenario>().eq("project_id", projectId));
materialService.remove(new QueryWrapper<Material>().eq("project_id", projectId));
deviceService.remove(new QueryWrapper<Device>().eq("project_id", projectId));
this.removeById(projectId);
}
private Map<String, Integer> headerIndex(Row header, DataFormatter df) {
Map<String, Integer> map = new HashMap<>();
if (header == null) return map;
short last = header.getLastCellNum();
for (int i = 0; i < last; i++) {
Cell c = header.getCell(i);
String v = c == null ? "" : df.formatCellValue(c);
if (v != null && !v.isBlank()) {
map.put(v.trim(), i);
}
}
return map;
}
private String cell(DataFormatter df, Row row, Integer idx) {
if (row == null || idx == null) return null;
Cell c = row.getCell(idx);
if (c == null) return null;
String v = df.formatCellValue(c);
return v == null ? null : v.trim();
}
private Double parseDoubleObj(String s) {
if (s == null || s.isBlank()) return null;
try { return Double.parseDouble(s); } catch (Exception e) { return null; }
}
private BigDecimal parseBigDecimal(String s) {
if (s == null || s.isBlank()) return null;
try { return new BigDecimal(s.trim()); } catch (Exception e) { return null; }
}
//运行项目模拟
@Override
public java.util.Map<String, Object> runSimulation(String projectId, String scenarioId, java.util.Map<String, Object> params) {