diff --git a/cmvr-iot-admin/src/main/java/com/cmvr/web/controller/vi/ViCorpusController.java b/cmvr-iot-admin/src/main/java/com/cmvr/web/controller/vi/ViCorpusController.java index bd90e29..8be93c9 100644 --- a/cmvr-iot-admin/src/main/java/com/cmvr/web/controller/vi/ViCorpusController.java +++ b/cmvr-iot-admin/src/main/java/com/cmvr/web/controller/vi/ViCorpusController.java @@ -4,11 +4,14 @@ import com.cmvr.common.core.controller.BaseController; import com.cmvr.common.core.domain.AjaxResult; import com.cmvr.common.core.page.TableDataInfo; import com.cmvr.vi.model.domain.ViCorpus; +import com.cmvr.vi.model.vo.ViContinuousCorpusVO; +import com.cmvr.vi.model.vo.ViCorpusVO; import com.cmvr.vi.service.IViCorpusService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.RequiredArgsConstructor; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -20,11 +23,6 @@ import org.springframework.web.bind.annotation.RestController; import java.util.List; -/** - * 语料库Controller - * - * @author cmvr-iot - */ @Api(tags = "语音交互--语料库") @RestController @RequestMapping("/vi/corpus") @@ -33,67 +31,97 @@ public class ViCorpusController extends BaseController { private final IViCorpusService viCorpusService; - /** - * 查询语料库列表 - */ - @ApiOperation("查询语料库列表") - @PreAuthorize("@ss.hasPermi('vi:corpus:list')") - @GetMapping("/list") - public TableDataInfo list(ViCorpus viCorpus) { - startPage(); - List list = viCorpusService.selectViCorpusList(viCorpus); - return getDataTable(list); - } - -// /** -// * 导出语料库列表 -// */ -// @ApiOperation("查询语料库列表") -// @PreAuthorize("@ss.hasPermi('vi:corpus:export')") -// @PostMapping("/export") -// public void export(HttpServletResponse response, ViCorpus viCorpus) { -// List list = viCorpusService.selectViCorpusList(viCorpus); -// ExcelUtil util = new ExcelUtil(ViCorpus.class); -// util.exportExcel(response, list, "语料库数据"); -// } - - /** - * 获取语料库详细信息 - */ - @ApiOperation("根据id获取语料库详细信息") + @ApiOperation("根据id获取语料详细信息") @PreAuthorize("@ss.hasPermi('vi:corpus:query')") @GetMapping(value = "/{corpusId}") public AjaxResult getInfo(@PathVariable("corpusId") Long corpusId) { return success(viCorpusService.selectViCorpusByCorpusId(corpusId)); } - /** - * 新增语料库 - */ - @ApiOperation("新增语料库") + // ----------------------- 单次对话语料/唤醒语料 -------------------------- + + @ApiOperation(value = "查询单次对话语料/唤醒语料列表") + @PreAuthorize("@ss.hasPermi('vi:corpus:list')") + @PostMapping("/single/list") + public TableDataInfo list(@Validated @RequestBody ViCorpusVO viCorpusVO) { + startPage(); + List list = viCorpusService.selectViCorpusList(viCorpusVO); + return getDataTable(list); + } + + @ApiOperation("新增单次对话语料/唤醒语料") @PreAuthorize("@ss.hasPermi('vi:corpus:add')") @PostMapping public AjaxResult add(@RequestBody ViCorpus viCorpus) { return toAjax(viCorpusService.insertViCorpus(viCorpus)); } - /** - * 修改语料库 - */ - @ApiOperation("修改语料库") + @ApiOperation("批量新增单次对话语料/唤醒语料") + @PreAuthorize("@ss.hasPermi('vi:corpus:add')") + @PostMapping("/insertBatch") + public AjaxResult insertBatch(@RequestBody List data) { + return toAjax(viCorpusService.insertBatch(data)); + } + + @ApiOperation("删除单次对话语料/唤醒语料") + @PreAuthorize("@ss.hasPermi('vi:corpus:remove')") + @DeleteMapping("/{corpusIds}") + public AjaxResult remove(@PathVariable Long[] corpusIds) { + return toAjax(viCorpusService.deleteViCorpusByCorpusIds(corpusIds)); + } + + @ApiOperation("修改单次对话语料/唤醒语料") @PreAuthorize("@ss.hasPermi('vi:corpus:edit')") @PutMapping public AjaxResult edit(@RequestBody ViCorpus viCorpus) { return toAjax(viCorpusService.updateViCorpus(viCorpus)); } - /** - * 删除语料库 - */ - @ApiOperation("删除语料库") - @PreAuthorize("@ss.hasPermi('vi:corpus:remove')") - @DeleteMapping("/{corpusIds}") - public AjaxResult remove(@PathVariable Long[] corpusIds) { - return toAjax(viCorpusService.deleteViCorpusByCorpusIds(corpusIds)); + // ----------------------- 连续对话 -------------------------- + + @ApiOperation("查询连续对话列表") + @PreAuthorize("@ss.hasPermi('vi:corpus:list')") + @PostMapping("/continuous/list") + public TableDataInfo continuousList(@Validated @RequestBody ViCorpusVO viCorpusVO) { + startPage(); + List list = viCorpusService.selectContinuousList(viCorpusVO); + return getDataTable(list); } + + @ApiOperation("新增连续对话") + @PreAuthorize("@ss.hasPermi('vi:corpus:add')") + @PostMapping("/continuous/insert") + public AjaxResult add(@RequestBody ViContinuousCorpusVO viContinuousCorpusVO) { + return toAjax(viCorpusService.insertContinuousViCorpus(viContinuousCorpusVO)); + } + + @ApiOperation("修改连续对话") + @PreAuthorize("@ss.hasPermi('vi:corpus:edit')") + @PostMapping("/continuous/update") + public AjaxResult update(@RequestBody ViContinuousCorpusVO viContinuousCorpusVO) { + return toAjax(viCorpusService.updateContinuousViCorpus(viContinuousCorpusVO)); + } + + @ApiOperation("删除连续对话") + @PreAuthorize("@ss.hasPermi('vi:corpus:remove')") + @DeleteMapping("/continuous") + public AjaxResult continuousRemove(@RequestBody String[] parentIds) { + return toAjax(viCorpusService.deleteViCorpusByParentIds(parentIds)); + } + +/* @ApiOperation("批量修改") + @PreAuthorize("@ss.hasPermi('vi:corpus:edit')") + @PostMapping("/updateBatch") + public AjaxResult updateBatch(@RequestBody List data) { + return toAjax(viCorpusService.updateBatch(data)); + }*/ + +/* @ApiOperation("批量新增/修改") + @PreAuthorize("@ss.hasPermi('vi:corpus:edit')") + @PostMapping("/insertOrUpdateBatch") + public AjaxResult insertOrUpdateBatch(@RequestBody List data) { + return toAjax(viCorpusService.insertOrUpdateBatch(data)); + }*/ + + } diff --git a/cmvr-iot-admin/src/main/java/com/cmvr/web/controller/vi/ViProjectController.java b/cmvr-iot-admin/src/main/java/com/cmvr/web/controller/vi/ViProjectController.java new file mode 100644 index 0000000..249db2a --- /dev/null +++ b/cmvr-iot-admin/src/main/java/com/cmvr/web/controller/vi/ViProjectController.java @@ -0,0 +1,74 @@ +package com.cmvr.web.controller.vi; + +import com.cmvr.common.core.controller.BaseController; +import com.cmvr.common.core.domain.AjaxResult; +import com.cmvr.common.core.page.TableDataInfo; +import com.cmvr.vi.model.domain.ViProject; +import com.cmvr.vi.service.IViProjectService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@Api(tags = "语音交互--项目管理") +@RestController +@RequestMapping("/vi/project") +@RequiredArgsConstructor +public class ViProjectController extends BaseController { + + private final IViProjectService viProjectService; + + @ApiOperation("获取项目列表") + @PreAuthorize("@ss.hasPermi('vi:project:list')") + @GetMapping("/list") + public TableDataInfo list(ViProject viProject) { + startPage(); + List list = viProjectService.selectViProjectList(viProject); + return getDataTable(list); + } + + @ApiOperation("根据id获取项目信息") + @PreAuthorize("@ss.hasPermi('vi:project:query')") + @GetMapping(value = "/{projectId}") + public AjaxResult getInfo(@PathVariable("projectId") String projectId) { + return success(viProjectService.selectViProjectByProjectId(projectId)); + } + + @ApiOperation("添加项目") + @PreAuthorize("@ss.hasPermi('vi:project:add')") + @PostMapping + public AjaxResult add(@RequestBody ViProject viProject) { + return toAjax(viProjectService.insertViProject(viProject)); + } + + @ApiOperation("编辑项目") + @PreAuthorize("@ss.hasPermi('vi:project:edit')") + @PutMapping + public AjaxResult edit(@RequestBody ViProject viProject) { + return toAjax(viProjectService.updateViProject(viProject)); + } + + @ApiOperation("删除项目") + @PreAuthorize("@ss.hasPermi('vi:project:remove')") + @DeleteMapping("/{projectIds}") + public AjaxResult remove(@PathVariable String[] projectIds) { + return toAjax(viProjectService.deleteViProjectByProjectIds(projectIds)); + } + +// @ApiOperation("执行项目") + @PostMapping("/execute") + public AjaxResult execute(@RequestParam("projectId") String projectId) { + return AjaxResult.ok(viProjectService.execute(projectId)); + } +} diff --git a/cmvr-iot-admin/src/main/java/com/cmvr/web/controller/vi/ViSchemeController.java b/cmvr-iot-admin/src/main/java/com/cmvr/web/controller/vi/ViSchemeController.java new file mode 100644 index 0000000..d9ca7ef --- /dev/null +++ b/cmvr-iot-admin/src/main/java/com/cmvr/web/controller/vi/ViSchemeController.java @@ -0,0 +1,66 @@ +package com.cmvr.web.controller.vi; + +import com.cmvr.common.core.controller.BaseController; +import com.cmvr.common.core.domain.AjaxResult; +import com.cmvr.common.core.page.TableDataInfo; +import com.cmvr.vi.model.domain.ViScheme; +import com.cmvr.vi.service.IViSchemeService; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import lombok.RequiredArgsConstructor; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@Api(tags = "语音交互--方案") +@RestController +@RequestMapping("/vi/scheme") +@RequiredArgsConstructor +public class ViSchemeController extends BaseController { + private final IViSchemeService viSchemeService; + + @ApiOperation("查询方案列表") + @PreAuthorize("@ss.hasPermi('vi:scheme:list')") + @GetMapping("/list") + public TableDataInfo list(ViScheme viScheme) { + startPage(); + List list = viSchemeService.selectViSchemeList(viScheme); + return getDataTable(list); + } + + @ApiOperation("根据ID查询方案详细信息") + @PreAuthorize("@ss.hasPermi('vi:scheme:query')") + @GetMapping(value = "/{schemeId}") + public AjaxResult getInfo(@PathVariable("schemeId") String schemeId) { + return success(viSchemeService.selectViSchemeBySchemeId(schemeId)); + } + + @ApiOperation("新增方案") + @PreAuthorize("@ss.hasPermi('vi:scheme:add')") + @PostMapping + public AjaxResult add(@RequestBody ViScheme viScheme) { + return toAjax(viSchemeService.insertViScheme(viScheme)); + } + + @ApiOperation("修改方案") + @PreAuthorize("@ss.hasPermi('vi:scheme:edit')") + @PutMapping + public AjaxResult edit(@RequestBody ViScheme viScheme) { + return toAjax(viSchemeService.updateViScheme(viScheme)); + } + + @ApiOperation("删除方案") + @PreAuthorize("@ss.hasPermi('vi:scheme:remove')") + @DeleteMapping("/{schemeIds}") + public AjaxResult remove(@PathVariable String[] schemeIds) { + return toAjax(viSchemeService.deleteViSchemeBySchemeIds(schemeIds)); + } +} diff --git a/cmvr-iot-api/cmvr-iot-edge/cmvr-iot-grpc-client/src/main/java/com/cmvr/edge/client/service/impl/EdgeCameraServiceImpl.java b/cmvr-iot-api/cmvr-iot-edge/cmvr-iot-grpc-client/src/main/java/com/cmvr/edge/client/service/impl/EdgeCameraServiceImpl.java index 4f191cd..6cabbf4 100644 --- a/cmvr-iot-api/cmvr-iot-edge/cmvr-iot-grpc-client/src/main/java/com/cmvr/edge/client/service/impl/EdgeCameraServiceImpl.java +++ b/cmvr-iot-api/cmvr-iot-edge/cmvr-iot-grpc-client/src/main/java/com/cmvr/edge/client/service/impl/EdgeCameraServiceImpl.java @@ -24,7 +24,11 @@ import org.springframework.http.MediaType; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; +import javax.imageio.IIOImage; import javax.imageio.ImageIO; +import javax.imageio.ImageWriteParam; +import javax.imageio.ImageWriter; +import javax.imageio.stream.FileImageOutputStream; import java.awt.*; import java.awt.color.ColorSpace; import java.awt.image.BufferedImage; @@ -42,6 +46,7 @@ import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Base64; +import java.util.Iterator; import java.util.List; @@ -166,6 +171,7 @@ public class EdgeCameraServiceImpl implements EdgeCameraService, EdgeStreamServi throw new GlobalException("响应图片数据无效"); } String filePath = saveImage(frame.getData(), frame.getWidth(), frame.getHeight(), false); + log.info("彩色图片地址:{}", filePath); // String deepFilePath = saveImage(depthFrame.getData(), depthFrame.getWidth(), depthFrame.getHeight(), true); // List result = new ArrayList<>(); try { @@ -178,7 +184,7 @@ public class EdgeCameraServiceImpl implements EdgeCameraService, EdgeStreamServi } catch (IOException e) { throw new GlobalException("图片上传失败: " + e.getMessage()); } finally { - deleteTempFile(filePath); +// deleteTempFile(filePath); // deleteTempFile(deepFilePath); // todo 暂时不关闭摄像头 // stop(terminalId, deviceId); @@ -368,50 +374,50 @@ public class EdgeCameraServiceImpl implements EdgeCameraService, EdgeStreamServi String fileName = System.currentTimeMillis() + (isDepth ? "_depth" : "") + ".jpg"; String filePath = Paths.get(tempDir.getAbsolutePath(), fileName).toString(); + try { byte[] pixels = pixelData.toByteArray(); BufferedImage image; if (isDepth) { - // 深度图:单通道(灰度) + // 灰度深度图 DataBuffer buffer = new DataBufferByte(pixels, pixels.length); - WritableRaster raster = Raster.createInterleavedRaster( - buffer, width, height, width, 1, - new int[]{0}, null - ); - - ColorModel cm = new ComponentColorModel( - ColorSpace.getInstance(ColorSpace.CS_GRAY), - false, false, - Transparency.OPAQUE, - DataBuffer.TYPE_BYTE - ); + WritableRaster raster = Raster.createInterleavedRaster(buffer, width, height, width, 1, new int[]{0}, null); + ColorModel cm = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), false, false, + Transparency.OPAQUE, DataBuffer.TYPE_BYTE); image = new BufferedImage(cm, raster, false, null); } else { - // 彩色图:RGB 三通道 + // 彩色RGB DataBuffer buffer = new DataBufferByte(pixels, pixels.length); - WritableRaster raster = Raster.createInterleavedRaster( - buffer, width, height, 3 * width, 3, - new int[]{2, 1, 0}, null - ); - - ColorModel cm = new ComponentColorModel( - ColorSpace.getInstance(ColorSpace.CS_sRGB), - false, false, - Transparency.OPAQUE, - DataBuffer.TYPE_BYTE - ); + WritableRaster raster = Raster.createInterleavedRaster(buffer, width, height, 3 * width, 3, + new int[]{1, 2, 0}, null); + ColorModel cm = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), false, false, + Transparency.OPAQUE, DataBuffer.TYPE_BYTE); image = new BufferedImage(cm, raster, false, null); } - File output = new File(filePath); - ImageIO.write(image, "jpg", output); + // 设置高质量JPEG压缩 + Iterator writers = ImageIO.getImageWritersByFormatName("jpg"); + ImageWriter writer = writers.next(); + + try (FileImageOutputStream output = new FileImageOutputStream(new File(filePath))) { + writer.setOutput(output); + ImageWriteParam param = writer.getDefaultWriteParam(); + if (param.canWriteCompressed()) { + param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); + param.setCompressionQuality(0.95f); // 压缩质量/压缩比 95% + } + writer.write(null, new IIOImage(image, null, null), param); + } + writer.dispose(); + } catch (IOException e) { throw new GlobalException(e.getMessage()); } return filePath; } + /** * 将本地文件转换为 MultipartFile */ diff --git a/cmvr-iot-test/src/main/java/com/cmvr/test/enums/FlowSceneTypeEnum.java b/cmvr-iot-test/src/main/java/com/cmvr/test/enums/FlowSceneTypeEnum.java new file mode 100644 index 0000000..56ce95e --- /dev/null +++ b/cmvr-iot-test/src/main/java/com/cmvr/test/enums/FlowSceneTypeEnum.java @@ -0,0 +1,11 @@ +package com.cmvr.test.enums; + +/** + * 流程场景类型 + */ +public enum FlowSceneTypeEnum { + NORMAL, // 普通 + VI_WAKEUP_CORPUS, // 语音交互-唤醒场景 + VI_SINGLE_CORPUS, // 语音交互-单次对话场景 + VI_CONTINUOUS_CORPUS // 语音交互-连续对话场景 +} diff --git a/cmvr-iot-test/src/main/java/com/cmvr/test/flow/builder/FlowGraph.java b/cmvr-iot-test/src/main/java/com/cmvr/test/flow/builder/FlowGraph.java index ce4dfa3..ea3a24d 100644 --- a/cmvr-iot-test/src/main/java/com/cmvr/test/flow/builder/FlowGraph.java +++ b/cmvr-iot-test/src/main/java/com/cmvr/test/flow/builder/FlowGraph.java @@ -174,7 +174,6 @@ public class FlowGraph { * 获取开始节点对象 */ public FlowNodeWrapper getStartNode() { - System.out.println("这是什么线程:" + Thread.currentThread().getName()); String id = findStartNodeId(); return getNode(id); } diff --git a/cmvr-iot-test/src/main/java/com/cmvr/test/flow/context/TaskInstHolder.java b/cmvr-iot-test/src/main/java/com/cmvr/test/flow/context/TaskInstHolder.java index 0056c36..632137e 100644 --- a/cmvr-iot-test/src/main/java/com/cmvr/test/flow/context/TaskInstHolder.java +++ b/cmvr-iot-test/src/main/java/com/cmvr/test/flow/context/TaskInstHolder.java @@ -10,6 +10,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; +import java.util.List; + /** * 任务实例状态协调器:负责同步日志记录、上下文状态、数据库状态 */ @@ -40,8 +42,8 @@ public class TaskInstHolder { public void markRunning(String instId, String taskId, String itemId, String nodeId, String nodeType, String operate, - String action, String paramsIn) { - nodeInstService.logRunning(instId, taskId, itemId, nodeId, nodeType, operate, action, paramsIn); + String action, String paramsIn, List iterations) { + nodeInstService.logRunning(instId, taskId, itemId, nodeId, nodeType, operate, action, paramsIn, iterations); } public void markSuccess(String instId, String itemId, String nodeId, int pendingItemCount, String nodeType, diff --git a/cmvr-iot-test/src/main/java/com/cmvr/test/flow/control/FlowControlService.java b/cmvr-iot-test/src/main/java/com/cmvr/test/flow/control/FlowControlService.java index b597d2d..c12f004 100644 --- a/cmvr-iot-test/src/main/java/com/cmvr/test/flow/control/FlowControlService.java +++ b/cmvr-iot-test/src/main/java/com/cmvr/test/flow/control/FlowControlService.java @@ -1,5 +1,6 @@ package com.cmvr.test.flow.control; +import cn.hutool.core.bean.BeanUtil; import com.cmvr.common.exception.GlobalException; import com.cmvr.test.enums.NodeTypeEnum; import com.cmvr.test.enums.TaskStatusEnum; @@ -10,6 +11,7 @@ import com.cmvr.test.flow.context.TaskInstHolder; import com.cmvr.test.flow.context.TaskThreadRegistry; import com.cmvr.test.flow.runtime.engine.FlowItemExecutor; import com.cmvr.test.flow.runtime.message.TaskNodeExecuteContext; +import com.cmvr.test.flow.runtime.message.TaskNodeExecuteMessage; import com.cmvr.test.service.ITeNodeInstService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -17,6 +19,7 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Component; import javax.annotation.Resource; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.stream.Collectors; @@ -118,7 +121,7 @@ public class FlowControlService { nodeInstService.update(instId, nodeId, TaskStatusEnum.RUNNING); continue; } else { - nodeInstService.delete(instId, nodeId); + nodeInstService.delete(instId, nodeId, pendingNode.getIterations()); } if (graph.isSub()) { // 找到子图对应的loop节点 @@ -135,11 +138,21 @@ public class FlowControlService { // 子图中断点,先执行中断的子图节点 executor.execute(() -> { CountDownLatch latch = taskThreadRegistry.getLatch(instId, loopCtx.getNode().getNodeId()); + + // 恢复时构造新的 message,带上 loopNum 和 iterations + TaskNodeExecuteMessage resumeMessage = new TaskNodeExecuteMessage(); + BeanUtil.copyProperties(pendingNode.getRootMessage(), resumeMessage); + resumeMessage.setLoopNum(pendingNode.getLoopIteration()); // 保留中断时的循环次数 + resumeMessage.setIterations(new ArrayList<>(pendingNode.getIterations())); // 保留中断时的路径 + flowItemExecutor.executeNode( - pendingNode.getGraph(), pendingNode.getNode(), - pendingNode.getStartNodeId(), pendingNode.getStartInputDefs(), - pendingNode.getRootMessage(), latch::countDown, - pendingNode.getLoopIteration() + pendingNode.getGraph(), + pendingNode.getNode(), + pendingNode.getStartNodeId(), + pendingNode.getStartInputDefs(), + resumeMessage, + latch::countDown, + pendingNode.getIterations() ); try { @@ -152,11 +165,19 @@ public class FlowControlService { } else { // 普通节点直接恢复 executor.execute(() -> { + TaskNodeExecuteMessage resumeMessage = new TaskNodeExecuteMessage(); + BeanUtil.copyProperties(pendingNode.getRootMessage(), resumeMessage); + resumeMessage.setLoopNum(pendingNode.getLoopIteration()); // 保留循环次数 + resumeMessage.setIterations(new ArrayList<>(pendingNode.getIterations())); // 保留路径 + flowItemExecutor.executeNode( - pendingNode.getGraph(), pendingNode.getNode(), - pendingNode.getStartNodeId(), pendingNode.getStartInputDefs(), - pendingNode.getRootMessage(), pendingNode.getOnFinished(), - pendingNode.getLoopIteration() + pendingNode.getGraph(), + pendingNode.getNode(), + pendingNode.getStartNodeId(), + pendingNode.getStartInputDefs(), + resumeMessage, + pendingNode.getOnFinished(), + pendingNode.getIterations() ); }); } diff --git a/cmvr-iot-test/src/main/java/com/cmvr/test/flow/runtime/dispatcher/FlowLoopNodeHandler.java b/cmvr-iot-test/src/main/java/com/cmvr/test/flow/runtime/dispatcher/FlowLoopNodeHandler.java index 5bb9fcf..24e8838 100644 --- a/cmvr-iot-test/src/main/java/com/cmvr/test/flow/runtime/dispatcher/FlowLoopNodeHandler.java +++ b/cmvr-iot-test/src/main/java/com/cmvr/test/flow/runtime/dispatcher/FlowLoopNodeHandler.java @@ -1,5 +1,6 @@ package com.cmvr.test.flow.runtime.dispatcher; +import cn.hutool.core.bean.BeanUtil; import com.cmvr.common.utils.spring.SpringUtils; import com.cmvr.test.flow.builder.FlowGraph; import com.cmvr.test.flow.builder.FlowNodeWrapper; @@ -11,6 +12,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.CountDownLatch; @Slf4j @@ -25,7 +28,9 @@ public class FlowLoopNodeHandler implements FlowNodeTypeHandler { String instId = message.getInstId(); FlowGraph graph = message.getGraph(); // 当前从第几次循环开始 - int loopNum = message.getLoopNum() < 0 ? 1 : message.getLoopNum(); + int loopNum = Math.max(message.getLoopNum(), 1); + // 父级迭代路径 + List parentIterations = message.getIterations(); String nodeId = message.getNodeId(); FlowNodeWrapper nodeWrapper = graph.getNode(nodeId); @@ -50,7 +55,28 @@ public class FlowLoopNodeHandler implements FlowNodeTypeHandler { taskThreadRegistry.registerLatch(instId, nodeId, latch); FlowItemExecutor flowItemExecutor = SpringUtils.getBean(FlowItemExecutor.class); - flowItemExecutor.executeSubGraph(subGraph, message, latch::countDown, i); + + // 构造新的迭代路径 +// List newIterations = new ArrayList<>(parentIterations); +// newIterations.add(i); +// +// // 克隆 message,避免覆盖父级 +// TaskNodeExecuteMessage subMessage = new TaskNodeExecuteMessage(); +// BeanUtil.copyProperties(message, subMessage); +//// subMessage.setLoopNum(i); // 当前层循环次数 +// subMessage.setIterations(newIterations); // 完整迭代路径 + + // 构造新的迭代路径 + List newIterations = new ArrayList<>(parentIterations); + newIterations.add(i); + + // 克隆 message,并明确设置 loopNum + TaskNodeExecuteMessage subMessage = new TaskNodeExecuteMessage(); + BeanUtil.copyProperties(message, subMessage); +// subMessage.setLoopNum(i); // 当前 loop 第 i 次 + subMessage.setIterations(newIterations); // 完整路径 + + flowItemExecutor.executeSubGraph(subGraph, subMessage, latch::countDown, newIterations); try { latch.await(); // 等待子图执行完毕后再进行下一轮 diff --git a/cmvr-iot-test/src/main/java/com/cmvr/test/flow/runtime/engine/FlowItemExecutor.java b/cmvr-iot-test/src/main/java/com/cmvr/test/flow/runtime/engine/FlowItemExecutor.java index 2fd4838..b797aa3 100644 --- a/cmvr-iot-test/src/main/java/com/cmvr/test/flow/runtime/engine/FlowItemExecutor.java +++ b/cmvr-iot-test/src/main/java/com/cmvr/test/flow/runtime/engine/FlowItemExecutor.java @@ -53,12 +53,11 @@ public class FlowItemExecutor { String startNodeId = graph.findStartNodeId(); List startInputDefs = graph.getStartInputParamsDefs(); - - NodeExecutor executor = node -> - executeNode(graph, node, startNodeId, startInputDefs, rootMessage, onFinished, -1); - // 执行 start 节点(同步执行) - executeNode(graph, graph.getStartNode(), startNodeId, startInputDefs, rootMessage, onFinished, -1); + NodeExecutor executor = node -> + executeNode(graph, node, startNodeId, startInputDefs, rootMessage, onFinished, new ArrayList<>()); + + executeNode(graph, graph.getStartNode(), startNodeId, startInputDefs, rootMessage, onFinished, new ArrayList<>()); // 调度后续节点(并发) flowTaskScheduler.start(graph, taskInstHolder.getContext(rootMessage.getInstId()), executor, onFinished); @@ -70,7 +69,7 @@ public class FlowItemExecutor { public void executeSubGraph(FlowGraph subGraph, TaskNodeExecuteMessage rootMessage, Runnable onFinished, - int loopIteration) { + List iterations) { // 主图中开始节点的 inputParams List startInputDefs = subGraph.getStartInputParamsDefs(); @@ -81,15 +80,16 @@ public class FlowItemExecutor { NodeExecutor executor = node -> executeNode( subGraph, node, subStartNodeId, startInputDefs, - rootMessage, onFinished, loopIteration + rootMessage, onFinished, iterations ); // 执行子图起始节点 executeNode(subGraph, subStartNode, subStartNodeId, startInputDefs, - rootMessage, onFinished, loopIteration); + rootMessage, onFinished, iterations); // 启动子图调度 - flowTaskScheduler.subStart(subGraph, taskInstHolder.getContext(rootMessage.getInstId()), executor, onFinished, loopIteration); + flowTaskScheduler.subStart(subGraph, taskInstHolder.getContext(rootMessage.getInstId()), + executor, onFinished, iterations); } /** @@ -101,20 +101,22 @@ public class FlowItemExecutor { List startInputDefs, TaskNodeExecuteMessage rootMessage, Runnable onFinished, - int loopIteration) { + List iterations) { String instId = rootMessage.getInstId(); String itemId = rootMessage.getItemId(); String nodeId = node.getNodeId(); + log.info("当前执行的是 {} 节点,迭代路径={}", node.getNodeName(), iterations); //注册当前线程 - registerThread(graph, node, startNodeId, startInputDefs, rootMessage, onFinished, loopIteration, instId, itemId, nodeId); + registerThread(graph, node, startNodeId, startInputDefs, rootMessage, onFinished, iterations, instId, itemId, nodeId); + try { // 准备输入参数 JSONObject inputParams = paramPreparer.prepare(graph, node, startNodeId, startInputDefs, rootMessage); - // 创建带上下文的执行消息(关键:注入 graph) - TaskNodeExecuteMessage message = buildTaskNodeExecuteMsg(graph, node, rootMessage, nodeId, inputParams, loopIteration); + // 创建带上下文的执行消息(关键:注入 graph + iterations) + TaskNodeExecuteMessage message = buildTaskNodeExecuteMsg(graph, node, rootMessage, nodeId, inputParams, iterations); // 执行责任链 TaskNodeExecuteResult result = chainBuilder.build(interceptors, dispatchNodeHandler).apply(message); @@ -126,13 +128,14 @@ public class FlowItemExecutor { // 分支节点处理 if (node.getNodeType() == NodeTypeEnum.BRANCH) { - postHandleBranchNode(graph, node, startNodeId, startInputDefs, rootMessage, onFinished, loopIteration, nodeId, inputParams, message); + postHandleBranchNode(graph, node, startNodeId, startInputDefs, rootMessage, onFinished, iterations, nodeId, inputParams, message); return; } // END 节点 or 子图结束 if (node.getNodeType() == NodeTypeEnum.END || node.getNodeType() == NodeTypeEnum.SUB_END) { try { + log.info("{} 节点执行,释放线程", node.getNodeType()); onFinished.run(); } catch (Exception e) { log.error("onFinished 回调异常", e); @@ -140,22 +143,24 @@ public class FlowItemExecutor { } // 通知调度器继续调度后继节点 - flowTaskScheduler.markCompleted(message.getInstId(), nodeId, graph, n -> - executeNode(graph, n, startNodeId, startInputDefs, rootMessage, onFinished, loopIteration), loopIteration); + flowTaskScheduler.markCompleted( + message.getInstId(), nodeId, graph, + n -> executeNode(graph, n, startNodeId, startInputDefs, rootMessage, onFinished, iterations), + iterations + ); } finally { // 如果不是暂停全中断 TaskContext ctx = taskInstHolder.getContext(instId); if (ctx == null || !ctx.isPaused()) { taskThreadRegistry.unregisterNodeContext(instId, itemId, nodeId); - if (node.getNodeType().equals(NodeTypeEnum.SUB_START)) { - log.info("subStart注銷了"); - } } } } @NotNull - private static TaskNodeExecuteMessage buildTaskNodeExecuteMsg(FlowGraph graph, FlowNodeWrapper node, TaskNodeExecuteMessage rootMessage, String nodeId, JSONObject inputParams, int loopIteration) { + private static TaskNodeExecuteMessage buildTaskNodeExecuteMsg(FlowGraph graph, FlowNodeWrapper node, + TaskNodeExecuteMessage rootMessage, String nodeId, + JSONObject inputParams, List iterations) { TaskNodeExecuteMessage message = new TaskNodeExecuteMessage(); BeanUtil.copyProperties(rootMessage, message); message.setNodeId(nodeId); @@ -163,11 +168,18 @@ public class FlowItemExecutor { message.setAction(node.getAction()); message.setInputParams(inputParams); message.setGraph(graph); - message.setLoopNum(loopIteration); + // 只设置 iterations + message.setIterations(new ArrayList<>(iterations)); + + // loopNum 不在这里计算,而是由 FlowLoopNodeHandler / resume 显式写入 + // 如果 rootMessage 里已经带了 loopNum,就沿用它 + message.setLoopNum(rootMessage.getLoopNum()); return message; } - private void postHandleBranchNode(FlowGraph graph, FlowNodeWrapper node, String startNodeId, List startInputDefs, TaskNodeExecuteMessage rootMessage, Runnable onFinished, int loopIteration, String nodeId, JSONObject inputParams, TaskNodeExecuteMessage message) { + private void postHandleBranchNode(FlowGraph graph, FlowNodeWrapper node, String startNodeId, + List startInputDefs, TaskNodeExecuteMessage rootMessage, Runnable onFinished, + List iterations, String nodeId, JSONObject inputParams, TaskNodeExecuteMessage message) { String matchedAnchorId = FlowBranchEvaluator.evaluate(nodeId, node.getBranchConditions(), inputParams); FlowEdge matchedEdge = graph.getEdgeByFromAnchorId(matchedAnchorId); @@ -190,12 +202,13 @@ public class FlowItemExecutor { message.getInstId(), matchedEdge.getTo(), graph, - n -> executeNode(graph, n, startNodeId, startInputDefs, rootMessage, onFinished, loopIteration), - loopIteration + n -> executeNode(graph, n, startNodeId, startInputDefs, rootMessage, onFinished, iterations), + iterations ); } - private void registerThread(FlowGraph graph, FlowNodeWrapper node, String startNodeId, List startInputDefs, TaskNodeExecuteMessage rootMessage, Runnable onFinished, int loopIteration, String instId, String itemId, String nodeId) { + private void registerThread(FlowGraph graph, FlowNodeWrapper node, String startNodeId, List startInputDefs, + TaskNodeExecuteMessage rootMessage, Runnable onFinished, List iterations, String instId, String itemId, String nodeId) { TaskNodeExecuteContext nodeExecuteContext = new TaskNodeExecuteContext(); nodeExecuteContext.setThread(Thread.currentThread()); nodeExecuteContext.setGraph(graph); @@ -204,7 +217,8 @@ public class FlowItemExecutor { nodeExecuteContext.setNode(node); nodeExecuteContext.setOnFinished(onFinished); nodeExecuteContext.setRootMessage(rootMessage); - nodeExecuteContext.setLoopIteration(loopIteration); +// nodeExecuteContext.setLoopIteration(loopIteration); + nodeExecuteContext.setIterations(new ArrayList<>(iterations)); taskThreadRegistry.registerNodeContext(instId, itemId, nodeId, nodeExecuteContext); } diff --git a/cmvr-iot-test/src/main/java/com/cmvr/test/flow/runtime/engine/FlowTaskScheduler.java b/cmvr-iot-test/src/main/java/com/cmvr/test/flow/runtime/engine/FlowTaskScheduler.java index 32fe23c..56e5a6b 100644 --- a/cmvr-iot-test/src/main/java/com/cmvr/test/flow/runtime/engine/FlowTaskScheduler.java +++ b/cmvr-iot-test/src/main/java/com/cmvr/test/flow/runtime/engine/FlowTaskScheduler.java @@ -11,6 +11,7 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Component; import javax.annotation.Resource; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; @@ -32,7 +33,6 @@ public class FlowTaskScheduler { TaskContext context, FlowItemExecutor.NodeExecutor executorFunc, Runnable onFinished) { - String instId = context.getInstId(); String startNodeId = graph.findStartNodeId(); List nextNodes = graph.getNextNodes(startNodeId); @@ -44,7 +44,7 @@ public class FlowTaskScheduler { } for (String nextId : nextNodes) { - scheduleNode(instId, nextId, graph, executorFunc, -1); + scheduleNode(instId, nextId, graph, executorFunc, new ArrayList<>()); // 空 iterations } } @@ -55,8 +55,7 @@ public class FlowTaskScheduler { TaskContext context, FlowItemExecutor.NodeExecutor executorFunc, Runnable onFinished, - int loopIteration) { - + List iterations) { String instId = context.getInstId(); FlowNodeWrapper startNode = graph.findSubStartNodeId(); List nextNodes = graph.getNextNodes(startNode.getNodeId()); @@ -67,16 +66,15 @@ public class FlowTaskScheduler { return; } - // 并发调度后继节点 for (String nextId : nextNodes) { - scheduleNode(instId, nextId, graph, executorFunc, loopIteration); + scheduleNode(instId, nextId, graph, executorFunc, iterations); } } /** * 某节点执行完成后,判断并调度其所有后继节点 */ - public void markCompleted(String instId, +/* public void markCompleted(String instId, String nodeId, FlowGraph graph, FlowItemExecutor.NodeExecutor executorFunc, @@ -99,12 +97,38 @@ public class FlowTaskScheduler { scheduleNode(instId, nextId, graph, executorFunc, loopIteration); } } + }*/ + /** + * 某节点执行完成后,判断并调度其所有后继节点 + */ + public void markCompleted(String instId, + String nodeId, + FlowGraph graph, + FlowItemExecutor.NodeExecutor executorFunc, + List iterations) { + List nextNodes = graph.getNextNodes(nodeId); + + for (String nextId : nextNodes) { + String key = FlowSchedulerCache.buildKey(instId, nextId, iterations); + + // 使用 Set 来记录已完成的前驱节点 + Set completedPreNodes = schedulerCache.getOrInitCompletedPre(key); + // 添加当前节点作为已完成的前驱节点 + completedPreNodes.add(nodeId); + + int preCount = graph.getPredecessors().getOrDefault(nextId, Collections.emptyList()).size(); + + // 如果当前节点的所有前驱节点都已完成,则调度后继节点 + if (completedPreNodes.size() == preCount) { + scheduleNode(instId, nextId, graph, executorFunc, iterations); + } + } } /** * 提交节点执行任务(只调度一次) */ - private void scheduleNode(String instId, +/* private void scheduleNode(String instId, String nodeId, FlowGraph graph, FlowItemExecutor.NodeExecutor executorFunc, @@ -112,6 +136,31 @@ public class FlowTaskScheduler { String key = FlowSchedulerCache.buildKey(instId, nodeId, loopIteration); + // 如果已经调度过,则不再重复调度 + if (schedulerCache.trySchedule(key)) { + executor.submit(() -> { + Thread.currentThread().setName("node-thread-" + nodeId); + try { + executorFunc.execute(graph.getNode(nodeId)); + } catch (Exception e) { + if (ExceptionUtil.getRootCause(e) instanceof InterruptedException) { + return; + } + log.error("节点执行异常:instId={}, nodeId={}, err={}", instId, nodeId, e.getMessage(), e); + } + }); + } + }*/ + /** + * 提交节点执行任务(除循环只调度一次) + */ + private void scheduleNode(String instId, + String nodeId, + FlowGraph graph, + FlowItemExecutor.NodeExecutor executorFunc, + List iterations) { + String key = FlowSchedulerCache.buildKey(instId, nodeId, iterations); + // 如果已经调度过,则不再重复调度 if (schedulerCache.trySchedule(key)) { executor.submit(() -> { @@ -132,9 +181,9 @@ public class FlowTaskScheduler { String nodeId, FlowGraph graph, FlowItemExecutor.NodeExecutor executorFunc, - int loopIteration) { + List iterations) { - String key = FlowSchedulerCache.buildKey(instId, nodeId, loopIteration); + String key = FlowSchedulerCache.buildKey(instId, nodeId, iterations); if (schedulerCache.trySchedule(key)) { executor.submit(() -> { try { diff --git a/cmvr-iot-test/src/main/java/com/cmvr/test/flow/runtime/engine/support/FlowSchedulerCache.java b/cmvr-iot-test/src/main/java/com/cmvr/test/flow/runtime/engine/support/FlowSchedulerCache.java index 340f0fb..4b08217 100644 --- a/cmvr-iot-test/src/main/java/com/cmvr/test/flow/runtime/engine/support/FlowSchedulerCache.java +++ b/cmvr-iot-test/src/main/java/com/cmvr/test/flow/runtime/engine/support/FlowSchedulerCache.java @@ -3,6 +3,7 @@ package com.cmvr.test.flow.runtime.engine.support; import cn.hutool.core.util.StrUtil; import org.springframework.stereotype.Component; +import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -34,4 +35,12 @@ public class FlowSchedulerCache { ? StrUtil.format("{}_{}_{}", instId, nodeId, loopIteration) : StrUtil.format("{}_{}", instId, nodeId); } + + public static String buildKey(String instId, String nodeId, List iterations) { + StringBuilder sb = new StringBuilder(instId).append(":").append(nodeId); + for (int it : iterations) { + sb.append(":").append(it); + } + return sb.toString(); + } } diff --git a/cmvr-iot-test/src/main/java/com/cmvr/test/flow/runtime/interceptor/FlowLoggingInterceptor.java b/cmvr-iot-test/src/main/java/com/cmvr/test/flow/runtime/interceptor/FlowLoggingInterceptor.java index f71eb9a..f568a19 100644 --- a/cmvr-iot-test/src/main/java/com/cmvr/test/flow/runtime/interceptor/FlowLoggingInterceptor.java +++ b/cmvr-iot-test/src/main/java/com/cmvr/test/flow/runtime/interceptor/FlowLoggingInterceptor.java @@ -30,7 +30,8 @@ public class FlowLoggingInterceptor implements FlowMsgPreInterceptor { // todo 判断一下是否为LOOP重入 if (!(message.getNodeType().equalsIgnoreCase(NodeTypeEnum.LOOP.getCode()) && message.getLoopNum() > 0)) { taskInstHolder.markRunning(message.getInstId(), message.getTaskId(), message.getItemId(), message.getNodeId(), - message.getNodeType(), message.getAction().getOperate(), message.getAction().getAction(), message.getInputParams().toJSONString()); + message.getNodeType(), message.getAction().getOperate(), message.getAction().getAction(), + message.getInputParams().toJSONString(), message.getIterations()); } TaskNodeExecuteResult result = next.apply(message); @@ -43,7 +44,7 @@ public class FlowLoggingInterceptor implements FlowMsgPreInterceptor { return null; } - taskInstHolder.markSuccess(message.getInstId(), message.getItemId(), message.getNodeId(), message.getPendingItemCount(),message.getNodeType(), + taskInstHolder.markSuccess(message.getInstId(), message.getItemId(), message.getNodeId(), message.getPendingItemCount(), message.getNodeType(), result.getOutputParams().toJSONString(), StrUtil.format("[{}] 执行成功", message.getAction())); long end = System.currentTimeMillis(); diff --git a/cmvr-iot-test/src/main/java/com/cmvr/test/flow/runtime/message/TaskNodeExecuteContext.java b/cmvr-iot-test/src/main/java/com/cmvr/test/flow/runtime/message/TaskNodeExecuteContext.java index 0e56a6c..5c2f57c 100644 --- a/cmvr-iot-test/src/main/java/com/cmvr/test/flow/runtime/message/TaskNodeExecuteContext.java +++ b/cmvr-iot-test/src/main/java/com/cmvr/test/flow/runtime/message/TaskNodeExecuteContext.java @@ -5,6 +5,7 @@ import com.cmvr.test.flow.builder.FlowNodeWrapper; import com.cmvr.test.flow.builder.FlowParamDef; import lombok.Data; +import java.util.ArrayList; import java.util.List; /** @@ -21,4 +22,5 @@ public class TaskNodeExecuteContext { private TaskNodeExecuteMessage rootMessage; private Runnable onFinished; private int loopIteration; + private List iterations = new ArrayList<>(); } diff --git a/cmvr-iot-test/src/main/java/com/cmvr/test/flow/runtime/message/TaskNodeExecuteMessage.java b/cmvr-iot-test/src/main/java/com/cmvr/test/flow/runtime/message/TaskNodeExecuteMessage.java index 5d61754..1d81d69 100644 --- a/cmvr-iot-test/src/main/java/com/cmvr/test/flow/runtime/message/TaskNodeExecuteMessage.java +++ b/cmvr-iot-test/src/main/java/com/cmvr/test/flow/runtime/message/TaskNodeExecuteMessage.java @@ -5,25 +5,38 @@ import com.cmvr.test.enums.ActionEnum; import com.cmvr.test.flow.builder.FlowGraph; import lombok.Data; +import java.util.ArrayList; +import java.util.List; + /** * 单个节点的执行输入消息 */ @Data public class TaskNodeExecuteMessage { - /** 实例ID */ + /** + * 实例ID + */ private String instId; - /** 任务ID */ + /** + * 任务ID + */ private String taskId; - /** 检测项ID(用于定位流程) */ + /** + * 检测项ID(用于定位流程) + */ private String itemId; - /** 节点ID */ + /** + * 节点ID + */ private String nodeId; - /** 待执行检测项数 */ + /** + * 待执行检测项数 + */ private int pendingItemCount; /** @@ -37,25 +50,44 @@ public class TaskNodeExecuteMessage { */ private transient int loopNum = 0; - /** 节点名称 */ + /** + * 全局迭代路径(嵌套循环时用) + */ + private List iterations = new ArrayList<>(); + + /** + * 节点名称 + */ private String nodeName; - /** 节点类型(START、FUNCTION、END 等) */ + /** + * 节点类型(START、FUNCTION、END 等) + */ private String nodeType; - /** 节点行为(动作标识符) */ + /** + * 节点行为(动作标识符) + */ private ActionEnum action; - /** 是否试运行 */ + /** + * 是否试运行 + */ private boolean trial; - /** 所属终端ID */ + /** + * 所属终端ID + */ private String terminalId; - /** 节点入参 */ + /** + * 节点入参 + */ private JSONObject inputParams; - /** 上游输出参数(来自上一个节点的输出) */ + /** + * 上游输出参数(来自上一个节点的输出) + */ private JSONObject upStreamOutput; } diff --git a/cmvr-iot-test/src/main/java/com/cmvr/test/flow/runtime/operator/edge/EdgeCameraOperateService.java b/cmvr-iot-test/src/main/java/com/cmvr/test/flow/runtime/operator/edge/EdgeCameraOperateService.java index 09b6964..fb3f3d1 100644 --- a/cmvr-iot-test/src/main/java/com/cmvr/test/flow/runtime/operator/edge/EdgeCameraOperateService.java +++ b/cmvr-iot-test/src/main/java/com/cmvr/test/flow/runtime/operator/edge/EdgeCameraOperateService.java @@ -64,8 +64,8 @@ public class EdgeCameraOperateService implements EdgeOperateService { // // 驾驶偏好 // imageUrl = "http://192.168.1.100:9000/cmvr-iot/FILE/20250822/1755849654731.jpg"; // } -// String imageUrl = "http://192.168.1.100:9000/cmvr-iot/IMAGE/20250901/1756718594725.jpg"; - String imageUrl = edgeCameraService.getRGBDImages(terminalId, deviceId); + String imageUrl = "http://192.168.1.100:9000/cmvr-iot/IMAGE/20250901/1756718594725.jpg"; +// String imageUrl = edgeCameraService.getRGBDImages(terminalId, deviceId); JSONArray imageUrls = new JSONArray(); if (ObjUtil.isNotEmpty(upstreamOutput)) { diff --git a/cmvr-iot-test/src/main/java/com/cmvr/test/model/domain/TeDetectionItem.java b/cmvr-iot-test/src/main/java/com/cmvr/test/model/domain/TeDetectionItem.java index 37193cd..df50c0c 100644 --- a/cmvr-iot-test/src/main/java/com/cmvr/test/model/domain/TeDetectionItem.java +++ b/cmvr-iot-test/src/main/java/com/cmvr/test/model/domain/TeDetectionItem.java @@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.cmvr.common.annotation.Excel; import com.cmvr.common.core.domain.BaseEntity; +import com.cmvr.test.enums.LoopNodeTypeEnum; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; @@ -49,4 +50,10 @@ public class TeDetectionItem extends BaseEntity { @ApiModelProperty("检测项参数配置(JSON)") private String config; + + /** + * #{@link LoopNodeTypeEnum} + */ + @ApiModelProperty("场景标识,默认 NORMAL") + private String sceneCode; } diff --git a/cmvr-iot-test/src/main/java/com/cmvr/test/model/domain/TeNodeInst.java b/cmvr-iot-test/src/main/java/com/cmvr/test/model/domain/TeNodeInst.java index 1be4ad1..7b8034b 100644 --- a/cmvr-iot-test/src/main/java/com/cmvr/test/model/domain/TeNodeInst.java +++ b/cmvr-iot-test/src/main/java/com/cmvr/test/model/domain/TeNodeInst.java @@ -47,6 +47,9 @@ public class TeNodeInst { @ApiModelProperty("执行日志/备注") private String message; + @ApiModelProperty("循环节点的层级") + private String iteration; + @ApiModelProperty("开始时间戳(ms)") private Long startTime; diff --git a/cmvr-iot-test/src/main/java/com/cmvr/test/service/ITeDetectionItemService.java b/cmvr-iot-test/src/main/java/com/cmvr/test/service/ITeDetectionItemService.java index fd03c85..0d7243a 100644 --- a/cmvr-iot-test/src/main/java/com/cmvr/test/service/ITeDetectionItemService.java +++ b/cmvr-iot-test/src/main/java/com/cmvr/test/service/ITeDetectionItemService.java @@ -70,4 +70,12 @@ public interface ITeDetectionItemService extends IService { * @return 结果 */ public int deleteTeDetectionItemById(String id); + + /** + * 根据场景类型查询检测项 + * + * @param sceneType 场景类型 + * @return 结果 + */ + public TeDetectionItem selectTeDetectionItemBySceneType(String sceneType); } diff --git a/cmvr-iot-test/src/main/java/com/cmvr/test/service/ITeNodeInstService.java b/cmvr-iot-test/src/main/java/com/cmvr/test/service/ITeNodeInstService.java index 3117b0c..5ac529a 100644 --- a/cmvr-iot-test/src/main/java/com/cmvr/test/service/ITeNodeInstService.java +++ b/cmvr-iot-test/src/main/java/com/cmvr/test/service/ITeNodeInstService.java @@ -23,18 +23,19 @@ public interface ITeNodeInstService extends IService { /** * 记录节点进入运行状态 * - * @param instId 任务实例 ID - * @param taskId 任务定义 ID - * @param itemId 检测项 ID - * @param nodeId 节点 ID - * @param nodeType 节点类型 - * @param operate 执行来源 - * @param action 动作类型 - * @param paramsIn 输入参数(已序列化为 JSON 字符串) + * @param instId 任务实例 ID + * @param taskId 任务定义 ID + * @param itemId 检测项 ID + * @param nodeId 节点 ID + * @param nodeType 节点类型 + * @param operate 执行来源 + * @param action 动作类型 + * @param paramsIn 输入参数(已序列化为 JSON 字符串) + * @param iterations 迭代次数 */ void logRunning(String instId, String taskId, String itemId, String nodeId, String nodeType, - String operate, String action, String paramsIn); + String operate, String action, String paramsIn, List iterations); /** * 节点执行后更新输出参数等信息/记录节点成功完成 @@ -52,7 +53,7 @@ public interface ITeNodeInstService extends IService { /** * 删除节点 */ - void delete(String instId, String nodeId); + void delete(String instId, String nodeId, List iterations); /** * 更新节点 diff --git a/cmvr-iot-test/src/main/java/com/cmvr/test/service/impl/ITeNodeInstServiceImpl.java b/cmvr-iot-test/src/main/java/com/cmvr/test/service/impl/ITeNodeInstServiceImpl.java index f88e52b..28c8d2d 100644 --- a/cmvr-iot-test/src/main/java/com/cmvr/test/service/impl/ITeNodeInstServiceImpl.java +++ b/cmvr-iot-test/src/main/java/com/cmvr/test/service/impl/ITeNodeInstServiceImpl.java @@ -12,6 +12,7 @@ import com.cmvr.test.model.vo.TeFlowViewVO; import com.cmvr.test.service.ITeNodeInstService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.Nullable; import org.springframework.stereotype.Service; import java.util.List; @@ -33,7 +34,7 @@ public class ITeNodeInstServiceImpl extends ServiceImpl iterations) { TeNodeInst record = new TeNodeInst(); record.setInstId(instId); @@ -47,6 +48,7 @@ public class ITeNodeInstServiceImpl extends ServiceImpl iterations) { + String iteration = getIteration(iterations); LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); wrapper.eq(TeNodeInst::getInstId, instId) - .eq(TeNodeInst::getNodeId, nodeId); + .eq(TeNodeInst::getNodeId, nodeId) + .eq(TeNodeInst::getIteration, iteration); this.baseMapper.delete(wrapper); } + @Nullable + private static String getIteration(List iterations) { + return StrUtil.join(StrUtil.DASHED, iterations); + } + @Override public void update(String instId, String nodeId, TaskStatusEnum nodeStatus) { LambdaUpdateWrapper wrapper = Wrappers.lambdaUpdate(); wrapper.eq(TeNodeInst::getInstId, instId) .eq(TeNodeInst::getNodeId, nodeId) - .set(TeNodeInst::getStatus,nodeStatus.name()); + .set(TeNodeInst::getStatus, nodeStatus.name()); this.update(wrapper); } } diff --git a/cmvr-iot-test/src/main/java/com/cmvr/test/service/impl/TeDetectionItemServiceImpl.java b/cmvr-iot-test/src/main/java/com/cmvr/test/service/impl/TeDetectionItemServiceImpl.java index 8ae3077..3369767 100644 --- a/cmvr-iot-test/src/main/java/com/cmvr/test/service/impl/TeDetectionItemServiceImpl.java +++ b/cmvr-iot-test/src/main/java/com/cmvr/test/service/impl/TeDetectionItemServiceImpl.java @@ -1,12 +1,13 @@ package com.cmvr.test.service.impl; +import cn.hutool.core.util.StrUtil; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.cmvr.common.core.domain.entity.SysGroup; -import com.cmvr.common.utils.StringUtils; import com.cmvr.test.enums.NodeTypeEnum; import com.cmvr.test.mapper.TeDetectionItemMapper; import com.cmvr.test.model.domain.TeDetectionItem; @@ -56,11 +57,13 @@ public class TeDetectionItemServiceImpl extends ServiceImpl queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(TeDetectionItem::getSceneCode, sceneType); + return this.getOne(queryWrapper); + } } diff --git a/cmvr-iot-vi/pom.xml b/cmvr-iot-vi/pom.xml index e5c2a34..b87c779 100644 --- a/cmvr-iot-vi/pom.xml +++ b/cmvr-iot-vi/pom.xml @@ -23,6 +23,12 @@ cmvr-iot-common + + + com.cmvr + cmvr-iot-test + + \ No newline at end of file diff --git a/cmvr-iot-vi/src/main/java/com/cmvr/vi/enums/ViCorpusEnum.java b/cmvr-iot-vi/src/main/java/com/cmvr/vi/enums/ViCorpusEnum.java new file mode 100644 index 0000000..1142300 --- /dev/null +++ b/cmvr-iot-vi/src/main/java/com/cmvr/vi/enums/ViCorpusEnum.java @@ -0,0 +1,6 @@ +package com.cmvr.vi.enums; + +public enum ViCorpusEnum { + WAKEUP, //唤醒 + TEST //测试 +} diff --git a/cmvr-iot-vi/src/main/java/com/cmvr/vi/mapper/ViProjectMapper.java b/cmvr-iot-vi/src/main/java/com/cmvr/vi/mapper/ViProjectMapper.java new file mode 100644 index 0000000..6b7d5d8 --- /dev/null +++ b/cmvr-iot-vi/src/main/java/com/cmvr/vi/mapper/ViProjectMapper.java @@ -0,0 +1,12 @@ +package com.cmvr.vi.mapper; + +import com.cmvr.vi.model.domain.ViProject; +import com.github.yulichang.base.MPJBaseMapper; + +/** + * 语音交互-项目Mapper接口 + * + * @author cmvr-iot + */ +public interface ViProjectMapper extends MPJBaseMapper { +} diff --git a/cmvr-iot-vi/src/main/java/com/cmvr/vi/mapper/ViSchemeMapper.java b/cmvr-iot-vi/src/main/java/com/cmvr/vi/mapper/ViSchemeMapper.java new file mode 100644 index 0000000..aa14a7a --- /dev/null +++ b/cmvr-iot-vi/src/main/java/com/cmvr/vi/mapper/ViSchemeMapper.java @@ -0,0 +1,12 @@ +package com.cmvr.vi.mapper; + +import com.cmvr.vi.model.domain.ViScheme; +import com.github.yulichang.base.MPJBaseMapper; + +/** + * 语音交互-方案Mapper接口 + * + * @author cmvr-iot + */ +public interface ViSchemeMapper extends MPJBaseMapper { +} diff --git a/cmvr-iot-vi/src/main/java/com/cmvr/vi/model/domain/ViCorpus.java b/cmvr-iot-vi/src/main/java/com/cmvr/vi/model/domain/ViCorpus.java index 40d5355..0af674e 100644 --- a/cmvr-iot-vi/src/main/java/com/cmvr/vi/model/domain/ViCorpus.java +++ b/cmvr-iot-vi/src/main/java/com/cmvr/vi/model/domain/ViCorpus.java @@ -8,83 +8,55 @@ import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; -/** - * 语料库对象 vi_corpus - * - * @author cmvr-iot - */ @Data -@ApiModel("语料库对象") +@ApiModel("语音交互--语料库") public class ViCorpus extends BaseEntity { private static final long serialVersionUID = 1L; - /** - * 语料ID - */ @ApiModelProperty("语料ID") @TableId(type = IdType.AUTO) private Long corpusId; - /** - * 语料名称 - */ + @ApiModelProperty("测试语料-连续对话的父ID") + private String parentId; + @Excel(name = "语料名称") @ApiModelProperty("语料名称") private String corpusName; - /** - * 语料类型(WAKEUP唤醒 / TEST测试) - */ @Excel(name = "语料类型", readConverterExp = "WAKEUP唤醒,TEST测试") @ApiModelProperty("语料类型") private String type; - /** - * 语音对应的文本 - */ @Excel(name = "语音对应的文本") @ApiModelProperty("语音对应的文本") private String textContent; - /** - * 语音文件存放路径 - */ @Excel(name = "语音文件存放路径") @ApiModelProperty("语音文件存放路径") private String audioPath; - /** - * 音色 - */ @Excel(name = "音色") @ApiModelProperty("音色") private String voiceType; - /** - * 方言 - */ @Excel(name = "方言") @ApiModelProperty("方言") private String dialect; - /** - * 预期结果(预留) - */ -// @Excel(name = "预期结果(预留)") + // @Excel(name = "预期结果(预留)") @ApiModelProperty("预期结果(预留)") private String expectedResult; - /** - * 同一父语料下的顺序 - */ @Excel(name = "同一父语料下的顺序") @ApiModelProperty("同一父语料下的顺序") private Integer sortOrder; - /** - * 状态(0正常 1停用) - */ + @Excel(name = "是否连续(0连续 1单次)") + @ApiModelProperty("是否连续(0连续 1单次),测试语料时使用") + private Integer continuous = 1; + @Excel(name = "状态", readConverterExp = "0=正常,1=停用") @ApiModelProperty(value = "状态", notes = "0=正常,1=停用") private String status; diff --git a/cmvr-iot-vi/src/main/java/com/cmvr/vi/model/domain/ViProject.java b/cmvr-iot-vi/src/main/java/com/cmvr/vi/model/domain/ViProject.java new file mode 100644 index 0000000..a82dd9f --- /dev/null +++ b/cmvr-iot-vi/src/main/java/com/cmvr/vi/model/domain/ViProject.java @@ -0,0 +1,78 @@ +package com.cmvr.vi.model.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.cmvr.common.annotation.Excel; +import com.cmvr.common.core.domain.BaseEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + +@Data +@ApiModel("语音交互--项目表") +public class ViProject extends BaseEntity { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty("项目ID") + @TableId(type = IdType.ASSIGN_UUID) + private String projectId; + + @Excel(name = "项目名称") + @ApiModelProperty("项目名称") + private String projectName; + + @Excel(name = "测试人员") + @ApiModelProperty("测试人员") + private String tester; + + @Excel(name = "计划测试开始时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") + @ApiModelProperty("计划测试开始时间") + private Date planStart; + + @Excel(name = "计划测试结束时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss") + @ApiModelProperty("计划测试结束时间") + private Date planEnd; + + @Excel(name = "项目描述") + @ApiModelProperty("项目描述") + private String description; + + @Excel(name = "样品名称") + @ApiModelProperty("样品名称") + private String sampleName; + + @Excel(name = "商标") + @ApiModelProperty("商标") + private String trademark; + + @Excel(name = "型号规格") + @ApiModelProperty("型号规格") + private String modelSpec; + + @Excel(name = "数量") + @ApiModelProperty("数量") + private Integer quantity; + + @Excel(name = "委托单位") + @ApiModelProperty("委托单位") + private String entrustUnit; + + @Excel(name = "生产单位") + @ApiModelProperty("生产单位") + private String productionUnit; + + @Excel(name = "生产日期", width = 30, dateFormat = "yyyy-MM-dd") + @ApiModelProperty("生产日期") + private Date productionDate; + + @Excel(name = "送样日期", width = 30, dateFormat = "yyyy-MM-dd") + @ApiModelProperty("送样日期") + private Date sampleDate; + + @Excel(name = "状态", readConverterExp = "0=正常,1=停用") + @ApiModelProperty(value = "状态", notes = "0=正常,1=停用") + private String status; +} diff --git a/cmvr-iot-vi/src/main/java/com/cmvr/vi/model/domain/ViScheme.java b/cmvr-iot-vi/src/main/java/com/cmvr/vi/model/domain/ViScheme.java new file mode 100644 index 0000000..10afd0c --- /dev/null +++ b/cmvr-iot-vi/src/main/java/com/cmvr/vi/model/domain/ViScheme.java @@ -0,0 +1,45 @@ +package com.cmvr.vi.model.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.cmvr.common.annotation.Excel; +import com.cmvr.common.core.domain.BaseEntity; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel("语音交互--方案表") +public class ViScheme extends BaseEntity { + + private static final long serialVersionUID = 1L; + + @ApiModelProperty("方案ID") + @TableId(type = IdType.ASSIGN_UUID) + private String schemeId; + + @Excel(name = "所属项目ID") + @ApiModelProperty("所属项目ID") + private String projectId; + + @Excel(name = "方案名称") + @ApiModelProperty("方案名称") + private String schemeName; + + @ApiModelProperty("场景类型 1:唤醒场景 2: 单次对话场景 3:连续对话场景") + private Integer sceneType; + + @ApiModelProperty("唤醒语料parent_id") + private String wakeId; + + @ApiModelProperty("排序") + private Integer sort; + + @Excel(name = "语料ID列表") + @ApiModelProperty(value = "语料parent_id列表,逗号分隔",notes = "单次和唤醒的parent_id和id一致,连续语料的parent_id下有多条子语料") + private String corpusIds; + + @Excel(name = "状态", readConverterExp = "0=正常,1=停用") + @ApiModelProperty(value = "状态", notes = "0=正常,1=停用") + private String status; +} diff --git a/cmvr-iot-vi/src/main/java/com/cmvr/vi/model/vo/ViContinuousCorpusVO.java b/cmvr-iot-vi/src/main/java/com/cmvr/vi/model/vo/ViContinuousCorpusVO.java new file mode 100644 index 0000000..54c3ab1 --- /dev/null +++ b/cmvr-iot-vi/src/main/java/com/cmvr/vi/model/vo/ViContinuousCorpusVO.java @@ -0,0 +1,28 @@ +package com.cmvr.vi.model.vo; + +import com.cmvr.vi.model.domain.ViCorpus; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +@Data +@ApiModel("连续对话分组VO") +public class ViContinuousCorpusVO { + + @ApiModelProperty("测试语料-连续对话的父ID") + private String parentId; + + @ApiModelProperty("语料名称") + private String corpusName; + + @ApiModelProperty("音色") + private String voiceType; + + @ApiModelProperty("方言") + private String dialect; + + @ApiModelProperty("子语料列表") + private List children; +} diff --git a/cmvr-iot-vi/src/main/java/com/cmvr/vi/model/vo/ViCorpusVO.java b/cmvr-iot-vi/src/main/java/com/cmvr/vi/model/vo/ViCorpusVO.java new file mode 100644 index 0000000..48ff4e4 --- /dev/null +++ b/cmvr-iot-vi/src/main/java/com/cmvr/vi/model/vo/ViCorpusVO.java @@ -0,0 +1,38 @@ +package com.cmvr.vi.model.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotEmpty; +import java.util.List; + +@Data +@ApiModel("查询单次对话语料/唤醒语料VO") +public class ViCorpusVO { + + @ApiModelProperty("测试语料-连续对话的父ID") + private String parentId; + + @ApiModelProperty("测试语料-连续对话的父ID列表") + private List parentIds; + + @ApiModelProperty("语料名称") + private String corpusName; + + @ApiModelProperty("音色") + private String voiceType; + + @ApiModelProperty("方言") + private String dialect; + + @ApiModelProperty("语音对应的文本") + private String textContent; + + @ApiModelProperty(value = "状态", notes = "0=正常,1=停用") + private String status; + + @NotEmpty(message = "语料类型不能为空") + @ApiModelProperty(value = "语料类型", required = true, notes = "WAKEUP 唤醒,TEST 单次对话测试") + private String type; +} diff --git a/cmvr-iot-vi/src/main/java/com/cmvr/vi/service/IViCorpusService.java b/cmvr-iot-vi/src/main/java/com/cmvr/vi/service/IViCorpusService.java index cf76bf9..884a27a 100644 --- a/cmvr-iot-vi/src/main/java/com/cmvr/vi/service/IViCorpusService.java +++ b/cmvr-iot-vi/src/main/java/com/cmvr/vi/service/IViCorpusService.java @@ -2,6 +2,8 @@ package com.cmvr.vi.service; import com.baomidou.mybatisplus.extension.service.IService; import com.cmvr.vi.model.domain.ViCorpus; +import com.cmvr.vi.model.vo.ViContinuousCorpusVO; +import com.cmvr.vi.model.vo.ViCorpusVO; import java.util.List; @@ -22,10 +24,18 @@ public interface IViCorpusService extends IService { /** * 查询语料库列表 * - * @param viCorpus 语料库 + * @param viCorpusVO 语料库 * @return 语料库集合 */ - public List selectViCorpusList(ViCorpus viCorpus); + public List selectViCorpusList(ViCorpusVO viCorpusVO); + + /** + * 查询连续对话列表 + * + * @param viCorpusVO 语料库 + * @return 语料库集合 + */ + public List selectContinuousList(ViCorpusVO viCorpusVO); /** * 新增语料库 @@ -35,6 +45,14 @@ public interface IViCorpusService extends IService { */ public int insertViCorpus(ViCorpus viCorpus); + /** + * 批量新增语料库 + * + * @param viCorpus 语料库 + * @return 结果 + */ + public boolean insertBatch(List viCorpus); + /** * 修改语料库 * @@ -51,4 +69,20 @@ public interface IViCorpusService extends IService { */ public int deleteViCorpusByCorpusIds(Long[] corpusIds); + /** + * 批量新增/修改语料库 + */ + int insertOrUpdateBatch(List data); + + /** + * 新增连续对话 + */ + boolean insertContinuousViCorpus(ViContinuousCorpusVO viContinuousCorpusVO); + + /** + * 修改连续对话 + */ + int updateContinuousViCorpus(ViContinuousCorpusVO viContinuousCorpusVO); + + int deleteViCorpusByParentIds(String[] parentIds); } diff --git a/cmvr-iot-vi/src/main/java/com/cmvr/vi/service/IViProjectService.java b/cmvr-iot-vi/src/main/java/com/cmvr/vi/service/IViProjectService.java new file mode 100644 index 0000000..4c371bd --- /dev/null +++ b/cmvr-iot-vi/src/main/java/com/cmvr/vi/service/IViProjectService.java @@ -0,0 +1,58 @@ +package com.cmvr.vi.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.cmvr.vi.model.domain.ViProject; + +import java.util.List; + +/** + * 语音交互-项目Service接口 + * + * @author cmvr-iot + */ +public interface IViProjectService extends IService { + /** + * 查询语音交互-项目 + * + * @param projectId 语音交互-项目主键 + * @return 语音交互-项目 + */ + public ViProject selectViProjectByProjectId(String projectId); + + /** + * 查询语音交互-项目列表 + * + * @param viProject 语音交互-项目 + * @return 语音交互-项目集合 + */ + public List selectViProjectList(ViProject viProject); + + /** + * 新增语音交互-项目 + * + * @param viProject 语音交互-项目 + * @return 结果 + */ + public int insertViProject(ViProject viProject); + + /** + * 修改语音交互-项目 + * + * @param viProject 语音交互-项目 + * @return 结果 + */ + public int updateViProject(ViProject viProject); + + /** + * 批量删除语音交互-项目 + * + * @param projectIds 需要删除的语音交互-项目主键集合 + * @return 结果 + */ + public int deleteViProjectByProjectIds(String[] projectIds); + + /** + * 执行项目 + */ + public String execute(String projectId); +} diff --git a/cmvr-iot-vi/src/main/java/com/cmvr/vi/service/IViSchemeService.java b/cmvr-iot-vi/src/main/java/com/cmvr/vi/service/IViSchemeService.java new file mode 100644 index 0000000..e6fdfda --- /dev/null +++ b/cmvr-iot-vi/src/main/java/com/cmvr/vi/service/IViSchemeService.java @@ -0,0 +1,53 @@ +package com.cmvr.vi.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.cmvr.vi.model.domain.ViScheme; + +import java.util.List; + +/** + * 语音交互-方案Service接口 + * + * @author cmvr-iot + */ +public interface IViSchemeService extends IService { + /** + * 查询语音交互-方案 + * + * @param schemeId 语音交互-方案主键 + * @return 语音交互-方案 + */ + public ViScheme selectViSchemeBySchemeId(String schemeId); + + /** + * 查询语音交互-方案列表 + * + * @param viScheme 语音交互-方案 + * @return 语音交互-方案集合 + */ + public List selectViSchemeList(ViScheme viScheme); + + /** + * 新增语音交互-方案 + * + * @param viScheme 语音交互-方案 + * @return 结果 + */ + public int insertViScheme(ViScheme viScheme); + + /** + * 修改语音交互-方案 + * + * @param viScheme 语音交互-方案 + * @return 结果 + */ + public int updateViScheme(ViScheme viScheme); + + /** + * 批量删除语音交互-方案 + * + * @param schemeIds 需要删除的语音交互-方案主键集合 + * @return 结果 + */ + public int deleteViSchemeBySchemeIds(String[] schemeIds); +} diff --git a/cmvr-iot-vi/src/main/java/com/cmvr/vi/service/impl/IViCorpusServiceImpl.java b/cmvr-iot-vi/src/main/java/com/cmvr/vi/service/impl/IViCorpusServiceImpl.java index 3726bdb..d7d0620 100644 --- a/cmvr-iot-vi/src/main/java/com/cmvr/vi/service/impl/IViCorpusServiceImpl.java +++ b/cmvr-iot-vi/src/main/java/com/cmvr/vi/service/impl/IViCorpusServiceImpl.java @@ -1,15 +1,27 @@ package com.cmvr.vi.service.impl; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.cmvr.common.utils.uuid.IdUtils; +import com.cmvr.vi.enums.ViCorpusEnum; import com.cmvr.vi.mapper.ViCorpusMapper; import com.cmvr.vi.model.domain.ViCorpus; +import com.cmvr.vi.model.vo.ViContinuousCorpusVO; +import com.cmvr.vi.model.vo.ViCorpusVO; import com.cmvr.vi.service.IViCorpusService; +import com.github.yulichang.wrapper.MPJLambdaWrapper; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; /** * 语料库Service业务层处理 @@ -19,61 +31,193 @@ import java.util.List; @Service public class IViCorpusServiceImpl extends ServiceImpl implements IViCorpusService { - /** - * 查询语料库 - * - * @param corpusId 语料库主键 - * @return 语料库 - */ @Override public ViCorpus selectViCorpusByCorpusId(Long corpusId) { return this.getById(corpusId); } - /** - * 查询语料库列表 - * - * @param viCorpus 语料库 - * @return 语料库 - */ @Override - public List selectViCorpusList(ViCorpus viCorpus) { + public List selectViCorpusList(ViCorpusVO viCorpusVO) { LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); - wrapper.eq(ViCorpus::getType, viCorpus.getType()); + + // 如果是唤醒语料 + if (ViCorpusEnum.WAKEUP.name().equalsIgnoreCase(viCorpusVO.getType())) { + wrapper.eq(ViCorpus::getType, ViCorpusEnum.WAKEUP); + } + + // 如果是单次对话 + if (ViCorpusEnum.TEST.name().equalsIgnoreCase(viCorpusVO.getType())) { + wrapper.eq(ViCorpus::getType, ViCorpusEnum.TEST) + .eq(ViCorpus::getContinuous, 1); // 只查单次 + } + + // 公共条件 + wrapper.like(ObjUtil.isNotEmpty(viCorpusVO.getVoiceType()), ViCorpus::getVoiceType, viCorpusVO.getVoiceType()) + .like(ObjUtil.isNotEmpty(viCorpusVO.getDialect()), ViCorpus::getDialect, viCorpusVO.getDialect()) + .like(ObjUtil.isNotEmpty(viCorpusVO.getCorpusName()), ViCorpus::getCorpusName, viCorpusVO.getCorpusName()) + .like(ObjUtil.isNotEmpty(viCorpusVO.getTextContent()), ViCorpus::getTextContent, viCorpusVO.getTextContent()) + .eq(ObjUtil.isNotEmpty(viCorpusVO.getStatus()), ViCorpus::getStatus, viCorpusVO.getStatus()) + .in(CollUtil.isNotEmpty(viCorpusVO.getParentIds()), ViCorpus::getParentId, viCorpusVO.getParentIds()) + .orderByAsc(ViCorpus::getSortOrder) + .orderByDesc(ViCorpus::getUpdateTime); + return this.list(wrapper); } - /** - * 新增语料库 - * - * @param viCorpus 语料库 - * @return 结果 - */ + + @Override + public List selectContinuousList(ViCorpusVO viCorpusVO) { + // 查询父级 + MPJLambdaWrapper wrapper = new MPJLambdaWrapper<>(); + wrapper.select(ViCorpus::getParentId) + .select(ViCorpus::getCorpusName) + .select(ViCorpus::getVoiceType) + .select(ViCorpus::getDialect) + .eq(ViCorpus::getType, ViCorpusEnum.TEST) + .eq(ViCorpus::getContinuous, 0) + .like(ObjUtil.isNotEmpty(viCorpusVO.getVoiceType()), ViCorpus::getVoiceType, viCorpusVO.getVoiceType()) + .like(ObjUtil.isNotEmpty(viCorpusVO.getDialect()), ViCorpus::getDialect, viCorpusVO.getDialect()) + .like(ObjUtil.isNotEmpty(viCorpusVO.getCorpusName()), ViCorpus::getCorpusName, viCorpusVO.getCorpusName()) + .in(CollUtil.isNotEmpty(viCorpusVO.getParentIds()), ViCorpus::getParentId, viCorpusVO.getParentIds()) + .groupBy(ViCorpus::getParentId, ViCorpus::getVoiceType, ViCorpus::getDialect, ViCorpus::getCorpusName); + + List list = this.baseMapper.selectJoinList(ViContinuousCorpusVO.class, wrapper); + + // 查询 children 用 parentId 关联 + for (ViContinuousCorpusVO vo : list) { + List children = this.list( + Wrappers.lambdaQuery(ViCorpus.class) + .eq(ViCorpus::getParentId, vo.getParentId()) + .eq(ViCorpus::getContinuous, 0) + .eq(ViCorpus::getType, ViCorpusEnum.TEST) + .orderByAsc(ViCorpus::getSortOrder) + ); + vo.setChildren(children); + } + + return list; + } + + @Override public int insertViCorpus(ViCorpus viCorpus) { + viCorpus.setParentId(IdUtils.fastSimpleUUID()); return this.baseMapper.insert(viCorpus); } - /** - * 修改语料库 - * - * @param viCorpus 语料库 - * @return 结果 - */ + @Override + public boolean insertBatch(List data) { + data.forEach(v -> v.setParentId(IdUtils.fastSimpleUUID())); + return this.saveBatch(data); + } + @Override public int updateViCorpus(ViCorpus viCorpus) { return this.baseMapper.updateById(viCorpus); } - /** - * 批量删除语料库 - * - * @param corpusIds 需要删除的语料库主键 - * @return 结果 - */ @Override public int deleteViCorpusByCorpusIds(Long[] corpusIds) { return this.baseMapper.deleteBatchIds(Arrays.asList(corpusIds)); } + @Override + public int insertOrUpdateBatch(List data) { + if (CollUtil.isEmpty(data)) { + throw new IllegalArgumentException("data must not be null"); + } + + // 非空 id → 更新 + List updateList = data.stream() + .filter(item -> item.getCorpusId() != null) + .collect(Collectors.toList()); + + // 空 id → 新增 + List insertList = data.stream() + .filter(item -> item.getCorpusId() == null) + .collect(Collectors.toList()); + + if (!insertList.isEmpty()) { + this.saveBatch(insertList); + } + + // 批量更新(自定义 XML,返回影响行数) + if (!updateList.isEmpty()) { + for (ViCorpus viCorpus : updateList) { + this.updateById(viCorpus); + } + } + return data.size(); + } + + @Override + public boolean insertContinuousViCorpus(ViContinuousCorpusVO viContinuousCorpusVO) { + String parentId = IdUtils.fastSimpleUUID(); + List children = viContinuousCorpusVO.getChildren(); + children.forEach(v -> v.setParentId(parentId)); + return this.insertBatch(children); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public int updateContinuousViCorpus(ViContinuousCorpusVO viContinuousCorpusVO) { + String parentId = viContinuousCorpusVO.getParentId(); + + // parentId 下现有 corpusId + List dbIds = this.list(new LambdaQueryWrapper() + .select(ViCorpus::getCorpusId) + .eq(ViCorpus::getParentId, parentId)) + .stream() + .map(ViCorpus::getCorpusId) + .collect(Collectors.toList()); + + List children = viContinuousCorpusVO.getChildren(); + + // 新增、更新、保留 + List addList = new ArrayList<>(); + List updateList = new ArrayList<>(); + Set keepIds = new HashSet<>(); + + for (ViCorpus child : children) { + if (ObjUtil.isEmpty(child.getCorpusId())) { + // 新增 + child.setParentId(parentId); + addList.add(child); + } else { + // 更新 + updateList.add(child); + keepIds.add(child.getCorpusId()); + } + } + + // 要删除的 + List removeIds = dbIds.stream() + .filter(id -> !keepIds.contains(id)) + .collect(Collectors.toList()); + + if (!addList.isEmpty()) { + this.saveBatch(addList); + } + if (!updateList.isEmpty()) { + this.updateBatchById(updateList); + } + if (!removeIds.isEmpty()) { + this.removeByIds(removeIds); + } + + return addList.size() + updateList.size() + removeIds.size(); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public int deleteViCorpusByParentIds(String[] parentIds) { + if (parentIds == null || parentIds.length == 0) { + return 0; + } + return this.baseMapper.delete( + new LambdaQueryWrapper() + .in(ViCorpus::getParentId, Arrays.asList(parentIds)) + ); + } + } diff --git a/cmvr-iot-vi/src/main/java/com/cmvr/vi/service/impl/ViProjectServiceImpl.java b/cmvr-iot-vi/src/main/java/com/cmvr/vi/service/impl/ViProjectServiceImpl.java new file mode 100644 index 0000000..74658cb --- /dev/null +++ b/cmvr-iot-vi/src/main/java/com/cmvr/vi/service/impl/ViProjectServiceImpl.java @@ -0,0 +1,56 @@ +package com.cmvr.vi.service.impl; + +import cn.hutool.core.util.ObjUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.cmvr.vi.mapper.ViProjectMapper; +import com.cmvr.vi.model.domain.ViProject; +import com.cmvr.vi.service.IViProjectService; +import org.springframework.stereotype.Service; + +import java.util.Arrays; +import java.util.List; + +/** + * 语音交互-项目Service业务层处理 + * + * @author cmvr-iot + */ +@Service +public class ViProjectServiceImpl extends ServiceImpl implements IViProjectService { + + @Override + public ViProject selectViProjectByProjectId(String projectId) { + return this.getById(projectId); + } + + @Override + public List selectViProjectList(ViProject viProject) { + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.eq(ObjUtil.isNotEmpty(viProject.getProjectName()), ViProject::getProjectName, viProject.getProjectName()) + .eq(ObjUtil.isNotEmpty(viProject.getStatus()), ViProject::getStatus, viProject.getStatus()); + return this.list(wrapper); + } + + @Override + public int insertViProject(ViProject viProject) { + return this.baseMapper.insert(viProject); + } + + @Override + public int updateViProject(ViProject viProject) { + return this.baseMapper.updateById(viProject); + } + + @Override + public int deleteViProjectByProjectIds(String[] projectIds) { + return this.baseMapper.deleteBatchIds(Arrays.asList(projectIds)); + } + + @Override + public String execute(String projectId) { + + return ""; + } +} diff --git a/cmvr-iot-vi/src/main/java/com/cmvr/vi/service/impl/ViSchemeServiceImpl.java b/cmvr-iot-vi/src/main/java/com/cmvr/vi/service/impl/ViSchemeServiceImpl.java new file mode 100644 index 0000000..465cf09 --- /dev/null +++ b/cmvr-iot-vi/src/main/java/com/cmvr/vi/service/impl/ViSchemeServiceImpl.java @@ -0,0 +1,170 @@ +package com.cmvr.vi.service.impl; + +import cn.hutool.core.util.ObjUtil; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.cmvr.common.exception.GlobalException; +import com.cmvr.test.enums.FlowSceneTypeEnum; +import com.cmvr.test.model.domain.TeDetectionItem; +import com.cmvr.test.model.vo.TeQueryTaskOrchestraItemVO; +import com.cmvr.test.model.vo.TeTaskOrchestraVO; +import com.cmvr.test.service.ITeDetectionItemService; +import com.cmvr.test.service.ITeTaskOrchestrationService; +import com.cmvr.vi.mapper.ViSchemeMapper; +import com.cmvr.vi.model.domain.ViScheme; +import com.cmvr.vi.service.IViSchemeService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 语音交互-方案Service业务层处理 + * + * @author cmvr-iot + */ +@Service +@RequiredArgsConstructor +public class ViSchemeServiceImpl extends ServiceImpl implements IViSchemeService { + + private final ITeTaskOrchestrationService teTaskOrchestrationService; + private final ITeDetectionItemService detectionItemService; + + @Override + public ViScheme selectViSchemeBySchemeId(String schemeId) { + return this.getById(schemeId); + } + + @Override + public List selectViSchemeList(ViScheme viScheme) { + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.eq(ViScheme::getProjectId, viScheme.getProjectId()) + .eq(ObjUtil.isNotEmpty(viScheme.getSchemeName()), ViScheme::getSchemeName, viScheme.getSchemeName()) + .eq(ObjUtil.isNotEmpty(viScheme.getStatus()), ViScheme::getStatus, viScheme.getStatus()) + .orderByAsc(ViScheme::getSort); + return this.list(wrapper); + } + + @Override + public int insertViScheme(ViScheme viScheme) { + // 先插方案 + int insert = this.baseMapper.insert(viScheme); + + String taskId = viScheme.getProjectId(); + Integer sort = viScheme.getSort(); + + // 获取对应场景流程id + Integer sceneType = viScheme.getSceneType(); // 1:唤醒 2:单次对话 3:连续对话 + String key; + switch (sceneType) { + case 1: key = FlowSceneTypeEnum.VI_WAKEUP_CORPUS.name(); break; + case 2: key = FlowSceneTypeEnum.VI_SINGLE_CORPUS.name(); break; + case 3: key = FlowSceneTypeEnum.VI_CONTINUOUS_CORPUS.name(); break; + default: throw new GlobalException("场景类型参数错误!"); + } + TeDetectionItem item = detectionItemService.selectTeDetectionItemBySceneType(key); + String itemId = item.getId(); + + // 查询已有编排项 + List items = teTaskOrchestrationService.queryByTaskId(taskId); + + // 插入新编排项 + TeQueryTaskOrchestraItemVO itemVO = new TeQueryTaskOrchestraItemVO(); + itemVO.setItemId(itemId); + itemVO.setOrderNum(sort); + items.add(itemVO); + + // 落库并归一化 + normalizeTaskItems(taskId); + + return insert; + } + + @Override + public int updateViScheme(ViScheme viScheme) { + int update = this.baseMapper.updateById(viScheme); + + String taskId = viScheme.getProjectId(); + + // 这里不管 sort 是否改动,都做一次归一化,避免乱序 + normalizeTaskItems(taskId); + + return update; + } + + @Override + public int deleteViSchemeBySchemeIds(String[] schemeIds) { + List schemeIdList = Arrays.asList(schemeIds); + + // 查要删除的方案,获取对应 taskId 和流程 itemId + List schemes = this.baseMapper.selectBatchIds(schemeIdList); + int delete = this.baseMapper.deleteBatchIds(schemeIdList); + + // 每个 taskId 单独归一化一次 + Map> taskSchemeMap = schemes.stream() + .collect(Collectors.groupingBy(ViScheme::getProjectId)); + + for (String taskId : taskSchemeMap.keySet()) { + List items = teTaskOrchestrationService.queryByTaskId(taskId); + + if (items != null && !items.isEmpty()) { + // 找出要删的流程 itemId + List deleteItemIds = taskSchemeMap.get(taskId).stream() + .map(s -> { + String key; + switch (s.getSceneType()) { + case 1: key = FlowSceneTypeEnum.VI_WAKEUP_CORPUS.name(); break; + case 2: key = FlowSceneTypeEnum.VI_SINGLE_CORPUS.name(); break; + case 3: key = FlowSceneTypeEnum.VI_CONTINUOUS_CORPUS.name(); break; + default: throw new GlobalException("场景类型参数错误!"); + } + return detectionItemService.selectTeDetectionItemBySceneType(key).getId(); + }) + .collect(Collectors.toList()); + + // 移除这些 item + items.removeIf(i -> deleteItemIds.contains(i.getItemId())); + + // 覆盖保存并归一化 + normalizeTaskItems(taskId); + } + } + + return delete; + } + + /** + * 根据 taskId 对编排表的检测项重新排序(1..N) + */ + private void normalizeTaskItems(String taskId) { + List items = teTaskOrchestrationService.queryByTaskId(taskId); + if (items == null || items.isEmpty()) { + return; + } + + // 按顺序排序 + items.sort(Comparator.comparing(TeQueryTaskOrchestraItemVO::getOrderNum)); + + // 重新归一化 1..N + for (int i = 0; i < items.size(); i++) { + items.get(i).setOrderNum(i + 1); + } + + // 落库 + List itemIds = items.stream() + .map(TeQueryTaskOrchestraItemVO::getItemId) + .collect(Collectors.toList()); + + TeTaskOrchestraVO orchestraVO = new TeTaskOrchestraVO(); + orchestraVO.setTaskId(taskId); + orchestraVO.setItemIds(itemIds); + + teTaskOrchestrationService.insertTeTaskOrchestration(orchestraVO); + } + +}