diff --git a/.vscode/launch.json b/.vscode/launch.json
index 014e576..fdbcbb4 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -7,13 +7,16 @@
"hostName": "localhost",
"port": "5005"
},
- java
+
{
"type": "java",
"name": "CriticalScenarioApplication",
"request": "launch",
+ "cwd": "${workspaceFolder}",
"mainClass": "com.yfd.business.css.CriticalScenarioApplication",
- "projectName": "business-css"
+ "projectName": "business-css",
+ "args": "",
+ "envFile": "${workspaceFolder}/.env"
},
{
"type": "java",
diff --git a/business-css/docs/分析模拟系统方案.md b/business-css/docs/分析模拟系统方案.md
index 9886ead..d947c9c 100644
--- a/business-css/docs/分析模拟系统方案.md
+++ b/business-css/docs/分析模拟系统方案.md
@@ -51,6 +51,23 @@
4. 增加 OpenAPI 文档与前端集成接口规范。
5. 引入结果持久化与查询报表。
+## 调试与开发指南
+
+### Maven 命令行启动 + 远程调试
+
+如果您偏好使用命令行启动,或者需要模拟特定的 Maven 环境,可采用以下方式:
+
+1. **启动应用**:
+ 在终端中运行以下命令,该命令会以调试模式启动应用并监听 `5005` 端口(`suspend=n` 表示不等待调试器连接直接启动,如需等待可改为 `y`)。
+ > 注意:PowerShell 中需要使用单引号包裹 JVM 参数,防止解析错误。
+
+ ```bash
+ mvn -DskipTests spring-boot:run -pl business-css '-Dspring-boot.run.jvmArguments=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005'
+ ```
+
+2. **附加调试器**:
+ 应用启动后,转到 IDE 的 "运行和调试" (Run and Debug) 面板,选择 **"Attach to Remote Program(5005)"** 配置(需确保 `.vscode/launch.json` 中已存在相应配置),然后点击运行。IDE 将连接到正在运行的 Maven 进程,即可开始断点调试。
+
## 运维与配置
- 端口默认 `8082`,环境覆盖通过 `application.yml` 与外部化配置。
- 数据库连接按环境注入(dev/test/prod)。
diff --git a/business-css/pom.xml b/business-css/pom.xml
index 925ee02..fdadb3a 100644
--- a/business-css/pom.xml
+++ b/business-css/pom.xml
@@ -64,7 +64,14 @@
platform
1.0
plain
-
+
+
+
+
+ com.baomidou
+ mybatis-plus-boot-starter
+ 3.5.6
+
diff --git a/business-css/src/main/java/com/yfd/business/css/config/BusinessCssAutoConfiguration.java b/business-css/src/main/java/com/yfd/business/css/config/BusinessCssAutoConfiguration.java
deleted file mode 100644
index 2f66575..0000000
--- a/business-css/src/main/java/com/yfd/business/css/config/BusinessCssAutoConfiguration.java
+++ /dev/null
@@ -1,13 +0,0 @@
-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 {
-}
diff --git a/business-css/src/main/java/com/yfd/business/css/config/OpenApiConfig.java b/business-css/src/main/java/com/yfd/business/css/config/OpenApiConfig.java
index 339e2e9..d49934f 100644
--- a/business-css/src/main/java/com/yfd/business/css/config/OpenApiConfig.java
+++ b/business-css/src/main/java/com/yfd/business/css/config/OpenApiConfig.java
@@ -14,9 +14,6 @@ import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilde
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
-import com.fasterxml.jackson.annotation.PropertyAccessor;
-import com.fasterxml.jackson.annotation.JsonAutoDetect;
-import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import java.time.format.DateTimeFormatter;
import java.io.IOException;
@@ -41,9 +38,6 @@ public class OpenApiConfig {
builder.deserializers(new LocalDateTimeDeserializer(fmt));
builder.simpleDateFormat("yyyy-MM-dd HH:mm:ss");
builder.serializationInclusion(JsonInclude.Include.ALWAYS);
- builder.visibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.NONE);
- builder.visibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.PUBLIC_ONLY);
- builder.propertyNamingStrategy(PropertyNamingStrategies.LOWER_CAMEL_CASE);
SimpleModule module = new SimpleModule();
module.setSerializerModifier(new BeanSerializerModifier() {
@Override
diff --git a/business-css/src/main/java/com/yfd/business/css/controller/AlgorithmController.java b/business-css/src/main/java/com/yfd/business/css/controller/AlgorithmController.java
index 1f2c58a..a3edf4c 100644
--- a/business-css/src/main/java/com/yfd/business/css/controller/AlgorithmController.java
+++ b/business-css/src/main/java/com/yfd/business/css/controller/AlgorithmController.java
@@ -11,11 +11,15 @@ import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.Authentication;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+
import java.util.List;
import java.time.LocalDateTime;
@RestController
@RequestMapping("/algorithms")
+@Tag(name = "算法接口", description = "算法字典的增删改查与搜索")
public class AlgorithmController {
@Autowired
@@ -25,11 +29,13 @@ public class AlgorithmController {
@GetMapping("/{id}")
+ @Operation(summary = "根据算法ID获取算法", description = "路径参数传入算法ID,返回算法对象")
public Algorithm getAlgorithmById(@PathVariable String id) {
return algorithmService.getById(id);
}
@PostMapping
+ @Operation(summary = "新增算法", description = "请求体传入算法对象,返回是否新增成功")
public boolean createAlgorithm(@RequestBody Algorithm algorithm) {
algorithm.setModifier(currentUsername());
algorithm.setCreatedAt(LocalDateTime.now());
@@ -38,6 +44,7 @@ public class AlgorithmController {
}
@PutMapping
+ @Operation(summary = "修改算法", description = "请求体传入算法对象(需包含主键),返回是否修改成功")
public boolean updateAlgorithm(@RequestBody Algorithm algorithm) {
algorithm.setModifier(currentUsername());
algorithm.setUpdatedAt(LocalDateTime.now());
@@ -45,15 +52,57 @@ public class AlgorithmController {
}
@DeleteMapping("/{id}")
+ @Operation(summary = "删除算法(单条)", description = "根据算法ID删除算法")
public boolean deleteAlgorithm(@PathVariable String id) {
return algorithmService.removeById(id);
}
@DeleteMapping
+ @Operation(summary = "删除算法(批量)", description = "请求体传入算法ID列表,批量删除算法")
public boolean deleteAlgorithms(@RequestBody List ids) {
return algorithmService.removeByIds(ids);
}
+ //算法类型激活
+ @PostMapping("/activate")
+ @Operation(summary = "激活算法", description = "激活当前算法类型")
+ public boolean activate(@RequestParam String algorithmId) {
+ Algorithm algorithm = algorithmService.getById(algorithmId);
+ if (algorithm == null) return false;
+ // 先将当前算法类型激活
+ algorithm.setStatus("1");
+ algorithm.setModifier(currentUsername());
+ algorithm.setUpdatedAt(LocalDateTime.now());
+ return algorithmService.updateById(algorithm);
+ }
+
+ //算法类型关闭
+ @PostMapping("/unactivate")
+ @Operation(summary = "关闭算法", description = "关闭当前算法类型")
+ public boolean unactivate(@RequestParam String algorithmId) {
+ Algorithm algorithm = algorithmService.getById(algorithmId);
+ if (algorithm == null) return false;
+ // 先将当前算法类型激活
+ algorithm.setStatus("0");
+ algorithm.setModifier(currentUsername());
+ algorithm.setUpdatedAt(LocalDateTime.now());
+ return algorithmService.updateById(algorithm);
+ }
+
+ //获取激活的算法类型
+ /**
+ * 获取所有激活的算法类型
+ * 输出参数:激活的算法类型列表
+ * @return 激活的算法类型列表
+ */
+ @GetMapping("/getActiveAlgorithms")
+ @Operation(summary = "获取激活的算法类型", description = "返回所有激活的算法类型")
+ public List getActiveAlgorithms() {
+ QueryWrapper qw = new QueryWrapper<>();
+ qw.eq("status", "1");
+ return algorithmService.list(qw);
+ }
+
/**
* 根据算法名称搜索并分页返回
* 输入参数:查询参数 name(算法名称关键词,可为空),pageNum(页码,默认1),pageSize(每页条数,默认10)
@@ -64,6 +113,7 @@ public class AlgorithmController {
* @return 算法分页列表
*/
@GetMapping("/search")
+ @Operation(summary = "搜索算法并分页返回", description = "按名称关键词模糊查询,返回分页结果")
public Page searchAlgorithms(@RequestParam(required = false) String name,
@RequestParam(defaultValue = "1") long pageNum,
@RequestParam(defaultValue = "20") long pageSize) {
diff --git a/business-css/src/main/java/com/yfd/business/css/controller/AlgorithmModelController.java b/business-css/src/main/java/com/yfd/business/css/controller/AlgorithmModelController.java
index 7b485d8..ad0fc9e 100644
--- a/business-css/src/main/java/com/yfd/business/css/controller/AlgorithmModelController.java
+++ b/business-css/src/main/java/com/yfd/business/css/controller/AlgorithmModelController.java
@@ -3,7 +3,9 @@ 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.AlgorithmModel;
+import com.yfd.business.css.domain.Algorithm;
import com.yfd.business.css.service.AlgorithmModelService;
+import com.yfd.business.css.service.AlgorithmService;
import com.yfd.platform.system.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@@ -11,24 +13,46 @@ import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.Authentication;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.core.type.TypeReference;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.net.URI;
+import java.time.Duration;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.UUID;
+
import java.time.LocalDateTime;
import java.util.List;
@RestController
@RequestMapping("/algorithm-models")
+@Tag(name = "算法模型接口", description = "算法模型版本的增删改查、查询当前版本与在线训练")
public class AlgorithmModelController {
@Autowired
private AlgorithmModelService algorithmModelService;
@Autowired
private IUserService userService;
+ @Autowired
+ private AlgorithmService algorithmService;
+ @Autowired
+ private ObjectMapper objectMapper;
@GetMapping("/{id}")
+ @Operation(summary = "根据模型ID获取模型版本", description = "路径参数传入模型ID,返回模型版本对象")
public AlgorithmModel getById(@PathVariable String id) {
return algorithmModelService.getById(id);
}
@PostMapping
+ @Operation(summary = "新增模型版本", description = "请求体传入模型版本对象,返回是否新增成功")
public boolean create(@RequestBody AlgorithmModel model) {
model.setModifier(currentUsername());
model.setCreatedAt(LocalDateTime.now());
@@ -37,6 +61,7 @@ public class AlgorithmModelController {
}
@PutMapping
+ @Operation(summary = "修改模型版本", description = "请求体传入模型版本对象(需包含主键),返回是否修改成功")
public boolean update(@RequestBody AlgorithmModel model) {
model.setModifier(currentUsername());
model.setUpdatedAt(LocalDateTime.now());
@@ -44,24 +69,31 @@ public class AlgorithmModelController {
}
@DeleteMapping("/{id}")
+ @Operation(summary = "删除模型版本(单条)", description = "根据模型ID删除模型版本")
public boolean delete(@PathVariable String id) {
return algorithmModelService.removeById(id);
}
@DeleteMapping
+ @Operation(summary = "删除模型版本(批量)", description = "请求体传入模型ID列表,批量删除模型版本")
public boolean deleteBatch(@RequestBody List ids) {
return algorithmModelService.removeByIds(ids);
}
//返回:该算法+设备类型的版本列表
@GetMapping("/search")
+ @Operation(summary = "查询模型版本列表", description = "按算法类型与设备类型过滤并分页返回模型版本")
public Page search(@RequestParam(required = false) String algorithmType,
@RequestParam(required = false) String deviceType,
+ @RequestParam(required = false) String versionTag,
+ @RequestParam(required = false) String isCurrent,
@RequestParam(defaultValue = "1") long pageNum,
@RequestParam(defaultValue = "20") long pageSize) {
QueryWrapper qw = new QueryWrapper<>();
if (algorithmType != null && !algorithmType.isEmpty()) qw.eq("algorithm_type", algorithmType);
if (deviceType != null && !deviceType.isEmpty()) qw.eq("device_type", deviceType);
+ if (versionTag != null && !versionTag.isEmpty()) qw.eq("version_tag", versionTag);
+ if (isCurrent != null && !isCurrent.isEmpty()) qw.eq("is_current", isCurrent);
qw.orderByDesc("updated_at");
Page page = new Page<>(pageNum, pageSize, true);
return algorithmModelService.page(page, qw);
@@ -69,6 +101,7 @@ public class AlgorithmModelController {
//返回:该算法+设备类型的当前激活版本
@GetMapping("/current")
+ @Operation(summary = "获取当前激活版本", description = "根据算法类型与设备类型,返回 is_current=1 的模型版本")
public AlgorithmModel getCurrent(@RequestParam String algorithmType,
@RequestParam String deviceType) {
QueryWrapper qw = new QueryWrapper<>();
@@ -81,6 +114,7 @@ public class AlgorithmModelController {
//版本激活
@PostMapping("/activate")
+ @Operation(summary = "激活模型版本", description = "将目标模型版本设为当前,并将同组其他版本设为非当前")
public boolean activate(@RequestParam String algorithmModelId) {
AlgorithmModel model = algorithmModelService.getById(algorithmModelId);
if (model == null) return false;
@@ -98,15 +132,122 @@ public class AlgorithmModelController {
return algorithmModelService.updateById(model);
}
- //在线训练
+ // 在线训练(Excel 数据集)
+ @PostMapping("/train/excel")
+ @Operation(summary = "在线训练(Excel)", description = "传入算法类型、设备类型与Excel路径,训练完成新增模型版本记录,可选激活")
+ public Map trainExcel(@RequestBody Map body) {
+ String algorithmType = str(body.get("algorithm_type"));
+ String deviceType = str(body.get("device_type"));
+ String datasetPath = str(body.get("dataset_path"));
+ String modelDir = str(body.getOrDefault("model_dir", ""));
+ boolean activate = bool(body.getOrDefault("activate", false));
+ String featureMapSnapshot = toJson(body.get("feature_map_snapshot"));
+ if (isBlank(algorithmType) || isBlank(deviceType) || isBlank(datasetPath)) {
+ return Map.of("code", 1, "msg", "algorithm_type/device_type/dataset_path 必填");
+ }
+ Algorithm algo = getAlgorithmByType(algorithmType);
+ if (algo == null || isBlank(algo.getTrainBaseUrl())) {
+ return Map.of("code", 1, "msg", "算法或训练URL未配置");
+ }
+ String baseUrl = algo.getTrainBaseUrl();
+ if (baseUrl != null && baseUrl.endsWith("/")) {
+ baseUrl = baseUrl.substring(0, baseUrl.length() - 1);
+ }
+ String url = baseUrl + "/v1/train/" + deviceType;
+ Map payload = new HashMap<>();
+ payload.put("dataset_path", datasetPath);
+ if (!isBlank(modelDir)) payload.put("model_dir", modelDir);
+ Map resp = httpPostJson(url, payload);
+ if (resp == null) return Map.of("code", 1, "msg", "训练接口无响应");
+ int code = 0;
+ Object codeObj = resp.get("code");
+ if (codeObj instanceof Number) {
+ code = ((Number) codeObj).intValue();
+ } else if (codeObj instanceof String) {
+ try {
+ code = Integer.parseInt((String) codeObj);
+ } catch (NumberFormatException ignored) {
+ code = 1;
+ }
+ }
+ if (code != 0) return Map.of("code", 1, "msg", "训练失败: " + str(resp.get("msg")));
+ Map data = castMap(resp.get("data"));
+ String modelPath = str(data.get("model_path"));
+ String metrics = toJson(data.get("metrics"));
+ AlgorithmModel model = new AlgorithmModel();
+ model.setAlgorithmModelId(UUID.randomUUID().toString());
+ model.setAlgorithmType(algorithmType);
+ model.setDeviceType(deviceType);
+ model.setVersionTag(genVersionTag());
+ model.setModelPath(modelPath);
+ model.setFeatureMapSnapshot(isBlank(featureMapSnapshot) ? "{}" : featureMapSnapshot);
+ model.setMetrics(metrics);
+ model.setTrainedAt(LocalDateTime.now());
+ model.setIsCurrent(activate ? 1 : 0);
+ model.setCreatedAt(LocalDateTime.now());
+ model.setUpdatedAt(LocalDateTime.now());
+ model.setModifier(currentUsername());
+ if (activate) {
+ QueryWrapper qw = new QueryWrapper<>();
+ qw.eq("algorithm_type", algorithmType).eq("device_type", deviceType);
+ AlgorithmModel upd = new AlgorithmModel();
+ upd.setIsCurrent(0);
+ algorithmModelService.update(upd, qw);
+ }
+ algorithmModelService.save(model);
+ return Map.of("code", 0, "msg", "训练成功", "data", model);
+ }
- @PostMapping("/train")
- public boolean train(@RequestParam String algorithmModelId) {
- AlgorithmModel model = algorithmModelService.getById(algorithmModelId);
- if (model == null) return false;
- // 调用训练接口
- // ...
- return true;
+ // 在线训练(样本集合)
+ @PostMapping("/train/samples")
+ @Operation(summary = "在线训练(样本集合)", description = "传入算法类型、设备类型与样本集,训练完成新增模型版本记录,可选激活")
+ public Map trainSamples(@RequestBody Map body) {
+ String algorithmType = str(body.get("algorithm_type"));
+ String deviceType = str(body.get("device_type"));
+ Object samples = body.get("samples"); // 期望为 List