diff --git a/business-css/src/main/java/com/yfd/business/css/build/package-info.java b/business-css/src/main/java/com/yfd/business/css/build/package-info.java new file mode 100644 index 0000000..98522d4 --- /dev/null +++ b/business-css/src/main/java/com/yfd/business/css/build/package-info.java @@ -0,0 +1,22 @@ +/** + * 仿真数据构建(Build)相关能力。 + *

+ * 该包用于将“项目拓扑/设备配置/事件/材料/关键参数”等多源输入组织成推演/计算可消费的结构化数据, + * 侧重于流程编排与数据组装,而非对外协议处理。 + *

+ * + *

+ * 设计原则: + *

+ * + */ +package com.yfd.business.css.build; diff --git a/business-css/src/main/java/com/yfd/business/css/common/exception/package-info.java b/business-css/src/main/java/com/yfd/business/css/common/exception/package-info.java new file mode 100644 index 0000000..34c0949 --- /dev/null +++ b/business-css/src/main/java/com/yfd/business/css/common/exception/package-info.java @@ -0,0 +1,21 @@ +/** + * 业务异常定义。 + *

+ * 用于表达业务处理过程中的可预期错误,区分于系统异常(NPE、网络故障、数据库不可用等)。 + * 业务异常应具备明确语义,便于前端提示与测试用例覆盖。 + *

+ * + *

+ * 约定: + *

+ * + */ +package com.yfd.business.css.common.exception; diff --git a/business-css/src/main/java/com/yfd/business/css/config/package-info.java b/business-css/src/main/java/com/yfd/business/css/config/package-info.java new file mode 100644 index 0000000..b40cea0 --- /dev/null +++ b/business-css/src/main/java/com/yfd/business/css/config/package-info.java @@ -0,0 +1,21 @@ +/** + * Spring Boot 配置。 + *

+ * 该包集中放置与运行时装配相关的配置类,例如: + *

+ * + *

+ * 约定: + *

+ * + */ +package com.yfd.business.css.config; 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 d0ab464..a4ac7d3 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 @@ -19,23 +19,58 @@ import io.swagger.v3.oas.annotations.tags.Tag; import java.util.List; import java.time.LocalDateTime; +/** + * 算法字典管理接口。 + *

+ * 提供算法字典的基础 CRUD、按算法类型/名称查询,以及算法启用/停用等管理能力。 + *

+ *

+ * 字段约定: + *

+ * + */ @RestController @RequestMapping("/algorithms") @Tag(name = "算法接口", description = "算法字典的增删改查与搜索") public class AlgorithmController { + /** + * 算法字典服务(MyBatis-Plus Service)。 + */ @Autowired private AlgorithmService algorithmService; + + /** + * 当前用户信息服务,用于记录 modifier 等审计字段。 + */ @Autowired private IUserService userService; + /** + * 根据主键查询算法。 + * + * @param id 算法主键 + * @return 算法对象;不存在时返回 null + */ @GetMapping("/{id}") @Operation(summary = "根据算法ID获取算法", description = "路径参数传入算法ID,返回算法对象") public Algorithm getAlgorithmById(@PathVariable String id) { return algorithmService.getById(id); } + /** + * 根据算法类型查询算法。 + *

+ * 当前实现返回满足条件的第一条记录(LIMIT 1)。 + *

+ * + * @param type 算法类型(如 GPR) + * @return 算法对象;不存在时返回 null + */ @GetMapping("/type/{type}") @Operation(summary = "根据算法类型获取算法", description = "路径参数传入算法类型(如GPR),返回算法对象") public Algorithm getAlgorithmByType(@PathVariable String type) { @@ -49,6 +84,15 @@ public class AlgorithmController { @PreAuthorize("hasAuthority('algorithm:add')") @PostMapping @Operation(summary = "新增算法", description = "请求体传入算法对象,返回是否新增成功") + /** + * 新增算法。 + *

+ * 写入审计字段:modifier、createdAt、updatedAt。 + *

+ * + * @param algorithm 算法对象(由请求体提供) + * @return 是否新增成功 + */ public boolean createAlgorithm(@RequestBody Algorithm algorithm) { algorithm.setModifier(currentUsername()); algorithm.setCreatedAt(LocalDateTime.now()); @@ -60,6 +104,15 @@ public class AlgorithmController { @PreAuthorize("hasAuthority('algorithm:update')") @PutMapping @Operation(summary = "修改算法", description = "请求体传入算法对象(需包含主键),返回是否修改成功") + /** + * 修改算法。 + *

+ * 仅更新 updatedAt 与 modifier,其他字段由请求体携带并覆盖更新。 + *

+ * + * @param algorithm 算法对象(需包含主键) + * @return 是否修改成功 + */ public boolean updateAlgorithm(@RequestBody Algorithm algorithm) { algorithm.setModifier(currentUsername()); algorithm.setUpdatedAt(LocalDateTime.now()); @@ -70,6 +123,12 @@ public class AlgorithmController { @PreAuthorize("hasAuthority('algorithm:del')") @DeleteMapping("/{id}") @Operation(summary = "删除算法(单条)", description = "根据算法ID删除算法") + /** + * 删除单条算法记录。 + * + * @param id 算法主键 + * @return 是否删除成功 + */ public boolean deleteAlgorithm(@PathVariable String id) { return algorithmService.removeById(id); } @@ -78,6 +137,12 @@ public class AlgorithmController { @PreAuthorize("hasAuthority('algorithm:del')") @DeleteMapping @Operation(summary = "删除算法(批量)", description = "请求体传入算法ID列表,批量删除算法") + /** + * 批量删除算法记录。 + * + * @param ids 算法主键列表 + * @return 是否删除成功 + */ public boolean deleteAlgorithms(@RequestBody List ids) { return algorithmService.removeByIds(ids); } @@ -86,6 +151,12 @@ public class AlgorithmController { // @PreAuthorize("hasAuthority('algorithm:activate')") @PostMapping("/activate") @Operation(summary = "激活算法", description = "激活当前算法类型") + /** + * 启用算法(status=1)。 + * + * @param algorithmId 算法主键 + * @return 是否更新成功;算法不存在时返回 false + */ public boolean activate(@RequestParam String algorithmId) { Algorithm algorithm = algorithmService.getById(algorithmId); if (algorithm == null) return false; @@ -100,6 +171,12 @@ public class AlgorithmController { // @PreAuthorize("hasAuthority('algorithm:unactivate')") @PostMapping("/unactivate") @Operation(summary = "关闭算法", description = "关闭当前算法类型") + /** + * 停用算法(status=0)。 + * + * @param algorithmId 算法主键 + * @return 是否更新成功;算法不存在时返回 false + */ public boolean unactivate(@RequestParam String algorithmId) { Algorithm algorithm = algorithmService.getById(algorithmId); if (algorithm == null) return false; @@ -110,10 +187,9 @@ public class AlgorithmController { return algorithmService.updateById(algorithm); } - //获取激活的算法类型 /** - * 获取所有激活的算法类型 - * 输出参数:激活的算法类型列表 + * 获取所有启用状态的算法列表(status=1)。 + * * @return 激活的算法类型列表 */ @GetMapping("/getActiveAlgorithms") @@ -125,12 +201,11 @@ public class AlgorithmController { } /** - * 根据算法名称搜索并分页返回 - * 输入参数:查询参数 name(算法名称关键词,可为空),pageNum(页码,默认1),pageSize(每页条数,默认10) - * 输出参数:算法分页列表 + * 根据算法名称搜索并分页返回。 + * * @param name 算法名称关键词(可为空) - * @param pageNum 页码 - * @param pageSize 每页条数 + * @param pageNum 页码(默认 1) + * @param pageSize 每页条数(默认 20) * @return 算法分页列表 */ @GetMapping("/search") @@ -147,6 +222,14 @@ public class AlgorithmController { return algorithmService.page(page, qw); } + /** + * 获取当前登录用户名。 + *

+ * 用于落库的 modifier 字段,若用户未登录或上下文解析失败,返回 "anonymous"。 + *

+ * + * @return 当前用户名或 "anonymous" + */ private String currentUsername() { try { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); 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 a973245..1177177 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,9 +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.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.*; @@ -18,35 +16,49 @@ import com.yfd.platform.annotation.Log; 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 { + /** + * 算法模型服务(MyBatis-Plus Service)。 + */ @Autowired private AlgorithmModelService algorithmModelService; + + /** + * 当前用户信息服务,用于记录 modifier 等审计字段。 + */ @Autowired private IUserService userService; - @Autowired - private AlgorithmService algorithmService; - @Autowired - private ObjectMapper objectMapper; + /** + * 根据主键查询模型版本。 + * + * @param id 模型主键 + * @return 模型版本对象;不存在时返回 null + */ @GetMapping("/{id}") @Operation(summary = "根据模型ID获取模型版本", description = "路径参数传入模型ID,返回模型版本对象") public AlgorithmModel getById(@PathVariable String id) { @@ -57,6 +69,15 @@ public class AlgorithmModelController { // @PreAuthorize("hasAuthority('algorithmModel:add')") // @PostMapping @Operation(summary = "新增模型版本", description = "请求体传入模型版本对象,返回是否新增成功") + /** + * 新增模型版本。 + *

+ * 当前方法仅负责落库与审计字段写入;接口映射若被关闭(@PostMapping 注释),则不会对外提供入口。 + *

+ * + * @param model 模型版本对象 + * @return 是否新增成功 + */ public boolean create(@RequestBody AlgorithmModel model) { model.setModifier(currentUsername()); model.setCreatedAt(LocalDateTime.now()); @@ -68,6 +89,15 @@ public class AlgorithmModelController { // @PreAuthorize("hasAuthority('algorithmModel:update')") // @PutMapping @Operation(summary = "修改模型版本", description = "请求体传入模型版本对象(需包含主键),返回是否修改成功") + /** + * 修改模型版本。 + *

+ * 当前方法仅负责落库与审计字段写入;接口映射若被关闭(@PutMapping 注释),则不会对外提供入口。 + *

+ * + * @param model 模型版本对象(需包含主键) + * @return 是否修改成功 + */ public boolean update(@RequestBody AlgorithmModel model) { model.setModifier(currentUsername()); model.setUpdatedAt(LocalDateTime.now()); @@ -78,6 +108,12 @@ public class AlgorithmModelController { @PreAuthorize("hasAuthority('algorithmModel:del')") @DeleteMapping("/{id}") @Operation(summary = "删除模型版本(单条)", description = "根据模型ID删除模型版本") + /** + * 删除单条模型版本记录。 + * + * @param id 模型主键 + * @return 是否删除成功 + */ public boolean delete(@PathVariable String id) { return algorithmModelService.removeById(id); } @@ -86,11 +122,28 @@ public class AlgorithmModelController { @PreAuthorize("hasAuthority('algorithmModel:del')") @DeleteMapping @Operation(summary = "删除模型版本(批量)", description = "请求体传入模型ID列表,批量删除模型版本") + /** + * 批量删除模型版本记录。 + * + * @param ids 模型主键列表 + * @return 是否删除成功 + */ public boolean deleteBatch(@RequestBody List ids) { return algorithmModelService.deleteBatchWithCheck(ids); } - //返回:该算法+设备类型+材料类型的版本列表 + /** + * 查询模型版本列表(分页)。 + * + * @param algorithmType 算法类型 + * @param deviceType 设备类型 + * @param materialType 材料类型(支持 Mixed/MIX 兼容) + * @param versionTag 版本号 + * @param isCurrent 是否为当前版本(1/0) + * @param pageNum 页码(默认 1) + * @param pageSize 每页条数(默认 20) + * @return 模型版本分页结果 + */ @GetMapping("/search") @Operation(summary = "查询模型版本列表", description = "按算法类型、设备类型与材料类型过滤并分页返回模型版本") public Page search(@RequestParam(required = false) String algorithmType, @@ -118,6 +171,17 @@ public class AlgorithmModelController { return algorithmModelService.page(page, qw); } + /** + * 获取模型版本下拉选项。 + *

+ * 返回包含 options(value=模型ID,label=版本号)与 currentModelId(当前激活模型ID)的结构,便于前端直接使用。 + *

+ * + * @param algorithmType 算法类型 + * @param deviceType 设备类型 + * @param materialType 材料类型(可选) + * @return 选项数据 + */ @GetMapping("/options") @Operation(summary = "获取模型版本选项列表", description = "用于界面下拉选择:按算法类型与设备类型返回模型版本列表(value=模型ID,label=版本号),并返回当前激活模型ID") public Map options(@RequestParam String algorithmType, @@ -158,7 +222,14 @@ public class AlgorithmModelController { return out; } - //返回:该算法+设备类型+材料类型的当前激活版本 + /** + * 获取当前激活版本(is_current=1)。 + * + * @param algorithmType 算法类型 + * @param deviceType 设备类型 + * @param materialType 材料类型(可选) + * @return 当前激活模型版本;不存在时返回 null + */ @GetMapping("/current") @Operation(summary = "获取当前激活版本", description = "根据算法类型、设备类型与材料类型,返回 is_current=1 的模型版本") public AlgorithmModel getCurrent(@RequestParam String algorithmType, @@ -184,6 +255,15 @@ public class AlgorithmModelController { @PreAuthorize("hasAuthority('algorithmModel:activate')") @PostMapping("/activate") @Operation(summary = "激活模型版本", description = "将目标模型版本设为当前,并将同组(算法+设备+材料)其他版本设为非当前") + /** + * 激活指定模型版本。 + *

+ * 激活时会将同组(algorithmType+deviceType+materialType)其他版本置为非当前(isCurrent=0),再将指定版本置为当前。 + *

+ * + * @param algorithmModelId 模型主键 + * @return 是否更新成功;模型不存在时返回 false + */ public boolean activate(@RequestParam String algorithmModelId) { AlgorithmModel model = algorithmModelService.getById(algorithmModelId); if (model == null) return false; @@ -212,148 +292,12 @@ public class AlgorithmModelController { return algorithmModelService.updateById(model); } - @Log(value = "在线训练(Excel)", module = "算法模型管理") - // 在线训练(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 materialType = normalizeMaterialType(str(body.getOrDefault("material_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.setMaterialType(materialType); - 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); - if (!isBlank(materialType)) { - if ("Mixed".equals(materialType)) { - qw.in("material_type", List.of("Mixed", "MIX")); - } else { - qw.eq("material_type", materialType); - } - } else { - qw.and(wrapper -> wrapper.isNull("material_type").or().eq("material_type", "")); - } - AlgorithmModel upd = new AlgorithmModel(); - upd.setIsCurrent(0); - algorithmModelService.update(upd, qw); - } - algorithmModelService.save(model); - return Map.of("code", 0, "msg", "训练成功", "data", model); - } - - @Log(value = "在线训练(样本)", module = "算法模型管理") - // 在线训练(样本集合) - // @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")); - String materialType = normalizeMaterialType(str(body.getOrDefault("material_type", ""))); - Object samples = body.get("samples"); // 期望为 List,由前端提供 - 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) || samples == null) { - return Map.of("code", 1, "msg", "algorithm_type/device_type/samples 必填"); - } - Algorithm algo = getAlgorithmByType(algorithmType); - if (algo == null || isBlank(algo.getTrainBaseUrl())) { - return Map.of("code", 1, "msg", "算法或训练URL未配置"); - } - String url = "";//trimSlash(algo.getTrainBaseUrl()) + "/v1/train/" + deviceType + "/from-samples"; - Map payload = new HashMap<>(); - payload.put("samples", samples); - 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;//intVal(resp.get("code")); - 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.setMaterialType(materialType); - 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); - if (!isBlank(materialType)) { - if ("Mixed".equals(materialType)) { - qw.in("material_type", List.of("Mixed", "MIX")); - } else { - qw.eq("material_type", materialType); - } - } else { - qw.and(wrapper -> wrapper.isNull("material_type").or().eq("material_type", "")); - } - AlgorithmModel upd = new AlgorithmModel(); - upd.setIsCurrent(0); - algorithmModelService.update(upd, qw); - } - algorithmModelService.save(model); - return Map.of("code", 0, "msg", "训练成功", "data", model); - } - + /** + * 归一化材料类型取值。 + * + * @param raw 原始材料类型 + * @return 归一化后的材料类型;空白返回 null + */ private String normalizeMaterialType(String raw) { if (raw == null) return null; String s = raw.trim(); @@ -365,6 +309,14 @@ public class AlgorithmModelController { } + /** + * 获取当前登录用户名。 + *

+ * 用于落库的 modifier 字段,若用户未登录或上下文解析失败,返回 "anonymous"。 + *

+ * + * @return 当前用户名或 "anonymous" + */ private String currentUsername() { try { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); @@ -385,55 +337,4 @@ public class AlgorithmModelController { return "anonymous"; } } - - private Algorithm getAlgorithmByType(String algorithmType) { - QueryWrapper qw = new QueryWrapper<>(); - qw.eq("algorithm_type", algorithmType); - return algorithmService.getOne(qw); - } - - private Map httpPostJson(String url, Object payload) { - try { - String json = objectMapper.writeValueAsString(payload); - HttpClient client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(5)).build(); - HttpRequest req = HttpRequest.newBuilder() - .uri(URI.create(url)) - .header("Content-Type", "application/json") - .timeout(Duration.ofSeconds(30)) - .POST(HttpRequest.BodyPublishers.ofString(json, StandardCharsets.UTF_8)) - .build(); - HttpResponse res = client.send(req, HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8)); - if (res.statusCode() >= 200 && res.statusCode() < 300) { - return objectMapper.readValue(res.body(), new TypeReference>() {}); - } - return Map.of("code", 1, "msg", "HTTP " + res.statusCode()); - } catch (Exception e) { - return Map.of("code", 1, "msg", e.getMessage()); - } - } - - private String genVersionTag() { - return "v" + LocalDateTime.now().toString().replace(":", "-"); - } - private String str(Object v) { return v == null ? "" : String.valueOf(v); } - private boolean isBlank(String s) { return s == null || s.trim().isEmpty(); } - private boolean bool(Object v) { - if (v instanceof Boolean) return (Boolean) v; - String s = str(v).toLowerCase(); - return "true".equals(s) || "1".equals(s) || "yes".equals(s); - } - private String toJson(Object v) { - try { - if (v == null) return null; - if (v instanceof String) return (String) v; - return objectMapper.writeValueAsString(v); - } catch (Exception e) { - return null; - } - } - @SuppressWarnings("unchecked") - private Map castMap(Object v) { - if (v instanceof Map) return (Map) v; - return new HashMap<>(); - } } diff --git a/business-css/src/main/java/com/yfd/business/css/controller/CriticalDataController.java b/business-css/src/main/java/com/yfd/business/css/controller/CriticalDataController.java index 726b1e7..be8aba6 100644 --- a/business-css/src/main/java/com/yfd/business/css/controller/CriticalDataController.java +++ b/business-css/src/main/java/com/yfd/business/css/controller/CriticalDataController.java @@ -24,12 +24,32 @@ import java.time.LocalDateTime; import java.util.List; import java.util.Map; +/** + * 临界数据管理接口。 + *

+ * 提供临界数据的新增、修改、删除、按设备类型查询,以及导入/导出等管理能力。 + *

+ *

+ * 字段约定: + *

+ *
    + *
  • extraFeatures:JSON 字段;前端传空字符串时需归一化为 null,避免 JSON 列写入失败。
  • + *
  • modifier:最后修改人用户名,取自当前登录上下文。
  • + *
+ */ @RestController @RequestMapping("/critical-data") public class CriticalDataController { + /** + * 临界数据服务(MyBatis-Plus Service)。 + */ @Resource private CriticalDataService criticalDataService; + + /** + * 当前用户信息服务,用于记录 modifier 等审计字段。 + */ @Resource private IUserService userService; @@ -51,6 +71,14 @@ public class CriticalDataController { return criticalDataService.save(data); } + /** + * 获取当前登录用户名。 + *

+ * 用于落库的 modifier 字段,若用户未登录或上下文解析失败,返回 "anonymous"。 + *

+ * + * @return 当前用户名或 "anonymous" + */ private String currentUsername() { try { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); @@ -185,6 +213,12 @@ public class CriticalDataController { .body(bytes); } + /** + * 导出临界数据模板(V2)。 + * + * @param deviceType 设备类型 + * @return 模板文件字节流 + */ @PreAuthorize("hasAuthority('criticalData:import')") @GetMapping("/v2/template") public ResponseEntity templateCriticalDataV2(@RequestParam String deviceType) { diff --git a/business-css/src/main/java/com/yfd/business/css/controller/DeviceMetaController.java b/business-css/src/main/java/com/yfd/business/css/controller/DeviceMetaController.java index 0ddc686..3614b99 100644 --- a/business-css/src/main/java/com/yfd/business/css/controller/DeviceMetaController.java +++ b/business-css/src/main/java/com/yfd/business/css/controller/DeviceMetaController.java @@ -12,19 +12,40 @@ import org.springframework.web.bind.annotation.RestController; import jakarta.annotation.Resource; import java.util.Map; +/** + * 设备元数据接口。 + *

+ * 提供设备尺寸 schema(size-schema)等元数据,供前端动态渲染表单、导入导出与校验使用。 + *

+ */ @RestController @RequestMapping("/devices/v2") @Tag(name = "设备元数据接口", description = "提供设备尺寸 size-schema 元数据,供前端动态渲染与校验使用") public class DeviceMetaController { + + /** + * 设备尺寸 schema 注册表。 + */ @Resource private DeviceSizeSchemaRegistry registry; + /** + * 获取指定设备类型的尺寸 schema。 + * + * @param deviceType 设备类型 + * @return 尺寸字段元数据 + */ @GetMapping("/size-schema") @Operation(summary = "获取指定设备类型的尺寸 schema", description = "返回 deviceType 对应的字段元数据(key/label/unit/required/order/min/max),用于前端动态渲染与与导入导出/推理口径对齐") public DeviceSizeSchema schema(@RequestParam String deviceType) { return registry.getSchema(deviceType); } + /** + * 获取全部设备类型的尺寸 schema。 + * + * @return deviceType -> schema 的映射 + */ @GetMapping("/size-schema/all") @Operation(summary = "获取全部设备类型的尺寸 schema", description = "返回所有 deviceType 的 schema Map,适合前端一次性缓存") public Map all() { diff --git a/business-css/src/main/java/com/yfd/business/css/controller/EventController.java b/business-css/src/main/java/com/yfd/business/css/controller/EventController.java index 4da1003..1861831 100644 --- a/business-css/src/main/java/com/yfd/business/css/controller/EventController.java +++ b/business-css/src/main/java/com/yfd/business/css/controller/EventController.java @@ -33,18 +33,50 @@ import org.apache.poi.ss.usermodel.*; import java.io.InputStream; import java.util.Iterator; +/** + * 情景事件管理接口。 + *

+ * 提供事件的新增、修改、批量保存、查询与导入等能力,并通过 Scenario -> Project 的关系校验项目读写权限。 + *

+ *

+ * 事件与权限: + *

+ *
    + *
  • 事件属于情景(scenarioId),通过情景关联项目(projectId)进行权限控制。
  • + *
  • 写操作需要项目写权限;读操作需要项目读权限。
  • + *
+ */ @RestController @RequestMapping("/events") public class EventController { + /** + * 事件服务(MyBatis-Plus Service)。 + */ @Resource private EventService eventService; + + /** + * JSON 解析与转换。 + */ @Resource private ObjectMapper objectMapper; + + /** + * 当前用户信息服务,用于记录 modifier 等审计字段。 + */ @Resource private IUserService userService; + + /** + * 情景服务,用于根据 scenarioId 获取 projectId 并执行权限校验。 + */ @Resource private ScenarioService scenarioService; + + /** + * 项目权限辅助类,用于对 projectId 的读写操作进行鉴权校验。 + */ @Resource private ProjectAccessHelper projectAccessHelper; @@ -436,6 +468,9 @@ public class EventController { } private double getCellValueAsDouble(Cell cell) { + if (cell == null) { + return 0.0; + } if (cell.getCellType() == CellType.NUMERIC) { return cell.getNumericCellValue(); } else if (cell.getCellType() == CellType.STRING) { @@ -482,6 +517,14 @@ public class EventController { projectAccessHelper.assertCanWriteProject(sc.getProjectId()); } + /** + * 获取当前登录用户名。 + *

+ * 用于落库的 modifier 字段,若用户未登录或上下文解析失败,返回 "anonymous"。 + *

+ * + * @return 当前用户名或 "anonymous" + */ private String currentUsername() { try { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); diff --git a/business-css/src/main/java/com/yfd/business/css/controller/HealthController.java b/business-css/src/main/java/com/yfd/business/css/controller/HealthController.java index e0e6e18..a1f1ee1 100644 --- a/business-css/src/main/java/com/yfd/business/css/controller/HealthController.java +++ b/business-css/src/main/java/com/yfd/business/css/controller/HealthController.java @@ -4,9 +4,20 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; +/** + * 健康检查接口。 + *

+ * 用于探活与连通性测试,不依赖业务数据与权限。 + *

+ */ @RestController public class HealthController { + /** + * Ping 接口,返回固定字符串 "ok"。 + * + * @return ok + */ @GetMapping("/ping") public ResponseEntity ping() { return ResponseEntity.ok("ok"); diff --git a/business-css/src/main/java/com/yfd/business/css/controller/MaterialController.java b/business-css/src/main/java/com/yfd/business/css/controller/MaterialController.java index d9583e5..c4f8aaa 100644 --- a/business-css/src/main/java/com/yfd/business/css/controller/MaterialController.java +++ b/business-css/src/main/java/com/yfd/business/css/controller/MaterialController.java @@ -23,14 +23,38 @@ import jakarta.annotation.Resource; import java.util.List; import java.time.LocalDateTime; +/** + * 物料管理接口(模板库与项目物料)。 + *

+ * 提供物料的基础增删改查,以及 Excel/CSV 导入导出与模板下载能力。 + *

+ *

+ * 权限与数据域: + *

+ *
    + *
  • 模板库数据:project_id = -1。
  • + *
  • 项目数据:project_id != -1 时,通过 ProjectAccessHelper 校验读写权限。
  • + *
+ */ @RestController @RequestMapping("/materials") public class MaterialController { + /** + * 物料服务(MyBatis-Plus Service)。 + */ @Resource private MaterialService materialService; + + /** + * 当前用户信息服务,用于记录 modifier 等审计字段。 + */ @Resource private IUserService userService; + + /** + * 项目权限辅助类,用于对 projectId 的读写操作进行鉴权校验。 + */ @Resource private ProjectAccessHelper projectAccessHelper; @@ -162,6 +186,11 @@ public class MaterialController { } @GetMapping("/v2/template") + /** + * 下载物料导入模板(V2)。 + * + * @return xlsx 模板字节流 + */ public ResponseEntity templateMaterialsV2() { byte[] bytes = materialService.templateMaterialsV2(); return ResponseEntity.ok() @@ -171,6 +200,15 @@ public class MaterialController { } @GetMapping("/{id}") + /** + * 根据主键查询物料。 + *

+ * projectId != -1 时会校验项目读权限。 + *

+ * + * @param id 物料主键 + * @return 物料对象;不存在时返回 null + */ public Material getById(@PathVariable String id) { Material m = materialService.getById(id); if (m != null && m.getProjectId() != null && !m.getProjectId().isBlank() && !"-1".equals(m.getProjectId())) { @@ -211,6 +249,14 @@ public class MaterialController { } @GetMapping("/by-project") + /** + * 按项目分页查询物料列表。 + * + * @param projectId 项目ID + * @param pageNum 页码(默认 1) + * @param pageSize 每页条数(默认 20) + * @return 物料分页列表 + */ public Page pageByProject(@RequestParam String projectId, @RequestParam(defaultValue = "1") long pageNum, @RequestParam(defaultValue = "20") long pageSize) { @@ -232,6 +278,14 @@ public class MaterialController { return materialService.page(page, qw); } + /** + * 获取当前登录用户名。 + *

+ * 用于落库的 modifier 字段,若用户未登录或上下文解析失败,返回 "anonymous"。 + *

+ * + * @return 当前用户名或 "anonymous" + */ private String currentUsername() { try { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); diff --git a/business-css/src/main/java/com/yfd/business/css/controller/ModelTrainController.java b/business-css/src/main/java/com/yfd/business/css/controller/ModelTrainController.java index 32b2d83..425625a 100644 --- a/business-css/src/main/java/com/yfd/business/css/controller/ModelTrainController.java +++ b/business-css/src/main/java/com/yfd/business/css/controller/ModelTrainController.java @@ -16,21 +16,48 @@ import org.springframework.web.multipart.MultipartFile; import java.util.Map; +/** + * 模型训练任务接口。 + *

+ * 提供训练任务的容量预检、任务提交、任务列表与详情查询、训练状态回调接入,以及模型发布与任务删除等能力。 + *

+ *

+ * 回调链路: + *

+ *
    + *
  • Python 侧回调 {@code /train/internal/callback},后端更新任务状态后通过 WebSocket 广播到 {@code /topic/train-status/all}。
  • + *
  • 前端列表页订阅广播主题以更新任务状态;详情页可通过轮询 {@code /train/status/{taskId}} 获取最新状态。
  • + *
+ */ @RestController @RequestMapping("/train") public class ModelTrainController { + /** + * 模型训练任务服务。 + */ @Autowired private ModelTrainService modelTrainService; + /** + * WebSocket 推送服务,用于将训练状态广播给前端。 + */ @Autowired private TrainWebSocketService trainWebSocketService; + /** + * JSON 序列化与请求参数解析。 + */ @Autowired private ObjectMapper objectMapper; @PreAuthorize("hasAuthority('modelTrain:add')") @GetMapping("/capacity/check") + /** + * 提交训练任务前的容量预检。 + * + * @return 容量检查结果(包含 canSubmit、counts、limits 等) + */ public ResponseResult capacityCheck() { return ResponseResult.successData(modelTrainService.checkCapacity()); } @@ -39,6 +66,15 @@ public class ModelTrainController { * 接收 Python 端的训练状态回调 */ @PostMapping("/internal/callback") + /** + * Python 训练状态回调入口。 + *

+ * 回调体需包含 taskId 与 status 等字段;后端更新任务状态并通过 WebSocket 推送给前端。 + *

+ * + * @param callbackData 回调数据 + * @return 标准响应 + */ public ResponseResult handleTrainCallback(@RequestBody Map callbackData) { System.out.println("====== 收到 Python 端训练回调 ======"); System.out.println("回调数据: " + callbackData); @@ -62,6 +98,12 @@ public class ModelTrainController { */ @Log(value = "上传训练数据集", module = "模型训练") @PostMapping("/upload") + /** + * 上传训练数据集并执行基础预检(解析列名、返回告警等)。 + * + * @param file 数据集文件 + * @return 上传与预检结果 + */ public ResponseResult upload(@RequestParam("file") MultipartFile file) { return ResponseResult.successData(modelTrainService.uploadAndInspectDataset(file)); } @@ -72,6 +114,16 @@ public class ModelTrainController { @Log(value = "提交训练任务", module = "模型训练") @PreAuthorize("hasAuthority('modelTrain:add')") @PostMapping("/submit") + /** + * 提交训练任务。 + *

+ * 采用 multipart/form-data 传参:task 为 JSON 字符串,file 为可选数据集文件。若上传了文件,优先以文件路径作为 datasetPath。 + *

+ * + * @param taskJson 任务 JSON(字符串) + * @param file 数据集文件(可选) + * @return 提交结果,data 为 taskId + */ public ResponseResult submit(@RequestPart("task") String taskJson, @RequestPart(value = "file", required = false) MultipartFile file) { try { @@ -105,6 +157,18 @@ public class ModelTrainController { * 查询任务列表 (支持条件查询) */ @GetMapping("/list") + /** + * 查询训练任务列表(分页,支持条件过滤)。 + * + * @param current 当前页(默认 1) + * @param size 每页条数(默认 10) + * @param algorithmType 算法类型(兼容 algorithmType/algoType 两种参数) + * @param algoType 算法类型(兼容字段) + * @param deviceType 设备类型 + * @param status 状态过滤 + * @param name 名称关键字(模糊匹配) + * @return 分页结果 + */ public ResponseResult list(@RequestParam(defaultValue = "1") Integer current, @RequestParam(defaultValue = "10") Integer size, @RequestParam(value = "algorithmType", required = false) String algorithmType, @@ -140,6 +204,12 @@ public class ModelTrainController { * 查询任务详情/状态 */ @GetMapping("/status/{taskId}") + /** + * 查询任务详情/状态。 + * + * @param taskId 任务ID + * @return 任务对象 + */ public ResponseResult status(@PathVariable String taskId) { ModelTrainTask task = modelTrainService.syncTaskStatus(taskId); return ResponseResult.successData(task); @@ -151,6 +221,15 @@ public class ModelTrainController { @Log(value = "发布训练模型", module = "模型训练") @PreAuthorize("hasAuthority('modelTrain:publish')") @PostMapping("/publish") + /** + * 发布模型。 + *

+ * 仅允许对成功完成的训练任务进行发布;versionTag 需满足版本号格式约束。 + *

+ * + * @param body 包含 taskId 与 versionTag + * @return 发布结果 + */ public ResponseResult publish(@RequestBody Map body) { String taskId = body.get("taskId"); String versionTag = body.get("versionTag"); @@ -165,6 +244,12 @@ public class ModelTrainController { @PreAuthorize("hasAuthority('modelTrain:del')") //删除训练任务 @DeleteMapping("/{taskId}") + /** + * 删除训练任务。 + * + * @param taskId 任务ID + * @return 删除结果 + */ public ResponseResult delete(@PathVariable String taskId) { boolean success = modelTrainService.removeById(taskId); return success ? ResponseResult.success() : ResponseResult.error("删除失败"); diff --git a/business-css/src/main/java/com/yfd/business/css/controller/ProjectController.java b/business-css/src/main/java/com/yfd/business/css/controller/ProjectController.java index 383ec6d..543abf8 100644 --- a/business-css/src/main/java/com/yfd/business/css/controller/ProjectController.java +++ b/business-css/src/main/java/com/yfd/business/css/controller/ProjectController.java @@ -24,17 +24,45 @@ import java.util.List; import java.util.Map; import java.time.LocalDateTime; +/** + * 项目管理接口。 + *

+ * 提供项目的增删改查、权限可见性管理、工程数据导入导出、拓扑解析与仿真初始化相关能力。 + *

+ *

+ * 权限与可见性: + *

+ *
    + *
  • visibility:PRIVATE / READONLY / PUBLIC(由 ProjectAccessHelper 统一归一化与鉴权)。
  • + *
  • 非管理员用户查询项目列表时会按可见性与所有者过滤。
  • + *
+ */ @RestController @RequestMapping("/projects") @Tag(name = "项目接口", description = "项目增删改查、拓扑解析与模拟初始化") public class ProjectController { + /** + * 项目服务(MyBatis-Plus Service)。 + */ @Resource private ProjectService projectService; + + /** + * JSON 序列化与拓扑字段解析。 + */ @Resource private com.fasterxml.jackson.databind.ObjectMapper objectMapper; + + /** + * 当前用户信息服务,用于获取用户名等信息。 + */ @Resource private IUserService userService; + + /** + * 项目权限辅助类,用于对项目读写、可见性修改等操作进行鉴权校验。 + */ @Resource private ProjectAccessHelper projectAccessHelper; @@ -98,6 +126,14 @@ public class ProjectController { return projectService.updateById(p); } + /** + * 获取当前登录账号。 + *

+ * 用于 creator/modifier 等审计字段;未登录或取不到用户信息时返回 "anonymous"。 + *

+ * + * @return 当前账号或 "anonymous" + */ private String currentAccount() { try { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); diff --git a/business-css/src/main/java/com/yfd/business/css/controller/ScenarioController.java b/business-css/src/main/java/com/yfd/business/css/controller/ScenarioController.java index 9c18ada..d18bae8 100644 --- a/business-css/src/main/java/com/yfd/business/css/controller/ScenarioController.java +++ b/business-css/src/main/java/com/yfd/business/css/controller/ScenarioController.java @@ -18,14 +18,37 @@ import java.util.HashSet; import java.util.Set; import java.time.LocalDateTime; +/** + * 情景管理接口。 + *

+ * 提供情景的新增、修改、删除、按项目查询等能力,并在写操作时进行项目写权限校验。 + *

+ *

+ * 状态约定: + *

+ *
    + *
  • status="0":默认初始状态(待运行/未运行)。
  • + *
+ */ @RestController @RequestMapping("/scenarios") public class ScenarioController { + /** + * 情景服务(MyBatis-Plus Service)。 + */ @Resource private ScenarioService scenarioService; + + /** + * 当前用户信息服务,用于记录 modifier 等审计字段。 + */ @Resource private IUserService userService; + + /** + * 项目权限辅助类,用于对 projectId 的读写操作进行鉴权校验。 + */ @Resource private ProjectAccessHelper projectAccessHelper; @@ -206,6 +229,14 @@ public class ScenarioController { return scenarioService.page(page, qw); } + /** + * 获取当前登录用户名。 + *

+ * 用于落库的 modifier 字段,若用户未登录或上下文解析失败,返回 "anonymous"。 + *

+ * + * @return 当前用户名或 "anonymous" + */ private String currentUsername() { try { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); diff --git a/business-css/src/main/java/com/yfd/business/css/controller/ScenarioResultController.java b/business-css/src/main/java/com/yfd/business/css/controller/ScenarioResultController.java index 1e13211..98d0dd7 100644 --- a/business-css/src/main/java/com/yfd/business/css/controller/ScenarioResultController.java +++ b/business-css/src/main/java/com/yfd/business/css/controller/ScenarioResultController.java @@ -28,16 +28,37 @@ import java.util.Map; import java.util.Set; import java.util.stream.Collectors; +/** + * 情景结果查询与导出接口。 + *

+ * 提供情景仿真结果的分页查询与按条件导出 Excel 能力,并通过 Scenario -> Project 的关系校验项目读权限。 + *

+ */ @RestController @RequestMapping("/scenario-results") public class ScenarioResultController { + /** + * 情景结果服务(MyBatis-Plus Service)。 + */ @Resource private ScenarioResultService scenarioResultService; + + /** + * 情景服务,用于获取 scenarioId 对应的 projectId 并执行权限校验。 + */ @Resource private ScenarioService scenarioService; + + /** + * 项目权限辅助类,用于对 projectId 的读操作进行鉴权校验。 + */ @Resource private ProjectAccessHelper projectAccessHelper; + + /** + * 设备服务,用于补全结果中的设备名称/类型等展示字段。 + */ @Resource private DeviceService deviceService; diff --git a/business-css/src/main/java/com/yfd/business/css/controller/package-info.java b/business-css/src/main/java/com/yfd/business/css/controller/package-info.java new file mode 100644 index 0000000..b225e64 --- /dev/null +++ b/business-css/src/main/java/com/yfd/business/css/controller/package-info.java @@ -0,0 +1,29 @@ +/** + * REST API Controller 层。 + *

+ * Controller 负责对外协议处理与请求路由,重点关注: + *

+ *
    + *
  • 入参来源:Path/Query/Body 的解析与校验。
  • + *
  • 权限校验:在安全框架基础上增加必要的业务级访问控制(例如项目权限)。
  • + *
  • 业务委托:不堆叠复杂业务逻辑,主要调用 Service 层完成业务处理。
  • + *
  • 返回统一:接口返回结构与错误码语义一致,便于前端与测试定位。
  • + *
+ *

+ * 常见接口类型: + *

+ *
    + *
  • 基础 CRUD:项目、场景、设备、事件、材料、关键数据等。
  • + *
  • 解析与校验:拓扑解析、属性解析、数据校验与问题汇总。
  • + *
  • 推演与训练:推演提交、训练提交、状态查询、结果查询等。
  • + *
+ *

+ * 编码约定: + *

+ *
    + *
  • 避免直接操作 Mapper;统一通过 Service 层访问数据。
  • + *
  • 避免在 Controller 中拼装复杂对象;复杂拼装放到 build/facade/model 相关包。
  • + *
  • 异常处理依赖统一异常机制,Controller 内部尽量不捕获吞异常。
  • + *
+ */ +package com.yfd.business.css.controller; diff --git a/business-css/src/main/java/com/yfd/business/css/domain/package-info.java b/business-css/src/main/java/com/yfd/business/css/domain/package-info.java new file mode 100644 index 0000000..b44c226 --- /dev/null +++ b/business-css/src/main/java/com/yfd/business/css/domain/package-info.java @@ -0,0 +1,20 @@ +/** + * 领域实体(Domain)层。 + *

+ * 该包主要承载与数据库表结构强相关的数据对象(Entity/PO),用于持久化与查询映射。 + * 与 DTO/Model 不同,Domain 更强调: + *

+ *
    + *
  • 字段与表结构一致,便于 Mapper 层直接映射。
  • + *
  • 以数据承载为主,尽量避免包含跨聚合业务逻辑。
  • + *
  • 序列化字段(如 JSON)需要在接口层做输入规范化,避免落库类型不匹配。
  • + *
+ *

+ * 约定: + *

+ *
    + *
  • Domain 用于持久化,不作为接口出参的最终结构(避免字段泄露与耦合)。
  • + *
  • 字段校验(非空、范围、格式)在 Controller/Service 层完成。
  • + *
+ */ +package com.yfd.business.css.domain; diff --git a/business-css/src/main/java/com/yfd/business/css/dto/package-info.java b/business-css/src/main/java/com/yfd/business/css/dto/package-info.java new file mode 100644 index 0000000..adf6ee6 --- /dev/null +++ b/business-css/src/main/java/com/yfd/business/css/dto/package-info.java @@ -0,0 +1,20 @@ +/** + * DTO(Data Transfer Object)层。 + *

+ * DTO 用于跨层传递与接口出入参承载,典型场景包括: + *

+ *
    + *
  • 拓扑解析结果、推演计划、节点/边结构等结构化返回。
  • + *
  • 事件属性解析结果、时间线点位、分段信息等解析型数据。
  • + *
  • 面向前端的聚合视图对象,避免直接暴露 Domain 字段。
  • + *
+ *

+ * 约定: + *

+ *
    + *
  • 字段命名与前端协议保持一致,减少序列化配置复杂度。
  • + *
  • DTO 内部尽量不包含持久化细节(主键策略、表字段等)。
  • + *
  • 集合字段尽量返回空集合而非 null,减少前端判空负担。
  • + *
+ */ +package com.yfd.business.css.dto; diff --git a/business-css/src/main/java/com/yfd/business/css/facade/SimDataFacade.java b/business-css/src/main/java/com/yfd/business/css/facade/SimDataFacade.java index 9686da1..7baa349 100644 --- a/business-css/src/main/java/com/yfd/business/css/facade/SimDataFacade.java +++ b/business-css/src/main/java/com/yfd/business/css/facade/SimDataFacade.java @@ -14,15 +14,43 @@ import org.springframework.stereotype.Component; import java.util.List; /** - * 仿真数据门面 - * 负责与各个业务Service交互,获取仿真所需的原始数据 (Project, Events 等) + * 仿真数据门面。 + *

+ * 负责与各业务 Service 交互,获取仿真所需的原始数据(项目、设备、事件等),并组装为统一的数据包对象供仿真流程使用。 + *

+ *

+ * 当前实现的职责边界: + *

+ *
    + *
  • 只做数据拉取与聚合,不做仿真计算与业务写入。
  • + *
  • 数据查询维度:projectId 关联项目与设备;scenarioId 关联事件。
  • + *
*/ @Component public class SimDataFacade { + + /** + * 项目服务,用于获取项目基础信息与拓扑数据等。 + */ @Autowired private ProjectService projectService; + + /** + * 事件服务,用于加载情景下的事件列表。 + */ @Autowired private EventService eventService; + + /** + * 设备服务,用于加载项目下设备列表(静态属性解析与补全)。 + */ @Autowired private DeviceService deviceService; + /** + * 加载仿真所需的基础数据并组装为数据包。 + * + * @param projectId 项目ID + * @param scenarioId 情景ID + * @return 仿真数据包(包含 Project、Device 列表与 Event 列表) + */ public SimDataPackage loadSimulationData(String projectId, String scenarioId) { // 1. 获取项目与拓扑 Project project = projectService.getById(projectId); diff --git a/business-css/src/main/java/com/yfd/business/css/facade/package-info.java b/business-css/src/main/java/com/yfd/business/css/facade/package-info.java new file mode 100644 index 0000000..d6450d4 --- /dev/null +++ b/business-css/src/main/java/com/yfd/business/css/facade/package-info.java @@ -0,0 +1,19 @@ +/** + * Facade(门面)层。 + *

+ * 该包用于封装对外部依赖或跨领域聚合的调用细节,对上层(Controller/Service)提供更稳定的调用接口: + *

+ *
    + *
  • 聚合多个数据源/服务的结果,降低上层编排复杂度。
  • + *
  • 对外部系统返回做统一解析、容错与错误语义转换。
  • + *
  • 对外部调用的超时、重试、限流策略提供集中入口(如有需要)。
  • + *
+ *

+ * 约定: + *

+ *
    + *
  • Facade 不应绕过 Service 的业务约束,避免形成“第二套业务入口”。
  • + *
  • 对外部调用涉及敏感信息时必须避免日志泄露。
  • + *
+ */ +package com.yfd.business.css.facade; diff --git a/business-css/src/main/java/com/yfd/business/css/mapper/package-info.java b/business-css/src/main/java/com/yfd/business/css/mapper/package-info.java new file mode 100644 index 0000000..a85d21f --- /dev/null +++ b/business-css/src/main/java/com/yfd/business/css/mapper/package-info.java @@ -0,0 +1,20 @@ +/** + * Mapper(数据访问)层。 + *

+ * 该包用于定义数据库访问接口,负责将 Domain 实体与数据库表进行映射,典型职责: + *

+ *
    + *
  • 基础 CRUD:按主键/条件增删改查。
  • + *
  • 复杂查询:按业务维度做聚合/统计/分页查询(如有)。
  • + *
  • 尽量不在 Mapper 层加入业务判断,保持其“数据访问”属性。
  • + *
+ *

+ * 约定: + *

+ *
    + *
  • 事务边界由 Service 层控制,Mapper 不负责事务语义。
  • + *
  • 外部输入参数需在上层完成校验,Mapper 层不承担入参清洗。
  • + *
  • 对于可能返回 null 的聚合结果(如 max/sum),上层应做判空兜底。
  • + *
+ */ +package com.yfd.business.css.mapper; diff --git a/business-css/src/main/java/com/yfd/business/css/meta/package-info.java b/business-css/src/main/java/com/yfd/business/css/meta/package-info.java new file mode 100644 index 0000000..23d3776 --- /dev/null +++ b/business-css/src/main/java/com/yfd/business/css/meta/package-info.java @@ -0,0 +1,19 @@ +/** + * 元信息(Meta)定义与注册。 + *

+ * 用于描述与设备、属性、尺寸等相关的“元数据”结构,包括: + *

+ *
    + *
  • 字段定义:字段名、类型、单位、取值约束等。
  • + *
  • Schema/Registry:对元数据进行注册、查询与按类型匹配。
  • + *
  • 与业务实体解耦:元信息侧重“描述”,业务数据侧重“实例”。
  • + *
+ *

+ * 约定: + *

+ *
    + *
  • 元信息应具备向后兼容策略,避免升级导致历史数据不可解析。
  • + *
  • 注册表初始化应可控,避免在静态初始化中引入外部依赖。
  • + *
+ */ +package com.yfd.business.css.meta; diff --git a/business-css/src/main/java/com/yfd/business/css/model/package-info.java b/business-css/src/main/java/com/yfd/business/css/model/package-info.java new file mode 100644 index 0000000..71dce56 --- /dev/null +++ b/business-css/src/main/java/com/yfd/business/css/model/package-info.java @@ -0,0 +1,26 @@ +/** + * 业务过程模型(Model)。 + *

+ * 该包用于承载推演/仿真过程中的上下文、请求与响应结构、结果转换等“过程型对象”,典型用途: + *

+ *
    + *
  • 推演请求/响应:面向推演服务的请求体与结果结构。
  • + *
  • 推演上下文:在一次推演流程内贯穿使用的临时数据与中间结果。
  • + *
  • 结果转换:将底层数据结构转换为前端可展示的结构(如轨迹、影响链路)。
  • + *
+ *

+ * 与 Domain/DTO 的区别: + *

+ *
    + *
  • Domain:更偏持久化实体;Model:更偏过程与计算语义。
  • + *
  • DTO:更偏接口传输;Model:更偏内部流程编排与输出组织。
  • + *
+ *

+ * 约定: + *

+ *
    + *
  • Model 应可序列化(必要时),并避免引入不必要的框架依赖。
  • + *
  • 对外输出字段应稳定,避免随内部实现频繁变化。
  • + *
+ */ +package com.yfd.business.css.model; diff --git a/business-css/src/main/java/com/yfd/business/css/package-info.java b/business-css/src/main/java/com/yfd/business/css/package-info.java new file mode 100644 index 0000000..78ebfe9 --- /dev/null +++ b/business-css/src/main/java/com/yfd/business/css/package-info.java @@ -0,0 +1,54 @@ +/** + * business-css 后端主模块。 + *

+ * 本模块围绕“仿真/场景/项目/设备/事件/材料/关键数据/训练任务”等核心对象提供后端服务能力, + * 主要以 REST API 形式对外提供增删改查、解析、仿真推演、模型训练提交与状态查询等能力。 + *

+ *

+ * 设计目标: + *

+ *
    + *
  • 职责清晰:Controller 仅做入参校验与协议转换,业务规则集中在 Service 层。
  • + *
  • 最小副作用:对外接口尽量保持幂等,避免隐藏状态修改。
  • + *
  • 可追溯:关键动作与异常路径保持可定位性,便于测试与运维排障。
  • + *
  • 可演进:核心对象与流程以可扩展方式建模,避免跨层强耦合。
  • + *
+ *

+ * 目录结构约定(对应子 package): + *

+ *
    + *
  • build:仿真数据构建与组装流程(面向推演输入的结构化转换)。
  • + *
  • controller:对外 HTTP API 层,负责请求路由、鉴权入口与参数校验。
  • + *
  • service:领域服务接口层,定义业务能力边界与业务语义。
  • + *
  • service.impl:领域服务实现层,封装业务规则、事务语义与外部依赖调用。
  • + *
  • domain:领域实体(与表结构/持久化对象强相关)。
  • + *
  • dto:跨层传输对象(接口返回、解析结果、拓扑结构等)。
  • + *
  • mapper:数据访问层(MyBatis/MyBatis-Plus Mapper)。
  • + *
  • model:业务过程模型(推演上下文、推演结果、请求/响应结构)。
  • + *
  • meta:设备/属性等元信息定义与注册表。
  • + *
  • facade:面向外部依赖的聚合门面(减少 Controller/ServiceImpl 直接依赖外部细节)。
  • + *
  • security:与项目访问控制相关的辅助能力(在现有安全框架内做业务级校验)。
  • + *
  • config:Spring Boot 配置类(MyBatis、OpenAPI、WebSocket、RestTemplate 等)。
  • + *
  • utils:业务内工具类(解析、转换、拼装等与通用框架工具区分)。
  • + *
  • common.exception:业务异常定义(区分参数错误、状态错误、推演错误等)。
  • + *
+ *

+ * 编码约定: + *

+ *
    + *
  • 接口返回统一:成功/失败结构统一,错误信息可读且便于定位。
  • + *
  • 空值与边界:对外输入必须显式校验,对外输出避免返回 null 集合。
  • + *
  • 外部返回解析:对外系统返回字段(例如 code/msg/data)需做健壮解析与容错。
  • + *
  • 序列化:DTO/Model 中字段命名与前端约定一致,避免隐式字段映射。
  • + *
  • 日志:仅记录业务必要信息,不记录密钥、口令、token 等敏感数据。
  • + *
+ *

+ * 运行期约束: + *

+ *
    + *
  • 认证:统一使用平台侧鉴权机制(JWT + 在线状态校验)。
  • + *
  • 权限:以“项目”为业务隔离边界,读写操作需校验项目可访问性。
  • + *
  • 并发:训练/推演等重任务需受容量控制与超时回收机制约束。
  • + *
+ */ +package com.yfd.business.css; diff --git a/business-css/src/main/java/com/yfd/business/css/security/package-info.java b/business-css/src/main/java/com/yfd/business/css/security/package-info.java new file mode 100644 index 0000000..822cde4 --- /dev/null +++ b/business-css/src/main/java/com/yfd/business/css/security/package-info.java @@ -0,0 +1,20 @@ +/** + * 业务安全辅助能力。 + *

+ * 在平台安全框架(认证、鉴权、Token 校验等)之上,该包提供与业务对象相关的访问控制辅助能力, + * 常见场景为“按项目隔离”的访问检查。 + *

+ *
    + *
  • 对外接口:封装“用户是否可访问某项目/资源”的判断。
  • + *
  • 最小暴露:不在此处扩散安全框架细节,避免上层出现大量重复判断。
  • + *
  • 失败语义:权限不足时给出明确的错误信息与统一的返回结构。
  • + *
+ *

+ * 约定: + *

+ *
    + *
  • 安全校验为前置约束,避免业务执行到中途再拒绝导致数据不一致。
  • + *
  • 不记录敏感信息(token、密钥、口令等)。
  • + *
+ */ +package com.yfd.business.css.security; diff --git a/business-css/src/main/java/com/yfd/business/css/service/AlgorithmModelService.java b/business-css/src/main/java/com/yfd/business/css/service/AlgorithmModelService.java index de243d2..cc40771 100644 --- a/business-css/src/main/java/com/yfd/business/css/service/AlgorithmModelService.java +++ b/business-css/src/main/java/com/yfd/business/css/service/AlgorithmModelService.java @@ -5,6 +5,13 @@ import com.yfd.business.css.domain.AlgorithmModel; import java.util.List; +/** + * 算法模型版本服务接口。 + *

+ * 提供模型版本查询、当前激活版本解析,以及批量删除前置校验等能力。 + * 默认 CRUD 能力由 MyBatis-Plus {@link IService} 提供。 + *

+ */ public interface AlgorithmModelService extends IService { /** @@ -36,6 +43,12 @@ public interface AlgorithmModelService extends IService { */ AlgorithmModel getCurrentModel(String algorithmType, String deviceType, String materialType); + /** + * 批量删除模型版本(带业务校验)。 + * + * @param ids 模型主键列表 + * @return 是否删除成功 + */ boolean deleteBatchWithCheck(List ids); } diff --git a/business-css/src/main/java/com/yfd/business/css/service/AlgorithmService.java b/business-css/src/main/java/com/yfd/business/css/service/AlgorithmService.java index 226042c..6ea6e80 100644 --- a/business-css/src/main/java/com/yfd/business/css/service/AlgorithmService.java +++ b/business-css/src/main/java/com/yfd/business/css/service/AlgorithmService.java @@ -3,5 +3,11 @@ package com.yfd.business.css.service; import com.baomidou.mybatisplus.extension.service.IService; import com.yfd.business.css.domain.Algorithm; +/** + * 算法字典服务接口。 + *

+ * 定义算法字典(Algorithm)的基础数据访问与管理能力,默认 CRUD 能力由 MyBatis-Plus {@link IService} 提供。 + *

+ */ public interface AlgorithmService extends IService { -} \ No newline at end of file +} diff --git a/business-css/src/main/java/com/yfd/business/css/service/CriticalDataService.java b/business-css/src/main/java/com/yfd/business/css/service/CriticalDataService.java index 50125ac..968e6b5 100644 --- a/business-css/src/main/java/com/yfd/business/css/service/CriticalDataService.java +++ b/business-css/src/main/java/com/yfd/business/css/service/CriticalDataService.java @@ -6,20 +6,59 @@ import org.springframework.web.multipart.MultipartFile; import java.util.List; import java.util.Map; +/** + * 临界数据服务接口。 + *

+ * 提供临界数据的导入导出、校验、清理等业务能力,默认 CRUD 能力由 MyBatis-Plus {@link IService} 提供。 + *

+ */ public interface CriticalDataService extends IService { /** * 导入临界数据 */ boolean importCriticalData(MultipartFile file, String deviceType); + /** + * 导入临界数据(V2)。 + * + * @param file Excel/CSV 文件 + * @param deviceType 设备类型 + * @return 是否导入成功 + */ boolean importCriticalDataV2(MultipartFile file, String deviceType); + /** + * 校验临界数据导入文件(V2)。 + * + * @param file Excel/CSV 文件 + * @param deviceType 设备类型 + * @return 校验结果(包含错误、可导入行等) + */ Map validateCriticalDataV2(MultipartFile file, String deviceType); + /** + * 导出临界数据(V2)。 + * + * @param deviceType 设备类型 + * @param ids 指定导出记录ID列表(可选) + * @return xlsx 文件字节流 + */ byte[] exportCriticalDataV2(String deviceType, List ids); + /** + * 下载临界数据导入模板(V2)。 + * + * @param deviceType 设备类型 + * @return xlsx 模板字节流 + */ byte[] templateCriticalDataV2(String deviceType); + /** + * 按设备类型清空临界数据。 + * + * @param deviceType 设备类型 + * @return 删除条数 + */ int deleteByDeviceType(String deviceType); } diff --git a/business-css/src/main/java/com/yfd/business/css/service/DeviceInferService.java b/business-css/src/main/java/com/yfd/business/css/service/DeviceInferService.java index 89002d1..3623542 100644 --- a/business-css/src/main/java/com/yfd/business/css/service/DeviceInferService.java +++ b/business-css/src/main/java/com/yfd/business/css/service/DeviceInferService.java @@ -28,6 +28,20 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; +/** + * 设备推理服务。 + *

+ * 将仿真计算得到的设备时序数据(按 deviceType/materialType 分组)转换为推理请求, + * 调用 Python 推理服务,并将推理结果写入情景结果表。 + *

+ *

+ * 推理分组策略(由业务约定驱动): + *

+ *
    + *
  • 按设备类型分组 → 按算法类型分组(全局/设备级覆盖)→ 按材料类型分组 → 按模型ID分组(可选)。
  • + *
  • 若未指定模型ID,则使用当前激活版本的模型。
  • + *
+ */ @Slf4j @Service public class DeviceInferService { @@ -39,16 +53,34 @@ public class DeviceInferService { @Value("${file-space.model-path}") private String modelRootPath; + /** + * 情景服务,用于读取情景配置(全局算法类型、设备级算法配置等)。 + */ @Resource private ScenarioService scenarioService; + + /** + * 模型版本服务,用于解析当前激活模型或按 ID 定向加载模型版本。 + */ @Resource private AlgorithmModelService algorithmModelService; + + /** + * 情景结果服务,用于写入推理结果与失败信息。 + */ @Resource private ScenarioResultService scenarioResultService; @Autowired private ObjectMapper objectMapper; + /** + * 对项目某情景下的设备数据进行推理并落库。 + * + * @param projectId 项目ID + * @param scenarioId 情景ID + * @param groupedDevices deviceType -> 设备时间步数据列表 + */ public void processDeviceInference(String projectId, String scenarioId, Map> groupedDevices) { // 增加标志位,记录是否至少成功执行了一次推理 diff --git a/business-css/src/main/java/com/yfd/business/css/service/DeviceService.java b/business-css/src/main/java/com/yfd/business/css/service/DeviceService.java index 2318495..cbedde1 100644 --- a/business-css/src/main/java/com/yfd/business/css/service/DeviceService.java +++ b/business-css/src/main/java/com/yfd/business/css/service/DeviceService.java @@ -5,21 +5,69 @@ import com.yfd.business.css.domain.Device; import org.springframework.web.multipart.MultipartFile; import java.util.List; import java.util.Map; +/** + * 设备服务接口。 + *

+ * 提供设备模板库/项目设备的业务操作能力,包括新增更新、以及 Excel 导入导出等。 + *

+ */ public interface DeviceService extends IService { /** * 导入设备 */ boolean importDevices(MultipartFile file, String deviceType); + /** + * 导入设备(V2)。 + * + * @param file Excel/CSV 文件 + * @param projectId 项目ID(为空时一般表示模板库) + * @param deviceType 设备类型 + * @return 是否导入成功 + */ boolean importDevicesV2(MultipartFile file, String projectId, String deviceType); + /** + * 校验设备导入文件(V2)。 + * + * @param file Excel/CSV 文件 + * @param projectId 项目ID + * @param deviceType 设备类型 + * @return 校验结果(包含错误、可导入行等) + */ Map validateDevicesV2(MultipartFile file, String projectId, String deviceType); + /** + * 导出设备(V2)。 + * + * @param projectId 项目ID + * @param deviceType 设备类型 + * @param ids 指定导出设备ID列表(可选) + * @return xlsx 文件字节流 + */ byte[] exportDevicesV2(String projectId, String deviceType, List ids); + /** + * 下载设备导入模板(V2)。 + * + * @param deviceType 设备类型 + * @return xlsx 模板字节流 + */ byte[] templateDevicesV2(String deviceType); + /** + * 新增设备(包含业务侧字段补全与校验)。 + * + * @param device 设备对象 + * @return 是否新增成功 + */ boolean createDevice(Device device) ; + /** + * 保存或更新设备(按业务主键/唯一键语义处理)。 + * + * @param device 设备对象 + * @return 是否保存/更新成功 + */ boolean saveOrUpdateByBusiness(Device device); } diff --git a/business-css/src/main/java/com/yfd/business/css/service/EventService.java b/business-css/src/main/java/com/yfd/business/css/service/EventService.java index 93d7686..03a1e9d 100644 --- a/business-css/src/main/java/com/yfd/business/css/service/EventService.java +++ b/business-css/src/main/java/com/yfd/business/css/service/EventService.java @@ -3,5 +3,11 @@ package com.yfd.business.css.service; import com.baomidou.mybatisplus.extension.service.IService; import com.yfd.business.css.domain.Event; +/** + * 事件服务接口。 + *

+ * 定义事件(Event)的基础数据访问与管理能力,默认 CRUD 能力由 MyBatis-Plus {@link IService} 提供。 + *

+ */ public interface EventService extends IService { } diff --git a/business-css/src/main/java/com/yfd/business/css/service/MaterialService.java b/business-css/src/main/java/com/yfd/business/css/service/MaterialService.java index b6e1430..98cd988 100644 --- a/business-css/src/main/java/com/yfd/business/css/service/MaterialService.java +++ b/business-css/src/main/java/com/yfd/business/css/service/MaterialService.java @@ -6,6 +6,12 @@ import com.yfd.business.css.domain.Material; import org.springframework.web.multipart.MultipartFile; import java.util.List; +/** + * 物料服务接口。 + *

+ * 提供物料(模板库/项目物料)的业务操作能力,包括导入导出、保存与业务维度的保存/更新等。 + *

+ */ public interface MaterialService extends IService { /** * 导入物料 @@ -17,9 +23,28 @@ public interface MaterialService extends IService { */ boolean saveMaterial(Material material); + /** + * 保存或更新物料(按业务主键/唯一键语义处理)。 + * + * @param material 物料对象 + * @return 是否保存/更新成功 + */ boolean saveOrUpdateByBusiness(Material material); + /** + * 导出物料(V2)。 + * + * @param projectId 项目ID(为空时一般表示模板库) + * @param ids 指定导出记录ID列表(可选) + * @param nameLike 名称模糊筛选(可选) + * @return xlsx 文件字节流 + */ byte[] exportMaterialsV2(String projectId, List ids, String nameLike); + /** + * 下载物料导入模板(V2)。 + * + * @return xlsx 模板字节流 + */ byte[] templateMaterialsV2(); } diff --git a/business-css/src/main/java/com/yfd/business/css/service/ModelTrainService.java b/business-css/src/main/java/com/yfd/business/css/service/ModelTrainService.java index 36a045d..309cf03 100644 --- a/business-css/src/main/java/com/yfd/business/css/service/ModelTrainService.java +++ b/business-css/src/main/java/com/yfd/business/css/service/ModelTrainService.java @@ -7,6 +7,12 @@ import org.springframework.web.multipart.MultipartFile; import java.util.List; import java.util.Map; +/** + * 模型训练任务服务接口。 + *

+ * 提供数据集上传与解析、训练任务提交、状态同步与回调更新、以及模型发布等能力。 + *

+ */ public interface ModelTrainService extends IService { /** * 上传数据集 @@ -15,8 +21,20 @@ public interface ModelTrainService extends IService { */ String uploadDataset(MultipartFile file); + /** + * 解析数据集列名列表。 + * + * @param datasetPath 数据集文件路径 + * @return 列名列表 + */ List parseDatasetColumns(String datasetPath); + /** + * 上传数据集并立即进行字段解析与基础检查。 + * + * @param file 上传文件 + * @return 预检结果(包含 path/columns/warnings 等) + */ Map uploadAndInspectDataset(MultipartFile file); /** @@ -48,5 +66,10 @@ public interface ModelTrainService extends IService { */ boolean publishModel(String taskId, String versionTag); + /** + * 查询训练任务提交容量(Training/Pending 上限与当前计数)。 + * + * @return 容量检查结果(包含 canSubmit、counts、limits 等) + */ Map checkCapacity(); } diff --git a/business-css/src/main/java/com/yfd/business/css/service/ProjectService.java b/business-css/src/main/java/com/yfd/business/css/service/ProjectService.java index 2bbe278..6f13cc7 100644 --- a/business-css/src/main/java/com/yfd/business/css/service/ProjectService.java +++ b/business-css/src/main/java/com/yfd/business/css/service/ProjectService.java @@ -6,6 +6,12 @@ import com.yfd.business.css.model.SimInfluenceNode; import java.util.List; +/** + * 项目服务接口。 + *

+ * 提供项目的基础 CRUD、工程导入导出、拓扑解析、仿真初始化与运行等业务能力。 + *

+ */ public interface ProjectService extends IService { /** diff --git a/business-css/src/main/java/com/yfd/business/css/service/ScenarioResultService.java b/business-css/src/main/java/com/yfd/business/css/service/ScenarioResultService.java index ae825e2..03ffbbd 100644 --- a/business-css/src/main/java/com/yfd/business/css/service/ScenarioResultService.java +++ b/business-css/src/main/java/com/yfd/business/css/service/ScenarioResultService.java @@ -3,5 +3,11 @@ package com.yfd.business.css.service; import com.baomidou.mybatisplus.extension.service.IService; import com.yfd.business.css.domain.ScenarioResult; +/** + * 情景结果服务接口。 + *

+ * 定义情景仿真结果(ScenarioResult)的基础数据访问与管理能力,默认 CRUD 能力由 MyBatis-Plus {@link IService} 提供。 + *

+ */ public interface ScenarioResultService extends IService { } diff --git a/business-css/src/main/java/com/yfd/business/css/service/ScenarioService.java b/business-css/src/main/java/com/yfd/business/css/service/ScenarioService.java index 2dd1ab3..1d99f0e 100644 --- a/business-css/src/main/java/com/yfd/business/css/service/ScenarioService.java +++ b/business-css/src/main/java/com/yfd/business/css/service/ScenarioService.java @@ -3,7 +3,18 @@ package com.yfd.business.css.service; import com.baomidou.mybatisplus.extension.service.IService; import com.yfd.business.css.domain.Scenario; +/** + * 情景服务接口。 + *

+ * 定义情景(Scenario)的基础数据访问与管理能力,默认 CRUD 能力由 MyBatis-Plus {@link IService} 提供。 + *

+ */ public interface ScenarioService extends IService { - //根据场景id,获取算法类型 + /** + * 根据情景 ID 获取该情景配置的算法类型。 + * + * @param scenarioId 情景ID + * @return 算法类型 + */ String getAlgorithmType(String scenarioId); } diff --git a/business-css/src/main/java/com/yfd/business/css/service/SimulationService.java b/business-css/src/main/java/com/yfd/business/css/service/SimulationService.java index bc06cfe..5d4ad85 100644 --- a/business-css/src/main/java/com/yfd/business/css/service/SimulationService.java +++ b/business-css/src/main/java/com/yfd/business/css/service/SimulationService.java @@ -3,6 +3,18 @@ package com.yfd.business.css.service; import com.yfd.business.css.model.SimulationRequest; import com.yfd.business.css.model.SimulationResult; +/** + * 仿真服务接口。 + *

+ * 定义仿真执行的统一入口,具体实现可根据不同仿真引擎/算法进行扩展。 + *

+ */ public interface SimulationService { + /** + * 执行一次仿真并返回结果。 + * + * @param request 仿真请求参数 + * @return 仿真结果 + */ SimulationResult runSimulation(SimulationRequest request); -} \ No newline at end of file +} diff --git a/business-css/src/main/java/com/yfd/business/css/service/TrainWebSocketService.java b/business-css/src/main/java/com/yfd/business/css/service/TrainWebSocketService.java index f79b7fe..7324fdb 100644 --- a/business-css/src/main/java/com/yfd/business/css/service/TrainWebSocketService.java +++ b/business-css/src/main/java/com/yfd/business/css/service/TrainWebSocketService.java @@ -6,6 +6,16 @@ import org.springframework.stereotype.Service; import lombok.extern.slf4j.Slf4j; import java.util.Map; +/** + * 训练状态 WebSocket 推送服务。 + *

+ * 将训练任务状态变化通过 STOMP 广播给前端,支持: + *

+ *
    + *
  • 单任务主题:/topic/train-status/{taskId}(详情页可用)
  • + *
  • 全局主题:/topic/train-status/all(列表页可用)
  • + *
+ */ @Slf4j @Service public class TrainWebSocketService { diff --git a/business-css/src/main/java/com/yfd/business/css/service/impl/AlgorithmModelServiceImpl.java b/business-css/src/main/java/com/yfd/business/css/service/impl/AlgorithmModelServiceImpl.java index 8c082aa..e2fef82 100644 --- a/business-css/src/main/java/com/yfd/business/css/service/impl/AlgorithmModelServiceImpl.java +++ b/business-css/src/main/java/com/yfd/business/css/service/impl/AlgorithmModelServiceImpl.java @@ -12,6 +12,21 @@ import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.stream.Collectors; + +/** + * 算法模型版本服务实现。 + *

+ * 提供当前激活模型的查询(按 algorithmType/deviceType/materialType 维度),以及模型版本的批量删除校验能力。 + *

+ *

+ * 业务约定: + *

+ *
    + *
  • 当前版本:is_current=1。
  • + *
  • 材料类型兼容:Mixed 与 MIX 视为同一类。
  • + *
  • material_type 为空时,优先匹配空/NULL,再作为回退版本使用。
  • + *
+ */ @Slf4j @Service public class AlgorithmModelServiceImpl extends ServiceImpl implements AlgorithmModelService { diff --git a/business-css/src/main/java/com/yfd/business/css/service/impl/AlgorithmServiceImpl.java b/business-css/src/main/java/com/yfd/business/css/service/impl/AlgorithmServiceImpl.java index 8dc34a2..a6b6514 100644 --- a/business-css/src/main/java/com/yfd/business/css/service/impl/AlgorithmServiceImpl.java +++ b/business-css/src/main/java/com/yfd/business/css/service/impl/AlgorithmServiceImpl.java @@ -7,6 +7,12 @@ import com.yfd.business.css.service.AlgorithmService; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +/** + * 算法字典服务实现。 + *

+ * 在保存/更新算法字典时,对部分 JSON 字段进行归一化处理,避免空字符串导致解析失败或前端渲染异常。 + *

+ */ @Slf4j @Service public class AlgorithmServiceImpl extends ServiceImpl implements AlgorithmService { @@ -22,6 +28,14 @@ public class AlgorithmServiceImpl extends ServiceImpl + * 将空/空白字符串替换为默认 JSON({} 或 []),保证字段在下游解析时始终为合法 JSON。 + *

+ * + * @param a 算法对象 + */ private void normalizeJsonFields(Algorithm a) { if (a == null) return; a.setInputParams(blankToDefaultJson(a.getInputParams(), "{}")); diff --git a/business-css/src/main/java/com/yfd/business/css/service/impl/CriticalDataServiceImpl.java b/business-css/src/main/java/com/yfd/business/css/service/impl/CriticalDataServiceImpl.java index 158cbb0..22a00e5 100644 --- a/business-css/src/main/java/com/yfd/business/css/service/impl/CriticalDataServiceImpl.java +++ b/business-css/src/main/java/com/yfd/business/css/service/impl/CriticalDataServiceImpl.java @@ -37,13 +37,36 @@ import java.util.Collections; import java.io.ByteArrayOutputStream; import lombok.extern.slf4j.Slf4j; + +/** + * 临界数据服务实现。 + *

+ * 提供临界数据的 Excel 导入、校验、导出与模板生成能力,并在导入过程中做字段解析与数据规整。 + *

+ *

+ * 设计要点: + *

+ *
    + *
  • 导入支持 xls/xlsx;V2 提供“先校验后导入”的交互方式。
  • + *
  • 对数值/公式单元格使用 DataFormatter + FormulaEvaluator 进行兼容读取。
  • + *
  • 解析失败信息以结构化 errors 列表返回给前端,便于定位问题行。
  • + *
+ */ @Service @Slf4j public class CriticalDataServiceImpl extends ServiceImpl implements CriticalDataService { + + /** + * JSON 解析器,用于处理扩展字段与自定义属性的解析。 + */ @Resource private ObjectMapper objectMapper; + + /** + * 当前用户信息服务,用于写入 modifier 等审计字段。 + */ @Resource private IUserService userService; @Override diff --git a/business-css/src/main/java/com/yfd/business/css/service/impl/DeviceServiceImpl.java b/business-css/src/main/java/com/yfd/business/css/service/impl/DeviceServiceImpl.java index 039b513..3c36a9a 100644 --- a/business-css/src/main/java/com/yfd/business/css/service/impl/DeviceServiceImpl.java +++ b/business-css/src/main/java/com/yfd/business/css/service/impl/DeviceServiceImpl.java @@ -37,15 +37,40 @@ import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; +/** + * 设备服务实现。 + *

+ * 提供设备的导入导出(含 V2 校验与模板)、以及设备新增/更新等能力,并对设备尺寸等字段按 schema 做解析与规整。 + *

+ *

+ * 设计要点: + *

+ *
    + *
  • 导入支持 xls/xlsx;V2 提供“先校验后导入”的交互方式。
  • + *
  • deviceSizeSchemaRegistry 用于按 deviceType 获取尺寸字段元数据,以统一导入/导出/校验口径。
  • + *
+ */ @Slf4j @Service public class DeviceServiceImpl extends ServiceImpl implements DeviceService { + + /** + * JSON 解析器,用于处理扩展字段与自定义属性的解析。 + */ @Resource private ObjectMapper objectMapper; + + /** + * 当前用户信息服务,用于写入 modifier 等审计字段。 + */ @Resource private IUserService userService; + + /** + * 设备尺寸 schema 注册表,用于按设备类型解析/校验尺寸字段。 + */ @Resource private DeviceSizeSchemaRegistry deviceSizeSchemaRegistry; @Override diff --git a/business-css/src/main/java/com/yfd/business/css/service/impl/EventServiceImpl.java b/business-css/src/main/java/com/yfd/business/css/service/impl/EventServiceImpl.java index 868f7a7..b391871 100644 --- a/business-css/src/main/java/com/yfd/business/css/service/impl/EventServiceImpl.java +++ b/business-css/src/main/java/com/yfd/business/css/service/impl/EventServiceImpl.java @@ -7,6 +7,12 @@ import com.yfd.business.css.service.EventService; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +/** + * 事件服务实现。 + *

+ * 事件数据主要由上层控制器按情景维度进行读写与权限校验;此处提供基于 MyBatis-Plus 的基础 CRUD 能力。 + *

+ */ @Slf4j @Service public class EventServiceImpl diff --git a/business-css/src/main/java/com/yfd/business/css/service/impl/MaterialServiceImpl.java b/business-css/src/main/java/com/yfd/business/css/service/impl/MaterialServiceImpl.java index 606ff8a..c279c56 100644 --- a/business-css/src/main/java/com/yfd/business/css/service/impl/MaterialServiceImpl.java +++ b/business-css/src/main/java/com/yfd/business/css/service/impl/MaterialServiceImpl.java @@ -35,13 +35,34 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +/** + * 物料服务实现。 + *

+ * 提供物料的 Excel 导入导出、模板生成,以及物料新增/更新的业务语义处理(如 materialId 自动生成)。 + *

+ *

+ * 设计要点: + *

+ *
    + *
  • 导入支持 xls/xlsx,通过表头映射进行字段解析。
  • + *
  • custom_attrs 字段以 JSON 形式保存,需要做合法性校验与空值规整。
  • + *
+ */ @Slf4j @Service public class MaterialServiceImpl extends ServiceImpl implements MaterialService { + + /** + * JSON 解析器,用于处理自定义属性等 JSON 字段。 + */ @Resource private ObjectMapper objectMapper; + + /** + * 当前用户信息服务,用于写入 modifier 等审计字段。 + */ @Resource private IUserService userService; @Override diff --git a/business-css/src/main/java/com/yfd/business/css/service/impl/ModelTrainServiceImpl.java b/business-css/src/main/java/com/yfd/business/css/service/impl/ModelTrainServiceImpl.java index dc2baad..2416224 100644 --- a/business-css/src/main/java/com/yfd/business/css/service/impl/ModelTrainServiceImpl.java +++ b/business-css/src/main/java/com/yfd/business/css/service/impl/ModelTrainServiceImpl.java @@ -15,6 +15,7 @@ import com.yfd.business.css.service.ModelTrainService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Lazy; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; @@ -23,6 +24,8 @@ import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionSynchronization; +import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.web.client.HttpStatusCodeException; import org.springframework.web.client.RestTemplate; import org.springframework.web.multipart.MultipartFile; @@ -53,6 +56,31 @@ import java.util.UUID; import java.util.regex.Pattern; import java.util.regex.Matcher; +/** + * 模型训练任务服务实现。 + *

+ * 该类负责训练数据集上传与解析、训练任务创建与提交、与外部 Python 训练服务交互、 + * 以及训练任务状态流转与超时回收等能力。 + *

+ *

+ * 关键流程: + *

+ *
    + *
  • 数据集上传:接收 MultipartFile,将文件落盘并返回可追溯路径。
  • + *
  • 数据集检查:解析表头/字段并返回给前端,用于配置训练参数。
  • + *
  • 任务提交:创建训练任务,初始化状态并触发异步训练调用。
  • + *
  • 容量控制:限制 Training 并发上限与 Pending 排队上限。
  • + *
  • 超时回收:定时扫描超时的 Training 任务并置为失败,防止容量被长期占用。
  • + *
+ *

+ * 约束与注意事项: + *

+ *
    + *
  • 路径通过配置注入,避免环境差异导致不可写目录。
  • + *
  • 外部返回字段需要做健壮解析,避免 null/空字符串/非数字导致异常。
  • + *
  • 回调更新需要具备幂等语义,避免重复回调造成状态覆盖。
  • + *
+ */ @Slf4j @Service public class ModelTrainServiceImpl extends ServiceImpl implements ModelTrainService { @@ -74,6 +102,10 @@ public class ModelTrainServiceImpl extends ServiceImpl + * 文件将按日期目录分组保存,文件名使用 UUID,避免并发上传冲突。 + *

+ *
    + *
  • 入参校验:上传文件不能为空。
  • + *
  • 路径解析:支持相对路径配置,运行时转换为绝对路径。
  • + *
  • 目录创建:目录不存在时创建目录,并处理并发创建的情况。
  • + *
+ * + * @param file 上传文件 + * @return 保存后的绝对路径 + */ @Override public String uploadDataset(MultipartFile file) { if (file.isEmpty()) { @@ -129,6 +175,16 @@ public class ModelTrainServiceImpl extends ServiceImpl + * 该方法用于前端在配置训练参数时选择输入特征列/输出列等字段。 + * 当前支持 CSV 与 Excel(xls/xlsx)两类格式,并对不支持的格式返回明确错误语义。 + *

+ * + * @param datasetPath 数据集文件路径(绝对路径) + * @return 列名列表 + */ @Override public List parseDatasetColumns(String datasetPath) { if (datasetPath == null || datasetPath.isBlank()) { @@ -148,6 +204,20 @@ public class ModelTrainServiceImpl extends ServiceImpl + * 适用于“一步上传并预检”的页面交互,返回结构包含: + *

+ *
    + *
  • path:保存后的数据集路径
  • + *
  • columns:解析出的列名列表
  • + *
  • warnings:字段与内容的告警信息(用于提示潜在配置错误)
  • + *
+ * + * @param file 上传文件 + * @return 预检结果 + */ @Override public Map uploadAndInspectDataset(MultipartFile file) { String path = uploadDataset(file); @@ -183,12 +253,25 @@ public class ModelTrainServiceImpl extends ServiceImpl self.asyncCallTrain(task)); return task.getTaskId(); } + private void runAfterCommit(Runnable runnable) { + if (TransactionSynchronizationManager.isSynchronizationActive()) { + TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() { + @Override + public void afterCommit() { + runnable.run(); + } + }); + return; + } + runnable.run(); + } + @Override public Map checkCapacity() { long training = countActiveTraining(); diff --git a/business-css/src/main/java/com/yfd/business/css/service/impl/ProjectServiceImpl.java b/business-css/src/main/java/com/yfd/business/css/service/impl/ProjectServiceImpl.java index 6dcc17c..c9a445d 100644 --- a/business-css/src/main/java/com/yfd/business/css/service/impl/ProjectServiceImpl.java +++ b/business-css/src/main/java/com/yfd/business/css/service/impl/ProjectServiceImpl.java @@ -51,6 +51,30 @@ import java.time.format.DateTimeFormatter; import java.time.LocalDateTime; import java.math.BigDecimal; +/** + * 项目域核心服务实现。 + *

+ * 该类聚合了项目相关的高频业务能力,覆盖“项目基础信息”“拓扑解析与校验”“设备执行顺序解析” + * “画布视图数据组织”“推演初始化与运行”“工程数据 Excel 导入导出”等场景。 + *

+ *

+ * 主要能力边界: + *

+ *
    + *
  • 数据读取:通过 Project/Device/Material/Scenario/Event 等服务与 Mapper 获取所需数据。
  • + *
  • 结构解析:解析项目 topology JSON,生成节点/边与线性执行计划,并输出问题列表。
  • + *
  • 推演编排:组织推演输入上下文,计算设备顺序与材料注入信息,输出推演可用的数据结构。
  • + *
  • 工程数据:支持工程 Excel 的导入/导出,用于项目初始化、备份与批量维护。
  • + *
+ *

+ * 运行与质量约束: + *

+ *
    + *
  • 健壮性:对 JSON 字段缺失、空字符串、非法引用等情况给出明确问题列表或异常语义。
  • + *
  • 一致性:涉及级联删除/批量导入时,需要保证项目内相关实体的一致性与可追溯性。
  • + *
  • 性能:导入导出与解析逻辑避免一次性构造超大字符串,必要处做长度截断与分段处理。
  • + *
+ */ @Slf4j @Service public class ProjectServiceImpl @@ -78,6 +102,19 @@ public class ProjectServiceImpl this.deviceDataParser = deviceDataParser; } + /** + * 导出全部项目数据到 Excel(xlsx)。 + *

+ * 主要用于后台管理侧的项目清单导出,字段包含项目编号、名称、描述、拓扑文本与时间字段等。 + *

+ *
    + *
  • 排序:按创建时间倒序输出。
  • + *
  • 长度限制:拓扑字段受 Excel 单元格长度限制,超长文本会被截断并追加省略号。
  • + *
  • 异常语义:导出失败抛出运行时异常,交由上层统一异常处理机制返回。
  • + *
+ * + * @return Excel 二进制内容 + */ @Override public byte[] exportAllProjectsExcel() { log.info("exportAllProjectsExcel start"); @@ -113,16 +150,26 @@ public class ProjectServiceImpl if (s == null) return ""; int max = 32767; if (s.length() <= max) return s; - if (max <= 3) return s.substring(0, max); return s.substring(0, max - 3) + "..."; } - @Override /** - * 解析指定项目的拓扑结构,生成节点、边与线性计算计划 + * 解析指定项目的拓扑结构,生成节点、边与线性计算计划。 + *

+ * topology 期望为 JSON 字符串,内部包含设备列表与连接关系等结构。 + * 解析结果将包含: + *

+ *
    + *
  • nodes:拓扑节点列表。
  • + *
  • edges:拓扑边列表。
  • + *
  • plans:线性执行计划(用于后续计算顺序/推演编排)。
  • + *
  • issues:解析过程中发现的问题列表(例如字段缺失、引用不存在等)。
  • + *
+ * * @param projectId 项目ID * @return TopologyParseResult */ + @Override public TopologyParseResult parseTopology(String projectId) { log.info("parseTopology start projectId={}", projectId); try { @@ -386,6 +433,20 @@ public class ProjectServiceImpl return t; } + /** + * 按 topology 中的设备出现顺序解析设备列表。 + *

+ * 与直接按数据库字段排序不同,该方法以 topology JSON 中 devices 数组的顺序为准, + * 并将其映射为对应的设备实体列表。 + *

+ *
    + *
  • 当项目不存在或 topology 为空时返回空列表。
  • + *
  • 当某些 deviceId 在数据库中不存在时,会自动跳过缺失设备。
  • + *
+ * + * @param projectId 项目ID + * @return 设备列表(保持 topology 顺序) + */ @Override public List parseDeviceOrder(String projectId) { log.info("parseDeviceOrder start projectId={}", projectId); @@ -419,12 +480,21 @@ public class ProjectServiceImpl } } - @Override /** - * 提取画布视图所需数据(设备、管线、边界、显示配置) + * 提取画布视图所需数据(设备、管线、边界、显示配置)。 + *

+ * 用于前端建模/编辑页面的初始化展示,返回结构为 Map,按前端协议组织多个子对象: + *

+ *
    + *
  • 基础信息:项目信息、拓扑基础字段。
  • + *
  • 实体集合:设备、材料、事件等(按需要提供)。
  • + *
  • 展示配置:画布边界、布局与扩展字段(按拓扑内容解析)。
  • + *
+ * * @param projectId 项目ID - * @return Map 视图对象 + * @return 画布视图对象 */ + @Override public Map parseCanvasView(String projectId) { log.info("parseCanvasView start projectId={}", projectId); try { diff --git a/business-css/src/main/java/com/yfd/business/css/service/impl/ScenarioResultServiceImpl.java b/business-css/src/main/java/com/yfd/business/css/service/impl/ScenarioResultServiceImpl.java index 5871fb8..966ed52 100644 --- a/business-css/src/main/java/com/yfd/business/css/service/impl/ScenarioResultServiceImpl.java +++ b/business-css/src/main/java/com/yfd/business/css/service/impl/ScenarioResultServiceImpl.java @@ -7,6 +7,12 @@ import com.yfd.business.css.service.ScenarioResultService; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +/** + * 情景结果服务实现。 + *

+ * 结果数据由仿真/推理流程写入,本类提供基于 MyBatis-Plus 的基础 CRUD 能力与统一的数据访问入口。 + *

+ */ @Slf4j @Service public class ScenarioResultServiceImpl diff --git a/business-css/src/main/java/com/yfd/business/css/service/impl/ScenarioServiceImpl.java b/business-css/src/main/java/com/yfd/business/css/service/impl/ScenarioServiceImpl.java index 3b0b1e7..ab45de2 100644 --- a/business-css/src/main/java/com/yfd/business/css/service/impl/ScenarioServiceImpl.java +++ b/business-css/src/main/java/com/yfd/business/css/service/impl/ScenarioServiceImpl.java @@ -7,6 +7,12 @@ import com.yfd.business.css.service.ScenarioService; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +/** + * 情景服务实现。 + *

+ * 提供情景相关的基础 CRUD 能力,以及按情景 ID 获取算法类型等轻量查询能力。 + *

+ */ @Slf4j @Service public class ScenarioServiceImpl diff --git a/business-css/src/main/java/com/yfd/business/css/service/impl/SimulationServiceImpl.java b/business-css/src/main/java/com/yfd/business/css/service/impl/SimulationServiceImpl.java index 808cd84..56b3047 100644 --- a/business-css/src/main/java/com/yfd/business/css/service/impl/SimulationServiceImpl.java +++ b/business-css/src/main/java/com/yfd/business/css/service/impl/SimulationServiceImpl.java @@ -8,9 +8,21 @@ import org.springframework.stereotype.Service; import java.time.Instant; +/** + * 仿真服务实现(占位实现)。 + *

+ * 当前实现用于打通接口链路与日志观测,尚未接入真实的仿真引擎计算逻辑。 + *

+ */ @Slf4j @Service public class SimulationServiceImpl implements SimulationService { + /** + * 执行一次仿真并返回结果。 + * + * @param request 仿真请求参数 + * @return 仿真结果 + */ @Override public SimulationResult runSimulation(SimulationRequest request) { log.info("SimulationServiceImpl runSimulation start request={}", request); @@ -23,4 +35,4 @@ public class SimulationServiceImpl implements SimulationService { log.info("SimulationServiceImpl runSimulation finish status={}", result.getStatus()); return result; } -} \ No newline at end of file +} diff --git a/business-css/src/main/java/com/yfd/business/css/service/impl/package-info.java b/business-css/src/main/java/com/yfd/business/css/service/impl/package-info.java new file mode 100644 index 0000000..a918944 --- /dev/null +++ b/business-css/src/main/java/com/yfd/business/css/service/impl/package-info.java @@ -0,0 +1,29 @@ +/** + * Service 实现层。 + *

+ * 该包包含领域服务的具体实现,承载主要业务规则与流程编排,典型职责: + *

+ *
    + *
  • 业务规则落地:状态机、容量控制、参数校验、对象关联关系处理等。
  • + *
  • 事务边界:在必要处声明事务,确保写操作一致性。
  • + *
  • 外部调用:调用推演/训练等外部服务时,负责请求组装、返回解析与容错。
  • + *
  • 数据访问:通过 Mapper 层完成持久化读写,不直接拼接 SQL。
  • + *
+ *

+ * 质量与安全约束: + *

+ *
    + *
  • 健壮性:对空值、空集合、外部返回字段做防御性处理,避免 NPE。
  • + *
  • 一致性:写入前校验引用存在性,避免产生孤儿数据与不一致关联。
  • + *
  • 幂等性:对外部回调/重复提交等场景具备幂等保护(如按任务ID去重)。
  • + *
  • 敏感信息:日志不记录口令、token、密钥等敏感字段。
  • + *
+ *

+ * 性能约定: + *

+ *
    + *
  • 批量操作:大数据量导出/查询优先使用分页与批处理,避免一次性加载过多数据。
  • + *
  • 长耗时任务:推演/训练等应走任务化机制并提供状态回调与超时回收。
  • + *
+ */ +package com.yfd.business.css.service.impl; diff --git a/business-css/src/main/java/com/yfd/business/css/service/package-info.java b/business-css/src/main/java/com/yfd/business/css/service/package-info.java new file mode 100644 index 0000000..d3e5bf8 --- /dev/null +++ b/business-css/src/main/java/com/yfd/business/css/service/package-info.java @@ -0,0 +1,27 @@ +/** + * Service(领域服务)接口层。 + *

+ * 该包定义后端核心业务能力的接口边界,是 Controller 与数据访问层之间的“业务语义层”。 + *

+ *
    + *
  • 接口表达业务语义:方法名与入参/出参体现领域含义,而非表字段操作。
  • + *
  • 事务语义在实现层落地:接口侧强调“做什么”,实现侧强调“如何做”。
  • + *
  • 幂等与一致性:对外接口尽量保持幂等与可预期错误语义。
  • + *
+ *

+ * 典型服务域: + *

+ *
    + *
  • 项目/场景:拓扑解析、推演输入准备、结果查询等。
  • + *
  • 设备/事件/材料:基础数据维护、解析与结构化转换。
  • + *
  • 训练任务:提交、容量控制、状态流转与回调处理。
  • + *
+ *

+ * 约定: + *

+ *
    + *
  • 接口不要依赖 Web 层对象(HttpServletRequest 等)。
  • + *
  • 接口返回值尽量明确,避免 Object/Map 泛化导致契约不清。
  • + *
+ */ +package com.yfd.business.css.service; diff --git a/business-css/src/main/java/com/yfd/business/css/utils/package-info.java b/business-css/src/main/java/com/yfd/business/css/utils/package-info.java new file mode 100644 index 0000000..86576c1 --- /dev/null +++ b/business-css/src/main/java/com/yfd/business/css/utils/package-info.java @@ -0,0 +1,20 @@ +/** + * 业务工具类。 + *

+ * 该包用于放置与 business-css 业务强相关的工具能力,例如: + *

+ *
    + *
  • 数据解析:对设备数据、属性字段、拓扑结构等进行解析与归一化。
  • + *
  • 转换与拼装:将 Domain/DTO/Model 在不同层之间做结构化转换。
  • + *
  • 校验辅助:对复杂字段格式、引用关系、一致性约束做统一校验。
  • + *
+ *

+ * 约定: + *

+ *
    + *
  • 工具类不应持有全局可变状态,避免并发与复用风险。
  • + *
  • 与框架通用工具(framework 模块)区分,避免重复实现通用能力。
  • + *
  • 异常提示应可定位输入问题,便于测试与排障。
  • + *
+ */ +package com.yfd.business.css.utils; diff --git a/framework/src/main/java/com/yfd/platform/system/controller/LoginController.java b/framework/src/main/java/com/yfd/platform/system/controller/LoginController.java index 4091a0a..8cc8a00 100644 --- a/framework/src/main/java/com/yfd/platform/system/controller/LoginController.java +++ b/framework/src/main/java/com/yfd/platform/system/controller/LoginController.java @@ -73,20 +73,6 @@ public class LoginController { user.getPassword()); - // 是否需要验证码不需要改成false - boolean hascode = false;//true; - if (hascode) { - // 查询验证码 - String code = webConfig.loginuserCache().get(user.getUuid()); - // 清除验证码 - webConfig.loginuserCache().remove(user.getUuid()); - if (StrUtil.isBlank(code)) { - return ResponseResult.error("验证码不存在或已过期"); - } - if (StrUtil.isBlank(user.getCode()) || !user.getCode().equalsIgnoreCase(code)) { - return ResponseResult.error("验证码错误"); - } - } //如果认证通过了,使用userId生成token token存入ResponseResult返回 UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUsername(), diff --git a/framework/src/main/java/com/yfd/platform/utils/EncryptUtils.java b/framework/src/main/java/com/yfd/platform/utils/EncryptUtils.java deleted file mode 100644 index 2ae2b26..0000000 --- a/framework/src/main/java/com/yfd/platform/utils/EncryptUtils.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2019-2020 Zheng Jie - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.yfd.platform.utils; - -import javax.crypto.Cipher; -import javax.crypto.SecretKey; -import javax.crypto.SecretKeyFactory; -import javax.crypto.spec.DESKeySpec; -import javax.crypto.spec.IvParameterSpec; -import java.nio.charset.StandardCharsets; - -/** - * 加密 - * @author - * @date 2018-11-23 - */ - -public class EncryptUtils { - - private static final String STR_PARAM = "Passw0rd"; - - private static Cipher cipher; - - private static final IvParameterSpec IV = new IvParameterSpec(STR_PARAM.getBytes(StandardCharsets.UTF_8)); - - private static DESKeySpec getDesKeySpec(String source) throws Exception { - if (source == null || source.length() == 0){ - return null; - } - cipher = Cipher.getInstance("DES/CBC/PKCS5Padding"); - String strKey = "Passw0rd"; - return new DESKeySpec(strKey.getBytes(StandardCharsets.UTF_8)); - } - - /** - * 对称加密 - */ - public static String desEncrypt(String source) throws Exception { - DESKeySpec desKeySpec = getDesKeySpec(source); - SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); - SecretKey secretKey = keyFactory.generateSecret(desKeySpec); - cipher.init(Cipher.ENCRYPT_MODE, secretKey, IV); - return byte2hex( - cipher.doFinal(source.getBytes(StandardCharsets.UTF_8))).toUpperCase(); - } - - /** - * 对称解密 - */ - public static String desDecrypt(String source) throws Exception { - byte[] src = hex2byte(source.getBytes(StandardCharsets.UTF_8)); - DESKeySpec desKeySpec = getDesKeySpec(source); - SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("DES"); - SecretKey secretKey = keyFactory.generateSecret(desKeySpec); - cipher.init(Cipher.DECRYPT_MODE, secretKey, IV); - byte[] retByte = cipher.doFinal(src); - return new String(retByte); - } - - private static String byte2hex(byte[] inStr) { - String stmp; - StringBuilder out = new StringBuilder(inStr.length * 2); - for (byte b : inStr) { - stmp = Integer.toHexString(b & 0xFF); - if (stmp.length() == 1) { - // 如果是0至F的单位字符串,则添加0 - out.append("0").append(stmp); - } else { - out.append(stmp); - } - } - return out.toString(); - } - - private static byte[] hex2byte(byte[] b) { - int size = 2; - if ((b.length % size) != 0){ - throw new IllegalArgumentException("长度不是偶数"); - } - byte[] b2 = new byte[b.length / 2]; - for (int n = 0; n < b.length; n += size) { - String item = new String(b, n, 2); - b2[n / 2] = (byte) Integer.parseInt(item, 16); - } - return b2; - } -} diff --git a/framework/src/main/java/com/yfd/platform/utils/FileUtil.java b/framework/src/main/java/com/yfd/platform/utils/FileUtil.java index 6940aee..bc7ff25 100644 --- a/framework/src/main/java/com/yfd/platform/utils/FileUtil.java +++ b/framework/src/main/java/com/yfd/platform/utils/FileUtil.java @@ -38,7 +38,6 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.*; import java.net.URLDecoder; -import java.security.MessageDigest; import java.text.DecimalFormat; import java.text.SimpleDateFormat; import java.util.*; @@ -285,61 +284,6 @@ public class FileUtil extends cn.hutool.core.io.FileUtil { } } - /** - * 判断两个文件是否相同 - */ - public static boolean check(File file1, File file2) { - String img1Md5 = getMd5(file1); - String img2Md5 = getMd5(file2); - return img1Md5.equals(img2Md5); - } - - /** - * 判断两个文件是否相同 - */ - public static boolean check(String file1Md5, String file2Md5) { - return file1Md5.equals(file2Md5); - } - - private static byte[] getByte(File file) { - // 得到文件长度 - byte[] b = new byte[(int) file.length()]; - try { - InputStream in = new FileInputStream(file); - try { - System.out.println(in.read(b)); - } catch (IOException e) { - log.error(e.getMessage(), e); - } - } catch (FileNotFoundException e) { - log.error(e.getMessage(), e); - return null; - } - return b; - } - - private static String getMd5(byte[] bytes) { - // 16进制字符 - char[] hexDigits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; - try { - MessageDigest mdTemp = MessageDigest.getInstance("MD5"); - mdTemp.update(bytes); - byte[] md = mdTemp.digest(); - int j = md.length; - char[] str = new char[j * 2]; - int k = 0; - // 移位 输出字符串 - for (byte byte0 : md) { - str[k++] = hexDigits[byte0 >>> 4 & 0xf]; - str[k++] = hexDigits[byte0 & 0xf]; - } - return new String(str); - } catch (Exception e) { - log.error(e.getMessage(), e); - } - return null; - } - /** * 下载文件 * @@ -391,8 +335,4 @@ public class FileUtil extends cn.hutool.core.io.FileUtil { fis.close(); } - public static String getMd5(File file) { - return getMd5(getByte(file)); - } - } diff --git a/framework/src/main/java/com/yfd/platform/utils/RsaUtils.java b/framework/src/main/java/com/yfd/platform/utils/RsaUtils.java index 6638ac7..20d7674 100644 --- a/framework/src/main/java/com/yfd/platform/utils/RsaUtils.java +++ b/framework/src/main/java/com/yfd/platform/utils/RsaUtils.java @@ -4,8 +4,6 @@ import org.apache.commons.codec.binary.Base64; import javax.crypto.Cipher; import java.security.*; -import java.security.interfaces.RSAPrivateKey; -import java.security.interfaces.RSAPublicKey; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; @@ -16,57 +14,6 @@ import java.security.spec.X509EncodedKeySpec; **/ public class RsaUtils { - private static final String SRC = "123456"; - - public static void main(String[] args) throws Exception { - System.out.println("\n"); - RsaKeyPair keyPair = generateKeyPair(); - System.out.println("公钥:" + keyPair.getPublicKey()); - System.out.println("私钥:" + keyPair.getPrivateKey()); - System.out.println("\n"); - test1(keyPair); - System.out.println("\n"); - test2(keyPair); - System.out.println("\n"); - } - - /** - * 公钥加密私钥解密 - */ - private static void test1(RsaKeyPair keyPair) throws Exception { - System.out.println("***************** 公钥加密私钥解密开始 *****************"); - String text1 = encryptByPublicKey(keyPair.getPublicKey(), RsaUtils.SRC); - String text2 = decryptByPrivateKey(keyPair.getPrivateKey(), text1); - System.out.println("加密前:" + RsaUtils.SRC); - System.out.println("加密后:" + text1); - System.out.println("解密后:" + text2); - if (RsaUtils.SRC.equals(text2)) { - System.out.println("解密字符串和原始字符串一致,解密成功"); - } else { - System.out.println("解密字符串和原始字符串不一致,解密失败"); - } - System.out.println("***************** 公钥加密私钥解密结束 *****************"); - } - - /** - * 私钥加密公钥解密 - * @throws Exception / - */ - private static void test2(RsaKeyPair keyPair) throws Exception { - System.out.println("***************** 私钥加密公钥解密开始 *****************"); - String text1 = encryptByPrivateKey(keyPair.getPrivateKey(), RsaUtils.SRC); - String text2 = decryptByPublicKey(keyPair.getPublicKey(), text1); - System.out.println("加密前:" + RsaUtils.SRC); - System.out.println("加密后:" + text1); - System.out.println("解密后:" + text2); - if (RsaUtils.SRC.equals(text2)) { - System.out.println("解密字符串和原始字符串一致,解密成功"); - } else { - System.out.println("解密字符串和原始字符串不一致,解密失败"); - } - System.out.println("***************** 私钥加密公钥解密结束 *****************"); - } - /** * 公钥解密 * @@ -137,45 +84,4 @@ public class RsaUtils { byte[] result = cipher.doFinal(text.getBytes()); return Base64.encodeBase64String(result); } - - /** - * 构建RSA密钥对 - * - * @return / - * @throws NoSuchAlgorithmException / - */ - public static RsaKeyPair generateKeyPair() throws NoSuchAlgorithmException { - KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); - keyPairGenerator.initialize(1024); - KeyPair keyPair = keyPairGenerator.generateKeyPair(); - RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic(); - RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate(); - String publicKeyString = Base64.encodeBase64String(rsaPublicKey.getEncoded()); - String privateKeyString = Base64.encodeBase64String(rsaPrivateKey.getEncoded()); - return new RsaKeyPair(publicKeyString, privateKeyString); - } - - - /** - * RSA密钥对对象 - */ - public static class RsaKeyPair { - - private final String publicKey; - private final String privateKey; - - public RsaKeyPair(String publicKey, String privateKey) { - this.publicKey = publicKey; - this.privateKey = privateKey; - } - - public String getPublicKey() { - return publicKey; - } - - public String getPrivateKey() { - return privateKey; - } - - } }