diff --git a/business-css/src/main/java/com/yfd/business/css/common/exception/ScenarioInferException.java b/business-css/src/main/java/com/yfd/business/css/common/exception/ScenarioInferException.java new file mode 100644 index 0000000..eb78953 --- /dev/null +++ b/business-css/src/main/java/com/yfd/business/css/common/exception/ScenarioInferException.java @@ -0,0 +1,16 @@ +package com.yfd.business.css.common.exception; + +public class ScenarioInferException extends RuntimeException { + + private final String failDetailJson; + + public ScenarioInferException(String message, String failDetailJson) { + super(message); + this.failDetailJson = failDetailJson; + } + + public String getFailDetailJson() { + return failDetailJson; + } +} + diff --git a/business-css/src/main/java/com/yfd/business/css/controller/SimController.java b/business-css/src/main/java/com/yfd/business/css/controller/SimController.java index ee5da1b..55e845b 100644 --- a/business-css/src/main/java/com/yfd/business/css/controller/SimController.java +++ b/business-css/src/main/java/com/yfd/business/css/controller/SimController.java @@ -2,18 +2,23 @@ package com.yfd.business.css.controller; import com.yfd.business.css.build.SimBuilder; import com.yfd.business.css.domain.Scenario; +import com.yfd.business.css.domain.ScenarioResult; import com.yfd.business.css.facade.SimDataFacade; import com.yfd.business.css.model.*; +import com.yfd.business.css.service.ScenarioResultService; import com.yfd.business.css.service.MaterialService; import com.yfd.business.css.service.ScenarioService; import com.yfd.business.css.service.SimService; import com.yfd.business.css.service.SimInferService; import com.yfd.platform.config.ResponseResult; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.beans.factory.annotation.Autowired; import com.yfd.platform.annotation.Log; import org.springframework.web.bind.annotation.*; import java.time.LocalDateTime; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -31,6 +36,8 @@ public class SimController { @Autowired private MaterialService materialService; @Autowired private SimInferService simInferService; @Autowired private ScenarioService scenarioService; + @Autowired private ScenarioResultService scenarioResultService; + @Autowired private ObjectMapper objectMapper; /** * 执行仿真计算 @@ -44,9 +51,24 @@ public class SimController { String projectId = (String) req.get("projectId"); String scenarioId = (String) req.get("scenarioId"); int steps = req.containsKey("steps") ? (int) req.get("steps") : 10; + boolean clearResult = Boolean.TRUE.equals(req.get("clearResult")); + + Scenario sc = scenarioService.getById(scenarioId); + if (sc != null) { + String st = sc.getStatus(); + if ("1".equals(st)) { + return ResponseResult.error("情景正在运行中,请稍后重试"); + } + if (("2".equals(st) || "3".equals(st)) && !clearResult) { + return ResponseResult.error("请先清空历史结果后再重新模拟"); + } + } + if (clearResult) { + scenarioResultService.remove(new QueryWrapper().eq("scenario_id", scenarioId)); + } // 0. Update Status: 更新情景状态为进行中 - updateScenarioStatus(scenarioId, "1"); // 1: 进行中 + updateScenarioStatus(scenarioId, "1", null); // 1: 进行中 try { // 1. Load Data: 获取项目、设备和事件数据 @@ -75,16 +97,30 @@ public class SimController { } catch (Exception e) { e.printStackTrace(); // 仿真计算失败,更新状态为失败 (3: 失败) - updateScenarioStatus(scenarioId, "3"); + String failDetail = null; + try { + Map fd = new HashMap<>(); + fd.put("code", "SIMULATION_FAILED"); + fd.put("message", "仿真计算失败"); + fd.put("detail", e.getMessage() == null ? "" : e.getMessage()); + fd.put("suggestions", List.of( + "请检查情景配置、事件配置与拓扑数据是否完整", + "请检查输入参数 steps 与相关数据是否符合预期" + )); + failDetail = objectMapper.writeValueAsString(fd); + } catch (Exception ignored) { + } + updateScenarioStatus(scenarioId, "3", failDetail); return ResponseResult.error("Simulation failed: " + e.getMessage()); } } - private void updateScenarioStatus(String scenarioId, String status) { + private void updateScenarioStatus(String scenarioId, String status, String failDetail) { try { Scenario scenario = new Scenario(); scenario.setScenarioId(scenarioId); scenario.setStatus(status); + scenario.setFailDetail(failDetail); scenario.setUpdatedAt(LocalDateTime.now()); scenarioService.updateById(scenario); } catch (Exception e) { diff --git a/business-css/src/main/java/com/yfd/business/css/domain/Scenario.java b/business-css/src/main/java/com/yfd/business/css/domain/Scenario.java index 26685d8..85b2041 100644 --- a/business-css/src/main/java/com/yfd/business/css/domain/Scenario.java +++ b/business-css/src/main/java/com/yfd/business/css/domain/Scenario.java @@ -54,4 +54,7 @@ public class Scenario implements Serializable { */ @TableField("device_algo_config") private String deviceAlgoConfig; + + @TableField("fail_detail") + private String failDetail; } 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 c8d0e66..5b9ceb7 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 @@ -6,6 +6,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.yfd.business.css.domain.Scenario; import com.yfd.business.css.domain.ScenarioResult; import com.yfd.business.css.domain.AlgorithmModel; +import com.yfd.business.css.common.exception.ScenarioInferException; import com.yfd.business.css.model.DeviceStepInfo; import com.yfd.business.css.model.InferRequest; import com.yfd.business.css.model.InferResponse; @@ -52,6 +53,8 @@ public class DeviceInferService { // 增加标志位,记录是否至少成功执行了一次推理 boolean hasAnySuccess = false; boolean hasAnyError = false; + List> missingModels = new ArrayList<>(); + List errorMessages = new ArrayList<>(); // 1. 获取情景配置信息 Scenario scenario = scenarioService.getById(scenarioId); @@ -117,6 +120,11 @@ public class DeviceInferService { if (model == null || model.getModelPath() == null) { log.error("Model path not found for algorithmType: {}, deviceType: {}, materialType: {}", currentAlgoType, deviceType, currentMaterialType); hasAnyError = true; + missingModels.add(Map.of( + "algorithmType", currentAlgoType, + "deviceType", deviceType, + "materialType", currentMaterialType + )); continue; } @@ -186,6 +194,9 @@ public class DeviceInferService { } catch (Exception e) { log.error("推理异常: {}", e.getMessage(), e); hasAnyError = true; + if (e.getMessage() != null && !e.getMessage().isBlank()) { + errorMessages.add(e.getMessage()); + } } } } @@ -193,9 +204,43 @@ public class DeviceInferService { // 5. 最终检查:如果没有任何成功的推理,且发生过错误,抛出异常以通知上层 if (!hasAnySuccess && hasAnyError) { - throw new RuntimeException("所有设备推理均失败或未找到模型"); + Map failDetail = new HashMap<>(); + if (!missingModels.isEmpty()) { + failDetail.put("code", "MODEL_NOT_FOUND"); + failDetail.put("message", "缺少可用模型"); + failDetail.put("missingModels", dedupeMissingModels(missingModels)); + failDetail.put("suggestions", List.of( + "请在模型管理中训练并激活缺失模型(algorithmType/deviceType/materialType 对应组合)", + "检查材料类型来源是否正确,必要时启用材料类型回退/默认映射策略" + )); + } else { + failDetail.put("code", "INFER_FAILED"); + failDetail.put("message", "推理失败"); + if (!errorMessages.isEmpty()) { + failDetail.put("errors", errorMessages.size() > 50 ? errorMessages.subList(0, 50) : errorMessages); + } + failDetail.put("suggestions", List.of( + "请检查 Python 推理服务是否可用以及请求参数是否完整", + "请检查模型是否已训练并激活,算法类型/设备类型/材料类型是否匹配" + )); + } + String json = null; + try { + json = objectMapper.writeValueAsString(failDetail); + } catch (Exception ignored) { + } + throw new ScenarioInferException("推理失败", json); } } + + private List> dedupeMissingModels(List> missingModels) { + LinkedHashMap> m = new LinkedHashMap<>(); + for (Map x : missingModels) { + String k = String.valueOf(x.get("algorithmType")) + "|" + String.valueOf(x.get("deviceType")) + "|" + String.valueOf(x.get("materialType")); + m.putIfAbsent(k, x); + } + return new ArrayList<>(m.values()); + } private InferRequest buildInferenceRequest(String deviceType,List devices,String modelPath, List requiredFeatures) { InferRequest request = new InferRequest(); diff --git a/business-css/src/main/java/com/yfd/business/css/service/SimInferService.java b/business-css/src/main/java/com/yfd/business/css/service/SimInferService.java index bf4f638..1019ea1 100644 --- a/business-css/src/main/java/com/yfd/business/css/service/SimInferService.java +++ b/business-css/src/main/java/com/yfd/business/css/service/SimInferService.java @@ -1,6 +1,8 @@ package com.yfd.business.css.service; import com.yfd.business.css.domain.Scenario; +import com.yfd.business.css.common.exception.ScenarioInferException; +import com.fasterxml.jackson.databind.ObjectMapper; import com.yfd.business.css.model.*; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -24,6 +26,9 @@ public class SimInferService { @Autowired private ScenarioService scenarioService; + + @Autowired + private ObjectMapper objectMapper; @Async public void asyncInferAndSave(String projectId, String scenarioId, SimContext context, List units) { @@ -83,7 +88,7 @@ public class SimInferService { if (groupedDevices.isEmpty()) { log.warn("No device data found for inference. scenarioId: {}", scenarioId); // 即使没有数据,也应该更新状态为完成,或者视为正常结束 - updateScenarioStatus(scenarioId, "2"); + updateScenarioStatus(scenarioId, "2", null); return; } @@ -98,21 +103,39 @@ public class SimInferService { // 但由于是 void 方法,无法直接得知。 // 简单改进:如果 groupedDevices 非空,但所有组都因为找不到模型而跳过,应该视为失败吗? // 目前策略:只要没有抛出未捕获异常,就视为 Success。 - updateScenarioStatus(scenarioId, "2"); + updateScenarioStatus(scenarioId, "2", null); log.info("Async inference completed successfully. scenarioId: {}", scenarioId); } catch (Exception e) { log.error("Async inference failed. scenarioId: {}, error: {}", scenarioId, e.getMessage(), e); // 5. 更新状态为失败 (假设 3 代表失败) - updateScenarioStatus(scenarioId, "3"); + String failDetail = null; + if (e instanceof ScenarioInferException) { + failDetail = ((ScenarioInferException) e).getFailDetailJson(); + } else { + try { + Map fd = new HashMap<>(); + fd.put("code", "INFER_FAILED"); + fd.put("message", "推理失败"); + fd.put("detail", e.getMessage() == null ? "" : e.getMessage()); + fd.put("suggestions", List.of( + "请检查模型是否已训练并激活,算法类型/设备类型/材料类型是否匹配", + "请检查 Python 推理服务是否可用以及请求参数是否完整" + )); + failDetail = objectMapper.writeValueAsString(fd); + } catch (Exception ignored) { + } + } + updateScenarioStatus(scenarioId, "3", failDetail); } } - private void updateScenarioStatus(String scenarioId, String status) { + private void updateScenarioStatus(String scenarioId, String status, String failDetail) { try { Scenario scenario = new Scenario(); scenario.setScenarioId(scenarioId); scenario.setStatus(status); + scenario.setFailDetail(failDetail); scenario.setUpdatedAt(LocalDateTime.now()); scenarioService.updateById(scenario); } catch (Exception e) { 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 5d04994..270b910 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 @@ -24,49 +24,43 @@ public class AlgorithmModelServiceImpl extends ServiceImpl queryWrapper = new QueryWrapper<>(); - queryWrapper.eq("algorithm_type", algorithmType) - .eq("device_type", deviceType) - .eq("is_current", 1); // 当前激活版本 - - if (materialType != null && !materialType.isEmpty()) { - queryWrapper.eq("material_type", materialType); - } else { - // 如果未指定材料类型,优先匹配无材料类型的通用模型,或者返回任意匹配(兼容旧逻辑) - // 这里为了稳健,如果 materialType 为 null,暂不添加 material_type 的过滤条件,或者显式匹配 null - // 建议:如果 materialType 为空,尝试匹配 material_type is null or '' - // queryWrapper.and(w -> w.isNull("material_type").or().eq("material_type", "")); - } - - List models = list(queryWrapper); - if (models != null && !models.isEmpty()) { - AlgorithmModel model = models.get(0); - log.debug("Found model: {}", model.getModelPath()); - return model.getModelPath(); - } else { + AlgorithmModel model = getCurrentModel(algorithmType, deviceType, materialType); + if (model == null) { log.warn("Model not found in database for algorithmType: {}, deviceType: {}, materialType: {}", algorithmType, deviceType, materialType); return null; } + log.debug("Found model: {}", model.getModelPath()); + return model.getModelPath(); } @Override public AlgorithmModel getCurrentModel(String algorithmType, String deviceType, String materialType) { log.debug("Querying current model for algorithmType: {}, deviceType: {}, materialType: {}", algorithmType, deviceType, materialType); - QueryWrapper queryWrapper = new QueryWrapper<>(); - queryWrapper.eq("algorithm_type", algorithmType) - .eq("device_type", deviceType) - .eq("is_current", 1); // 当前激活版本 - - if (materialType != null && !materialType.isEmpty()) { - queryWrapper.eq("material_type", materialType); - } - - List models = list(queryWrapper); - if (models != null && !models.isEmpty()) { - return models.get(0); + String mt = materialType == null ? null : materialType.trim(); + if (mt != null && mt.isEmpty()) mt = null; + + QueryWrapper qw = new QueryWrapper<>(); + qw.eq("algorithm_type", algorithmType) + .eq("device_type", deviceType) + .eq("is_current", 1); + if (mt != null) { + qw.eq("material_type", mt); } else { - return null; + qw.and(w -> w.isNull("material_type").or().eq("material_type", "")); } + List models = list(qw); + if (models != null && !models.isEmpty()) return models.get(0); + + if (mt != null) { + QueryWrapper fallback = new QueryWrapper<>(); + fallback.eq("algorithm_type", algorithmType) + .eq("device_type", deviceType) + .eq("is_current", 1) + .and(w -> w.isNull("material_type").or().eq("material_type", "")); + List generic = list(fallback); + if (generic != null && !generic.isEmpty()) return generic.get(0); + } + return null; } @Override