From da679363a72a70a871f6780b678531c304466cb5 Mon Sep 17 00:00:00 2001 From: lixiaolong <702156524@qq.com> Date: Thu, 13 Aug 2026 17:55:38 +0800 Subject: [PATCH] =?UTF-8?q?feat(task):=20=E6=B7=BB=E5=8A=A0=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1=E5=AE=9E=E4=BE=8B=E9=87=8D=E6=96=B0=E6=89=A7=E8=A1=8C?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在AimaTaskInstanceController中添加rerun接口支持爱玛任务重新执行 - 在InspectionTaskInstanceController中添加rerun接口支持巡检任务重新执行 - 在TeTaskInstController中添加rerun接口支持测试任务重新执行 - 实现AimaTaskInstanceService的rerunInstance方法处理爱玛任务重跑逻辑 - 实现InspectionTaskInstanceService的rerunInstance方法处理巡检任务重跑逻辑 - 扩展FlowTaskRuntimeService添加loadExecutionRequest和rerunTask方法 - 修改createAndSaveTaskInstance方法保存执行请求快照到任务实例配置中 - 添加单元测试验证执行请求快照的恢复和向后兼容功能 --- .../aima/AimaTaskInstanceController.java | 8 +++ .../InspectionTaskInstanceController.java | 9 +++ .../controller/test/TeTaskInstController.java | 14 +++- .../service/IAimaTaskInstanceService.java | 3 + .../impl/AimaTaskInstanceServiceImpl.java | 30 ++++++++ .../IInspectionTaskInstanceService.java | 3 + .../InspectionTaskInstanceServiceImpl.java | 30 ++++++++ .../runtime/engine/FlowTaskRuntimeEntry.java | 52 +++++++++++++- .../engine/FlowTaskRuntimeService.java | 9 +++ .../FlowTaskRuntimeEntrySnapshotTest.java | 71 +++++++++++++++++++ 10 files changed, 225 insertions(+), 4 deletions(-) create mode 100644 cmvr-iot-test/src/test/java/com/cmvr/test/flow/runtime/engine/FlowTaskRuntimeEntrySnapshotTest.java diff --git a/cmvr-iot-admin/src/main/java/com/cmvr/web/controller/aima/AimaTaskInstanceController.java b/cmvr-iot-admin/src/main/java/com/cmvr/web/controller/aima/AimaTaskInstanceController.java index a77e4c2..6ab9fa4 100644 --- a/cmvr-iot-admin/src/main/java/com/cmvr/web/controller/aima/AimaTaskInstanceController.java +++ b/cmvr-iot-admin/src/main/java/com/cmvr/web/controller/aima/AimaTaskInstanceController.java @@ -88,6 +88,14 @@ public class AimaTaskInstanceController extends BaseController { return toAjax(aimaTaskInstanceService.startInstance(id, startVO)); } + @ApiOperation("再次执行已完成或失败的爱玛任务") + @PreAuthorize("@ss.hasPermi('aima:taskinstance:edit')") + @Log(title = "任务执行实例", businessType = BusinessType.INSERT) + @PostMapping("/rerun/{id}") + public AjaxResult rerun(@PathVariable("id") String id) { + return success(aimaTaskInstanceService.rerunInstance(id)); + } + /** * 暂停任务实例 */ diff --git a/cmvr-iot-admin/src/main/java/com/cmvr/web/controller/inspection/InspectionTaskInstanceController.java b/cmvr-iot-admin/src/main/java/com/cmvr/web/controller/inspection/InspectionTaskInstanceController.java index c00405e..afe663f 100644 --- a/cmvr-iot-admin/src/main/java/com/cmvr/web/controller/inspection/InspectionTaskInstanceController.java +++ b/cmvr-iot-admin/src/main/java/com/cmvr/web/controller/inspection/InspectionTaskInstanceController.java @@ -128,6 +128,15 @@ public class InspectionTaskInstanceController extends BaseController return toAjax(inspectionTaskInstanceService.startInstance(id, startVO)); } + @ApiOperation("再次执行已完成或失败的巡检任务") + @PreAuthorize("@ss.hasPermi('inspection:taskInstance:edit')") + @Log(title = "巡检任务执行实例", businessType = BusinessType.INSERT) + @PostMapping("/rerun/{id}") + public AjaxResult rerun(@PathVariable("id") String id) + { + return success(inspectionTaskInstanceService.rerunInstance(id)); + } + /** * 暂停任务实例 */ diff --git a/cmvr-iot-admin/src/main/java/com/cmvr/web/controller/test/TeTaskInstController.java b/cmvr-iot-admin/src/main/java/com/cmvr/web/controller/test/TeTaskInstController.java index 2856509..d40cea6 100644 --- a/cmvr-iot-admin/src/main/java/com/cmvr/web/controller/test/TeTaskInstController.java +++ b/cmvr-iot-admin/src/main/java/com/cmvr/web/controller/test/TeTaskInstController.java @@ -6,6 +6,7 @@ import com.cmvr.test.model.domain.TeNodeInst; import com.cmvr.test.model.vo.TeQueryTaskInstVO; import com.cmvr.test.model.vo.TeTaskInstVO; import com.cmvr.test.service.ITeTaskInstService; +import com.cmvr.test.flow.runtime.engine.FlowTaskRuntimeService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.RequiredArgsConstructor; @@ -14,6 +15,9 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import com.cmvr.common.core.domain.AjaxResult; import java.util.List; @@ -24,6 +28,7 @@ import java.util.List; public class TeTaskInstController extends BaseController { private final ITeTaskInstService teTaskInstService; + private final FlowTaskRuntimeService flowTaskRuntimeService; @ApiOperation("查询任务实例列表") @PreAuthorize("@ss.hasPermi('test:inst:list')") @@ -42,4 +47,11 @@ public class TeTaskInstController extends BaseController { return getDataTable(list); } -} \ No newline at end of file + @ApiOperation("再次执行已完成或失败的任务实例") + @PreAuthorize("@ss.hasPermi('test:config:execute')") + @PostMapping("/rerun/{id}") + public AjaxResult rerun(@PathVariable("id") String id) { + return success(flowTaskRuntimeService.rerunTask(id)); + } + +} diff --git a/cmvr-iot-aima/src/main/java/com/cmvr/aima/service/IAimaTaskInstanceService.java b/cmvr-iot-aima/src/main/java/com/cmvr/aima/service/IAimaTaskInstanceService.java index cc17de5..ec8a1ad 100644 --- a/cmvr-iot-aima/src/main/java/com/cmvr/aima/service/IAimaTaskInstanceService.java +++ b/cmvr-iot-aima/src/main/java/com/cmvr/aima/service/IAimaTaskInstanceService.java @@ -31,6 +31,9 @@ public interface IAimaTaskInstanceService extends IService { */ int startInstance(String id, AimaTaskStartVO startVO); + /** 按历史执行参数创建并启动一个新的爱玛任务实例。 */ + String rerunInstance(String id); + /** * 暂停任务实例 * diff --git a/cmvr-iot-aima/src/main/java/com/cmvr/aima/service/impl/AimaTaskInstanceServiceImpl.java b/cmvr-iot-aima/src/main/java/com/cmvr/aima/service/impl/AimaTaskInstanceServiceImpl.java index 6e99d33..d060902 100644 --- a/cmvr-iot-aima/src/main/java/com/cmvr/aima/service/impl/AimaTaskInstanceServiceImpl.java +++ b/cmvr-iot-aima/src/main/java/com/cmvr/aima/service/impl/AimaTaskInstanceServiceImpl.java @@ -188,6 +188,36 @@ public class AimaTaskInstanceServiceImpl extends ServiceImpl assignments) { if (assignments == null) { return; diff --git a/cmvr-iot-test/src/main/java/com/cmvr/test/flow/runtime/engine/FlowTaskRuntimeEntry.java b/cmvr-iot-test/src/main/java/com/cmvr/test/flow/runtime/engine/FlowTaskRuntimeEntry.java index f371bbd..e26880d 100644 --- a/cmvr-iot-test/src/main/java/com/cmvr/test/flow/runtime/engine/FlowTaskRuntimeEntry.java +++ b/cmvr-iot-test/src/main/java/com/cmvr/test/flow/runtime/engine/FlowTaskRuntimeEntry.java @@ -70,6 +70,50 @@ public class FlowTaskRuntimeEntry implements FlowTaskRuntimeService { ); } + @Override + public TeTaskExecuteNormalVO loadExecutionRequest(String instId) { + TeTaskInst source = taskInstService.getById(instId); + if (source == null) { + throw new GlobalException("任务实例不存在"); + } + if (!RunModeEnum.NORMAL.name().equals(source.getRunMode())) { + throw new GlobalException("只有正式任务实例支持再次执行"); + } + if (StrUtil.isNotBlank(source.getConfig())) { + try { + TeTaskExecuteNormalVO request = JSON.parseObject(source.getConfig(), TeTaskExecuteNormalVO.class); + if (request != null && StrUtil.isNotBlank(request.getTaskId())) { + return request; + } + } catch (RuntimeException ignored) { + // 旧版本的 config 可能不是执行请求快照,继续使用实例基础字段兼容重跑。 + } + } + return TeTaskExecuteNormalVO.builder() + .taskId(source.getTaskId()) + .robotId(source.getRobotId()) + .build(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public String rerunTask(String instId) { + TeTaskInst source = taskInstService.getById(instId); + assertRerunnable(source); + return executeTask(loadExecutionRequest(instId)); + } + + private void assertRerunnable(TeTaskInst source) { + if (source == null) { + throw new GlobalException("任务实例不存在"); + } + String success = String.valueOf(TaskStatusEnum.SUCCESS.getCode()); + String failed = String.valueOf(TaskStatusEnum.FAILED.getCode()); + if (!success.equals(source.getStatus()) && !failed.equals(source.getStatus())) { + throw new GlobalException("只有已完成或失败的任务才能再次执行"); + } + } + @Transactional(rollbackFor = Exception.class) public String executeTrialTask(TeTaskExecuteTrailVO taskExecuteTrailVO) { JSONObject runParams = taskExecuteTrailVO.getRunParams(); @@ -91,7 +135,7 @@ public class FlowTaskRuntimeEntry implements FlowTaskRuntimeService { String taskId = taskOrchestrationService.buildTrialTask(itemId); // 创建并保存任务实例 - TeTaskInst instance = createAndSaveTaskInstance(taskId, target, RunModeEnum.TRIAL); + TeTaskInst instance = createAndSaveTaskInstance(taskId, target, RunModeEnum.TRIAL, taskExecuteTrailVO); String instId = instance.getId(); try { @@ -164,7 +208,7 @@ public class FlowTaskRuntimeEntry implements FlowTaskRuntimeService { throw new GlobalException("当前任务存在未发布的检测项!"); } // 创建任务实例 - TeTaskInst instance = createAndSaveTaskInstance(taskId, target, runMode); + TeTaskInst instance = createAndSaveTaskInstance(taskId, target, runMode, originalVO); String instId = instance.getId(); try { @@ -188,10 +232,12 @@ public class FlowTaskRuntimeEntry implements FlowTaskRuntimeService { /** * 创建并保存任务实例 */ - private TeTaskInst createAndSaveTaskInstance(String taskId, RobotExecutionTarget target, RunModeEnum runMode) { + private TeTaskInst createAndSaveTaskInstance(String taskId, RobotExecutionTarget target, RunModeEnum runMode, + Object executionRequest) { TeTaskInst instance = new TeTaskInst(); instance.setTaskId(taskId); instance.setRobotId(target.getRobotId()); + instance.setConfig(executionRequest == null ? null : JSON.toJSONString(executionRequest)); instance.setRunMode(runMode.name()); instance.setStatus(String.valueOf(TaskStatusEnum.RUNNING.getCode())); boolean save = taskInstService.save(instance); diff --git a/cmvr-iot-test/src/main/java/com/cmvr/test/flow/runtime/engine/FlowTaskRuntimeService.java b/cmvr-iot-test/src/main/java/com/cmvr/test/flow/runtime/engine/FlowTaskRuntimeService.java index 4edf9fe..540eb3a 100644 --- a/cmvr-iot-test/src/main/java/com/cmvr/test/flow/runtime/engine/FlowTaskRuntimeService.java +++ b/cmvr-iot-test/src/main/java/com/cmvr/test/flow/runtime/engine/FlowTaskRuntimeService.java @@ -18,6 +18,15 @@ public interface FlowTaskRuntimeService { */ String executeTask(TeTaskExecuteNormalVO taskExecuteNormalVO); + /** + * 读取正式任务实例保存的执行请求快照。 + * 旧实例没有快照时,使用实例中的任务和机器人信息生成兼容请求。 + */ + TeTaskExecuteNormalVO loadExecutionRequest(String instId); + + /** 使用历史实例的执行请求创建并启动一个新的正式任务实例。 */ + String rerunTask(String instId); + /** * 执行试运行任务(TRIAL 模式) diff --git a/cmvr-iot-test/src/test/java/com/cmvr/test/flow/runtime/engine/FlowTaskRuntimeEntrySnapshotTest.java b/cmvr-iot-test/src/test/java/com/cmvr/test/flow/runtime/engine/FlowTaskRuntimeEntrySnapshotTest.java new file mode 100644 index 0000000..082c5c0 --- /dev/null +++ b/cmvr-iot-test/src/test/java/com/cmvr/test/flow/runtime/engine/FlowTaskRuntimeEntrySnapshotTest.java @@ -0,0 +1,71 @@ +package com.cmvr.test.flow.runtime.engine; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.cmvr.test.enums.RunModeEnum; +import com.cmvr.test.model.domain.TeTaskInst; +import com.cmvr.test.model.vo.ResourceRuntimeAssignmentVO; +import com.cmvr.test.model.vo.RobotRuntimeMemberVO; +import com.cmvr.test.model.vo.TeTaskExecuteNormalVO; +import com.cmvr.test.service.ITeTaskInstService; +import org.junit.Test; + +import java.lang.reflect.Proxy; +import java.util.List; + +import static org.junit.Assert.assertEquals; + +public class FlowTaskRuntimeEntrySnapshotTest { + + @Test + public void restoresCompleteExecutionRequestSnapshot() { + RobotRuntimeMemberVO member = new RobotRuntimeMemberVO(); + member.setRobotId("robot-2"); + ResourceRuntimeAssignmentVO assignment = new ResourceRuntimeAssignmentVO(); + assignment.setRoleKey("inspection"); + assignment.setMembers(List.of(member)); + TeTaskExecuteNormalVO request = TeTaskExecuteNormalVO.builder() + .taskId("task-1") + .robotId("robot-2") + .runtimeAssignments(List.of(assignment)) + .runParams(new JSONObject().fluentPut("item-1:1", + new JSONObject().fluentPut("threshold", 12))) + .build(); + TeTaskInst source = source(JSON.toJSONString(request)); + + TeTaskExecuteNormalVO restored = entry(source).loadExecutionRequest(source.getId()); + + assertEquals("task-1", restored.getTaskId()); + assertEquals("robot-2", restored.getRobotId()); + assertEquals("inspection", restored.getRuntimeAssignments().get(0).getRoleKey()); + assertEquals(12, restored.getRunParams().getJSONObject("item-1:1").getIntValue("threshold")); + } + + @Test + public void fallsBackForLegacyConfig() { + TeTaskInst source = source("{\"legacyParam\":true}"); + + TeTaskExecuteNormalVO restored = entry(source).loadExecutionRequest(source.getId()); + + assertEquals(source.getTaskId(), restored.getTaskId()); + assertEquals(source.getRobotId(), restored.getRobotId()); + } + + private FlowTaskRuntimeEntry entry(TeTaskInst source) { + ITeTaskInstService service = (ITeTaskInstService) Proxy.newProxyInstance( + ITeTaskInstService.class.getClassLoader(), + new Class[]{ITeTaskInstService.class}, + (proxy, method, args) -> "getById".equals(method.getName()) ? source : null); + return new FlowTaskRuntimeEntry(null, service, null, null, null, null, null); + } + + private TeTaskInst source(String config) { + TeTaskInst source = new TeTaskInst(); + source.setId("inst-1"); + source.setTaskId("legacy-task"); + source.setRobotId("legacy-robot"); + source.setRunMode(RunModeEnum.NORMAL.name()); + source.setConfig(config); + return source; + } +}