情景状态闭环

This commit is contained in:
wanxiaoli 2026-04-10 12:02:46 +08:00
parent 50fc25abbc
commit d102de849b
6 changed files with 158 additions and 41 deletions

View File

@ -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;
}
}

View File

@ -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<ScenarioResult>().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<String, Object> 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) {

View File

@ -54,4 +54,7 @@ public class Scenario implements Serializable {
*/
@TableField("device_algo_config")
private String deviceAlgoConfig;
@TableField("fail_detail")
private String failDetail;
}

View File

@ -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<Map<String, Object>> missingModels = new ArrayList<>();
List<String> 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<String, Object> 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<Map<String, Object>> dedupeMissingModels(List<Map<String, Object>> missingModels) {
LinkedHashMap<String, Map<String, Object>> m = new LinkedHashMap<>();
for (Map<String, Object> 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<DeviceStepInfo> devices,String modelPath, List<String> requiredFeatures) {
InferRequest request = new InferRequest();

View File

@ -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<SimUnit> 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<String, Object> 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) {

View File

@ -24,49 +24,43 @@ public class AlgorithmModelServiceImpl extends ServiceImpl<AlgorithmModelMapper,
@Override
public String getCurrentModelPath(String algorithmType, String deviceType, String materialType) {
log.debug("Querying current model path for algorithmType: {}, deviceType: {}, materialType: {}", algorithmType, deviceType, materialType);
QueryWrapper<AlgorithmModel> 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<AlgorithmModel> 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<AlgorithmModel> 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<AlgorithmModel> 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<AlgorithmModel> 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<AlgorithmModel> models = list(qw);
if (models != null && !models.isEmpty()) return models.get(0);
if (mt != null) {
QueryWrapper<AlgorithmModel> 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<AlgorithmModel> generic = list(fallback);
if (generic != null && !generic.isEmpty()) return generic.get(0);
}
return null;
}
@Override