Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
lixiaolong 2025-09-22 10:58:58 +08:00
commit fa63a8e039
37 changed files with 1323 additions and 233 deletions

View File

@ -4,11 +4,14 @@ import com.cmvr.common.core.controller.BaseController;
import com.cmvr.common.core.domain.AjaxResult; import com.cmvr.common.core.domain.AjaxResult;
import com.cmvr.common.core.page.TableDataInfo; import com.cmvr.common.core.page.TableDataInfo;
import com.cmvr.vi.model.domain.ViCorpus; 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.cmvr.vi.service.IViCorpusService;
import io.swagger.annotations.Api; import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize; 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.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
@ -20,11 +23,6 @@ import org.springframework.web.bind.annotation.RestController;
import java.util.List; import java.util.List;
/**
* 语料库Controller
*
* @author cmvr-iot
*/
@Api(tags = "语音交互--语料库") @Api(tags = "语音交互--语料库")
@RestController @RestController
@RequestMapping("/vi/corpus") @RequestMapping("/vi/corpus")
@ -33,67 +31,97 @@ public class ViCorpusController extends BaseController {
private final IViCorpusService viCorpusService; private final IViCorpusService viCorpusService;
/** @ApiOperation("根据id获取语料详细信息")
* 查询语料库列表
*/
@ApiOperation("查询语料库列表")
@PreAuthorize("@ss.hasPermi('vi:corpus:list')")
@GetMapping("/list")
public TableDataInfo list(ViCorpus viCorpus) {
startPage();
List<ViCorpus> list = viCorpusService.selectViCorpusList(viCorpus);
return getDataTable(list);
}
// /**
// * 导出语料库列表
// */
// @ApiOperation("查询语料库列表")
// @PreAuthorize("@ss.hasPermi('vi:corpus:export')")
// @PostMapping("/export")
// public void export(HttpServletResponse response, ViCorpus viCorpus) {
// List<ViCorpus> list = viCorpusService.selectViCorpusList(viCorpus);
// ExcelUtil<ViCorpus> util = new ExcelUtil<ViCorpus>(ViCorpus.class);
// util.exportExcel(response, list, "语料库数据");
// }
/**
* 获取语料库详细信息
*/
@ApiOperation("根据id获取语料库详细信息")
@PreAuthorize("@ss.hasPermi('vi:corpus:query')") @PreAuthorize("@ss.hasPermi('vi:corpus:query')")
@GetMapping(value = "/{corpusId}") @GetMapping(value = "/{corpusId}")
public AjaxResult getInfo(@PathVariable("corpusId") Long corpusId) { public AjaxResult getInfo(@PathVariable("corpusId") Long corpusId) {
return success(viCorpusService.selectViCorpusByCorpusId(corpusId)); return success(viCorpusService.selectViCorpusByCorpusId(corpusId));
} }
/** // ----------------------- 单次对话语料/唤醒语料 --------------------------
* 新增语料库
*/ @ApiOperation(value = "查询单次对话语料/唤醒语料列表")
@ApiOperation("新增语料库") @PreAuthorize("@ss.hasPermi('vi:corpus:list')")
@PostMapping("/single/list")
public TableDataInfo list(@Validated @RequestBody ViCorpusVO viCorpusVO) {
startPage();
List<ViCorpus> list = viCorpusService.selectViCorpusList(viCorpusVO);
return getDataTable(list);
}
@ApiOperation("新增单次对话语料/唤醒语料")
@PreAuthorize("@ss.hasPermi('vi:corpus:add')") @PreAuthorize("@ss.hasPermi('vi:corpus:add')")
@PostMapping @PostMapping
public AjaxResult add(@RequestBody ViCorpus viCorpus) { public AjaxResult add(@RequestBody ViCorpus viCorpus) {
return toAjax(viCorpusService.insertViCorpus(viCorpus)); return toAjax(viCorpusService.insertViCorpus(viCorpus));
} }
/** @ApiOperation("批量新增单次对话语料/唤醒语料")
* 修改语料库 @PreAuthorize("@ss.hasPermi('vi:corpus:add')")
*/ @PostMapping("/insertBatch")
@ApiOperation("修改语料库") public AjaxResult insertBatch(@RequestBody List<ViCorpus> 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')") @PreAuthorize("@ss.hasPermi('vi:corpus:edit')")
@PutMapping @PutMapping
public AjaxResult edit(@RequestBody ViCorpus viCorpus) { public AjaxResult edit(@RequestBody ViCorpus viCorpus) {
return toAjax(viCorpusService.updateViCorpus(viCorpus)); return toAjax(viCorpusService.updateViCorpus(viCorpus));
} }
/** // ----------------------- 连续对话 --------------------------
* 删除语料库
*/ @ApiOperation("查询连续对话列表")
@ApiOperation("删除语料库") @PreAuthorize("@ss.hasPermi('vi:corpus:list')")
@PreAuthorize("@ss.hasPermi('vi:corpus:remove')") @PostMapping("/continuous/list")
@DeleteMapping("/{corpusIds}") public TableDataInfo continuousList(@Validated @RequestBody ViCorpusVO viCorpusVO) {
public AjaxResult remove(@PathVariable Long[] corpusIds) { startPage();
return toAjax(viCorpusService.deleteViCorpusByCorpusIds(corpusIds)); List<ViContinuousCorpusVO> 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<ViCorpus> data) {
return toAjax(viCorpusService.updateBatch(data));
}*/
/* @ApiOperation("批量新增/修改")
@PreAuthorize("@ss.hasPermi('vi:corpus:edit')")
@PostMapping("/insertOrUpdateBatch")
public AjaxResult insertOrUpdateBatch(@RequestBody List<ViCorpus> data) {
return toAjax(viCorpusService.insertOrUpdateBatch(data));
}*/
} }

View File

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

View File

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

View File

@ -24,7 +24,11 @@ import org.springframework.http.MediaType;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import javax.imageio.IIOImage;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import javax.imageio.ImageWriteParam;
import javax.imageio.ImageWriter;
import javax.imageio.stream.FileImageOutputStream;
import java.awt.*; import java.awt.*;
import java.awt.color.ColorSpace; import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
@ -42,6 +46,7 @@ import java.nio.file.Files;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Base64; import java.util.Base64;
import java.util.Iterator;
import java.util.List; import java.util.List;
@ -166,6 +171,7 @@ public class EdgeCameraServiceImpl implements EdgeCameraService, EdgeStreamServi
throw new GlobalException("响应图片数据无效"); throw new GlobalException("响应图片数据无效");
} }
String filePath = saveImage(frame.getData(), frame.getWidth(), frame.getHeight(), false); String filePath = saveImage(frame.getData(), frame.getWidth(), frame.getHeight(), false);
log.info("彩色图片地址:{}", filePath);
// String deepFilePath = saveImage(depthFrame.getData(), depthFrame.getWidth(), depthFrame.getHeight(), true); // String deepFilePath = saveImage(depthFrame.getData(), depthFrame.getWidth(), depthFrame.getHeight(), true);
// List<String> result = new ArrayList<>(); // List<String> result = new ArrayList<>();
try { try {
@ -178,7 +184,7 @@ public class EdgeCameraServiceImpl implements EdgeCameraService, EdgeStreamServi
} catch (IOException e) { } catch (IOException e) {
throw new GlobalException("图片上传失败: " + e.getMessage()); throw new GlobalException("图片上传失败: " + e.getMessage());
} finally { } finally {
deleteTempFile(filePath); // deleteTempFile(filePath);
// deleteTempFile(deepFilePath); // deleteTempFile(deepFilePath);
// todo 暂时不关闭摄像头 // todo 暂时不关闭摄像头
// stop(terminalId, deviceId); // stop(terminalId, deviceId);
@ -368,50 +374,50 @@ public class EdgeCameraServiceImpl implements EdgeCameraService, EdgeStreamServi
String fileName = System.currentTimeMillis() + (isDepth ? "_depth" : "") + ".jpg"; String fileName = System.currentTimeMillis() + (isDepth ? "_depth" : "") + ".jpg";
String filePath = Paths.get(tempDir.getAbsolutePath(), fileName).toString(); String filePath = Paths.get(tempDir.getAbsolutePath(), fileName).toString();
try { try {
byte[] pixels = pixelData.toByteArray(); byte[] pixels = pixelData.toByteArray();
BufferedImage image; BufferedImage image;
if (isDepth) { if (isDepth) {
// 深度图单通道灰度 // 灰度深度图
DataBuffer buffer = new DataBufferByte(pixels, pixels.length); DataBuffer buffer = new DataBufferByte(pixels, pixels.length);
WritableRaster raster = Raster.createInterleavedRaster( WritableRaster raster = Raster.createInterleavedRaster(buffer, width, height, width, 1, new int[]{0}, null);
buffer, width, height, width, 1, ColorModel cm = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), false, false,
new int[]{0}, null Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
);
ColorModel cm = new ComponentColorModel(
ColorSpace.getInstance(ColorSpace.CS_GRAY),
false, false,
Transparency.OPAQUE,
DataBuffer.TYPE_BYTE
);
image = new BufferedImage(cm, raster, false, null); image = new BufferedImage(cm, raster, false, null);
} else { } else {
// 彩色RGB 三通道 // 彩色RGB
DataBuffer buffer = new DataBufferByte(pixels, pixels.length); DataBuffer buffer = new DataBufferByte(pixels, pixels.length);
WritableRaster raster = Raster.createInterleavedRaster( WritableRaster raster = Raster.createInterleavedRaster(buffer, width, height, 3 * width, 3,
buffer, width, height, 3 * width, 3, new int[]{1, 2, 0}, null);
new int[]{2, 1, 0}, null ColorModel cm = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), false, false,
); Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
ColorModel cm = new ComponentColorModel(
ColorSpace.getInstance(ColorSpace.CS_sRGB),
false, false,
Transparency.OPAQUE,
DataBuffer.TYPE_BYTE
);
image = new BufferedImage(cm, raster, false, null); image = new BufferedImage(cm, raster, false, null);
} }
File output = new File(filePath); // 设置高质量JPEG压缩
ImageIO.write(image, "jpg", output); Iterator<ImageWriter> 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) { } catch (IOException e) {
throw new GlobalException(e.getMessage()); throw new GlobalException(e.getMessage());
} }
return filePath; return filePath;
} }
/** /**
* 将本地文件转换为 MultipartFile * 将本地文件转换为 MultipartFile
*/ */

View File

@ -0,0 +1,11 @@
package com.cmvr.test.enums;
/**
* 流程场景类型
*/
public enum FlowSceneTypeEnum {
NORMAL, // 普通
VI_WAKEUP_CORPUS, // 语音交互-唤醒场景
VI_SINGLE_CORPUS, // 语音交互-单次对话场景
VI_CONTINUOUS_CORPUS // 语音交互-连续对话场景
}

View File

@ -174,7 +174,6 @@ public class FlowGraph {
* 获取开始节点对象 * 获取开始节点对象
*/ */
public FlowNodeWrapper getStartNode() { public FlowNodeWrapper getStartNode() {
System.out.println("这是什么线程:" + Thread.currentThread().getName());
String id = findStartNodeId(); String id = findStartNodeId();
return getNode(id); return getNode(id);
} }

View File

@ -10,6 +10,8 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component; 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, public void markRunning(String instId, String taskId, String itemId,
String nodeId, String nodeType, String operate, String nodeId, String nodeType, String operate,
String action, String paramsIn) { String action, String paramsIn, List<Integer> iterations) {
nodeInstService.logRunning(instId, taskId, itemId, nodeId, nodeType, operate, action, paramsIn); nodeInstService.logRunning(instId, taskId, itemId, nodeId, nodeType, operate, action, paramsIn, iterations);
} }
public void markSuccess(String instId, String itemId, String nodeId, int pendingItemCount, String nodeType, public void markSuccess(String instId, String itemId, String nodeId, int pendingItemCount, String nodeType,

View File

@ -1,5 +1,6 @@
package com.cmvr.test.flow.control; package com.cmvr.test.flow.control;
import cn.hutool.core.bean.BeanUtil;
import com.cmvr.common.exception.GlobalException; import com.cmvr.common.exception.GlobalException;
import com.cmvr.test.enums.NodeTypeEnum; import com.cmvr.test.enums.NodeTypeEnum;
import com.cmvr.test.enums.TaskStatusEnum; 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.context.TaskThreadRegistry;
import com.cmvr.test.flow.runtime.engine.FlowItemExecutor; import com.cmvr.test.flow.runtime.engine.FlowItemExecutor;
import com.cmvr.test.flow.runtime.message.TaskNodeExecuteContext; import com.cmvr.test.flow.runtime.message.TaskNodeExecuteContext;
import com.cmvr.test.flow.runtime.message.TaskNodeExecuteMessage;
import com.cmvr.test.service.ITeNodeInstService; import com.cmvr.test.service.ITeNodeInstService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -17,6 +19,7 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -118,7 +121,7 @@ public class FlowControlService {
nodeInstService.update(instId, nodeId, TaskStatusEnum.RUNNING); nodeInstService.update(instId, nodeId, TaskStatusEnum.RUNNING);
continue; continue;
} else { } else {
nodeInstService.delete(instId, nodeId); nodeInstService.delete(instId, nodeId, pendingNode.getIterations());
} }
if (graph.isSub()) { if (graph.isSub()) {
// 找到子图对应的loop节点 // 找到子图对应的loop节点
@ -135,11 +138,21 @@ public class FlowControlService {
// 子图中断点先执行中断的子图节点 // 子图中断点先执行中断的子图节点
executor.execute(() -> { executor.execute(() -> {
CountDownLatch latch = taskThreadRegistry.getLatch(instId, loopCtx.getNode().getNodeId()); 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( flowItemExecutor.executeNode(
pendingNode.getGraph(), pendingNode.getNode(), pendingNode.getGraph(),
pendingNode.getStartNodeId(), pendingNode.getStartInputDefs(), pendingNode.getNode(),
pendingNode.getRootMessage(), latch::countDown, pendingNode.getStartNodeId(),
pendingNode.getLoopIteration() pendingNode.getStartInputDefs(),
resumeMessage,
latch::countDown,
pendingNode.getIterations()
); );
try { try {
@ -152,11 +165,19 @@ public class FlowControlService {
} else { } else {
// 普通节点直接恢复 // 普通节点直接恢复
executor.execute(() -> { executor.execute(() -> {
TaskNodeExecuteMessage resumeMessage = new TaskNodeExecuteMessage();
BeanUtil.copyProperties(pendingNode.getRootMessage(), resumeMessage);
resumeMessage.setLoopNum(pendingNode.getLoopIteration()); // 保留循环次数
resumeMessage.setIterations(new ArrayList<>(pendingNode.getIterations())); // 保留路径
flowItemExecutor.executeNode( flowItemExecutor.executeNode(
pendingNode.getGraph(), pendingNode.getNode(), pendingNode.getGraph(),
pendingNode.getStartNodeId(), pendingNode.getStartInputDefs(), pendingNode.getNode(),
pendingNode.getRootMessage(), pendingNode.getOnFinished(), pendingNode.getStartNodeId(),
pendingNode.getLoopIteration() pendingNode.getStartInputDefs(),
resumeMessage,
pendingNode.getOnFinished(),
pendingNode.getIterations()
); );
}); });
} }

View File

@ -1,5 +1,6 @@
package com.cmvr.test.flow.runtime.dispatcher; package com.cmvr.test.flow.runtime.dispatcher;
import cn.hutool.core.bean.BeanUtil;
import com.cmvr.common.utils.spring.SpringUtils; import com.cmvr.common.utils.spring.SpringUtils;
import com.cmvr.test.flow.builder.FlowGraph; import com.cmvr.test.flow.builder.FlowGraph;
import com.cmvr.test.flow.builder.FlowNodeWrapper; import com.cmvr.test.flow.builder.FlowNodeWrapper;
@ -11,6 +12,8 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
@Slf4j @Slf4j
@ -25,7 +28,9 @@ public class FlowLoopNodeHandler implements FlowNodeTypeHandler {
String instId = message.getInstId(); String instId = message.getInstId();
FlowGraph graph = message.getGraph(); FlowGraph graph = message.getGraph();
// 当前从第几次循环开始 // 当前从第几次循环开始
int loopNum = message.getLoopNum() < 0 ? 1 : message.getLoopNum(); int loopNum = Math.max(message.getLoopNum(), 1);
// 父级迭代路径
List<Integer> parentIterations = message.getIterations();
String nodeId = message.getNodeId(); String nodeId = message.getNodeId();
FlowNodeWrapper nodeWrapper = graph.getNode(nodeId); FlowNodeWrapper nodeWrapper = graph.getNode(nodeId);
@ -50,7 +55,28 @@ public class FlowLoopNodeHandler implements FlowNodeTypeHandler {
taskThreadRegistry.registerLatch(instId, nodeId, latch); taskThreadRegistry.registerLatch(instId, nodeId, latch);
FlowItemExecutor flowItemExecutor = SpringUtils.getBean(FlowItemExecutor.class); FlowItemExecutor flowItemExecutor = SpringUtils.getBean(FlowItemExecutor.class);
flowItemExecutor.executeSubGraph(subGraph, message, latch::countDown, i);
// 构造新的迭代路径
// List<Integer> newIterations = new ArrayList<>(parentIterations);
// newIterations.add(i);
//
// // 克隆 message避免覆盖父级
// TaskNodeExecuteMessage subMessage = new TaskNodeExecuteMessage();
// BeanUtil.copyProperties(message, subMessage);
//// subMessage.setLoopNum(i); // 当前层循环次数
// subMessage.setIterations(newIterations); // 完整迭代路径
// 构造新的迭代路径
List<Integer> 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 { try {
latch.await(); // 等待子图执行完毕后再进行下一轮 latch.await(); // 等待子图执行完毕后再进行下一轮

View File

@ -53,12 +53,11 @@ public class FlowItemExecutor {
String startNodeId = graph.findStartNodeId(); String startNodeId = graph.findStartNodeId();
List<FlowParamDef> startInputDefs = graph.getStartInputParamsDefs(); List<FlowParamDef> startInputDefs = graph.getStartInputParamsDefs();
NodeExecutor executor = node ->
executeNode(graph, node, startNodeId, startInputDefs, rootMessage, onFinished, -1);
// 执行 start 节点同步执行 // 执行 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); flowTaskScheduler.start(graph, taskInstHolder.getContext(rootMessage.getInstId()), executor, onFinished);
@ -70,7 +69,7 @@ public class FlowItemExecutor {
public void executeSubGraph(FlowGraph subGraph, public void executeSubGraph(FlowGraph subGraph,
TaskNodeExecuteMessage rootMessage, TaskNodeExecuteMessage rootMessage,
Runnable onFinished, Runnable onFinished,
int loopIteration) { List<Integer> iterations) {
// 主图中开始节点的 inputParams // 主图中开始节点的 inputParams
List<FlowParamDef> startInputDefs = subGraph.getStartInputParamsDefs(); List<FlowParamDef> startInputDefs = subGraph.getStartInputParamsDefs();
@ -81,15 +80,16 @@ public class FlowItemExecutor {
NodeExecutor executor = node -> executeNode( NodeExecutor executor = node -> executeNode(
subGraph, node, subStartNodeId, startInputDefs, subGraph, node, subStartNodeId, startInputDefs,
rootMessage, onFinished, loopIteration rootMessage, onFinished, iterations
); );
// 执行子图起始节点 // 执行子图起始节点
executeNode(subGraph, subStartNode, subStartNodeId, startInputDefs, 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<FlowParamDef> startInputDefs, List<FlowParamDef> startInputDefs,
TaskNodeExecuteMessage rootMessage, TaskNodeExecuteMessage rootMessage,
Runnable onFinished, Runnable onFinished,
int loopIteration) { List<Integer> iterations) {
String instId = rootMessage.getInstId(); String instId = rootMessage.getInstId();
String itemId = rootMessage.getItemId(); String itemId = rootMessage.getItemId();
String nodeId = node.getNodeId(); 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 { try {
// 准备输入参数 // 准备输入参数
JSONObject inputParams = paramPreparer.prepare(graph, node, startNodeId, startInputDefs, rootMessage); JSONObject inputParams = paramPreparer.prepare(graph, node, startNodeId, startInputDefs, rootMessage);
// 创建带上下文的执行消息关键注入 graph // 创建带上下文的执行消息关键注入 graph + iterations
TaskNodeExecuteMessage message = buildTaskNodeExecuteMsg(graph, node, rootMessage, nodeId, inputParams, loopIteration); TaskNodeExecuteMessage message = buildTaskNodeExecuteMsg(graph, node, rootMessage, nodeId, inputParams, iterations);
// 执行责任链 // 执行责任链
TaskNodeExecuteResult result = chainBuilder.build(interceptors, dispatchNodeHandler).apply(message); TaskNodeExecuteResult result = chainBuilder.build(interceptors, dispatchNodeHandler).apply(message);
@ -126,13 +128,14 @@ public class FlowItemExecutor {
// 分支节点处理 // 分支节点处理
if (node.getNodeType() == NodeTypeEnum.BRANCH) { 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; return;
} }
// END 节点 or 子图结束 // END 节点 or 子图结束
if (node.getNodeType() == NodeTypeEnum.END || node.getNodeType() == NodeTypeEnum.SUB_END) { if (node.getNodeType() == NodeTypeEnum.END || node.getNodeType() == NodeTypeEnum.SUB_END) {
try { try {
log.info("{} 节点执行,释放线程", node.getNodeType());
onFinished.run(); onFinished.run();
} catch (Exception e) { } catch (Exception e) {
log.error("onFinished 回调异常", e); log.error("onFinished 回调异常", e);
@ -140,22 +143,24 @@ public class FlowItemExecutor {
} }
// 通知调度器继续调度后继节点 // 通知调度器继续调度后继节点
flowTaskScheduler.markCompleted(message.getInstId(), nodeId, graph, n -> flowTaskScheduler.markCompleted(
executeNode(graph, n, startNodeId, startInputDefs, rootMessage, onFinished, loopIteration), loopIteration); message.getInstId(), nodeId, graph,
n -> executeNode(graph, n, startNodeId, startInputDefs, rootMessage, onFinished, iterations),
iterations
);
} finally { } finally {
// 如果不是暂停全中断 // 如果不是暂停全中断
TaskContext ctx = taskInstHolder.getContext(instId); TaskContext ctx = taskInstHolder.getContext(instId);
if (ctx == null || !ctx.isPaused()) { if (ctx == null || !ctx.isPaused()) {
taskThreadRegistry.unregisterNodeContext(instId, itemId, nodeId); taskThreadRegistry.unregisterNodeContext(instId, itemId, nodeId);
if (node.getNodeType().equals(NodeTypeEnum.SUB_START)) {
log.info("subStart注銷了");
}
} }
} }
} }
@NotNull @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<Integer> iterations) {
TaskNodeExecuteMessage message = new TaskNodeExecuteMessage(); TaskNodeExecuteMessage message = new TaskNodeExecuteMessage();
BeanUtil.copyProperties(rootMessage, message); BeanUtil.copyProperties(rootMessage, message);
message.setNodeId(nodeId); message.setNodeId(nodeId);
@ -163,11 +168,18 @@ public class FlowItemExecutor {
message.setAction(node.getAction()); message.setAction(node.getAction());
message.setInputParams(inputParams); message.setInputParams(inputParams);
message.setGraph(graph); message.setGraph(graph);
message.setLoopNum(loopIteration); // 只设置 iterations
message.setIterations(new ArrayList<>(iterations));
// loopNum 不在这里计算而是由 FlowLoopNodeHandler / resume 显式写入
// 如果 rootMessage 里已经带了 loopNum就沿用它
message.setLoopNum(rootMessage.getLoopNum());
return message; return message;
} }
private void postHandleBranchNode(FlowGraph graph, FlowNodeWrapper node, String startNodeId, List<FlowParamDef> startInputDefs, TaskNodeExecuteMessage rootMessage, Runnable onFinished, int loopIteration, String nodeId, JSONObject inputParams, TaskNodeExecuteMessage message) { private void postHandleBranchNode(FlowGraph graph, FlowNodeWrapper node, String startNodeId,
List<FlowParamDef> startInputDefs, TaskNodeExecuteMessage rootMessage, Runnable onFinished,
List<Integer> iterations, String nodeId, JSONObject inputParams, TaskNodeExecuteMessage message) {
String matchedAnchorId = FlowBranchEvaluator.evaluate(nodeId, node.getBranchConditions(), inputParams); String matchedAnchorId = FlowBranchEvaluator.evaluate(nodeId, node.getBranchConditions(), inputParams);
FlowEdge matchedEdge = graph.getEdgeByFromAnchorId(matchedAnchorId); FlowEdge matchedEdge = graph.getEdgeByFromAnchorId(matchedAnchorId);
@ -190,12 +202,13 @@ public class FlowItemExecutor {
message.getInstId(), message.getInstId(),
matchedEdge.getTo(), matchedEdge.getTo(),
graph, graph,
n -> executeNode(graph, n, startNodeId, startInputDefs, rootMessage, onFinished, loopIteration), n -> executeNode(graph, n, startNodeId, startInputDefs, rootMessage, onFinished, iterations),
loopIteration iterations
); );
} }
private void registerThread(FlowGraph graph, FlowNodeWrapper node, String startNodeId, List<FlowParamDef> startInputDefs, TaskNodeExecuteMessage rootMessage, Runnable onFinished, int loopIteration, String instId, String itemId, String nodeId) { private void registerThread(FlowGraph graph, FlowNodeWrapper node, String startNodeId, List<FlowParamDef> startInputDefs,
TaskNodeExecuteMessage rootMessage, Runnable onFinished, List<Integer> iterations, String instId, String itemId, String nodeId) {
TaskNodeExecuteContext nodeExecuteContext = new TaskNodeExecuteContext(); TaskNodeExecuteContext nodeExecuteContext = new TaskNodeExecuteContext();
nodeExecuteContext.setThread(Thread.currentThread()); nodeExecuteContext.setThread(Thread.currentThread());
nodeExecuteContext.setGraph(graph); nodeExecuteContext.setGraph(graph);
@ -204,7 +217,8 @@ public class FlowItemExecutor {
nodeExecuteContext.setNode(node); nodeExecuteContext.setNode(node);
nodeExecuteContext.setOnFinished(onFinished); nodeExecuteContext.setOnFinished(onFinished);
nodeExecuteContext.setRootMessage(rootMessage); nodeExecuteContext.setRootMessage(rootMessage);
nodeExecuteContext.setLoopIteration(loopIteration); // nodeExecuteContext.setLoopIteration(loopIteration);
nodeExecuteContext.setIterations(new ArrayList<>(iterations));
taskThreadRegistry.registerNodeContext(instId, itemId, nodeId, nodeExecuteContext); taskThreadRegistry.registerNodeContext(instId, itemId, nodeId, nodeExecuteContext);
} }

View File

@ -11,6 +11,7 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@ -32,7 +33,6 @@ public class FlowTaskScheduler {
TaskContext context, TaskContext context,
FlowItemExecutor.NodeExecutor executorFunc, FlowItemExecutor.NodeExecutor executorFunc,
Runnable onFinished) { Runnable onFinished) {
String instId = context.getInstId(); String instId = context.getInstId();
String startNodeId = graph.findStartNodeId(); String startNodeId = graph.findStartNodeId();
List<String> nextNodes = graph.getNextNodes(startNodeId); List<String> nextNodes = graph.getNextNodes(startNodeId);
@ -44,7 +44,7 @@ public class FlowTaskScheduler {
} }
for (String nextId : nextNodes) { 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, TaskContext context,
FlowItemExecutor.NodeExecutor executorFunc, FlowItemExecutor.NodeExecutor executorFunc,
Runnable onFinished, Runnable onFinished,
int loopIteration) { List<Integer> iterations) {
String instId = context.getInstId(); String instId = context.getInstId();
FlowNodeWrapper startNode = graph.findSubStartNodeId(); FlowNodeWrapper startNode = graph.findSubStartNodeId();
List<String> nextNodes = graph.getNextNodes(startNode.getNodeId()); List<String> nextNodes = graph.getNextNodes(startNode.getNodeId());
@ -67,16 +66,15 @@ public class FlowTaskScheduler {
return; return;
} }
// 并发调度后继节点
for (String nextId : nextNodes) { 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, String nodeId,
FlowGraph graph, FlowGraph graph,
FlowItemExecutor.NodeExecutor executorFunc, FlowItemExecutor.NodeExecutor executorFunc,
@ -99,12 +97,38 @@ public class FlowTaskScheduler {
scheduleNode(instId, nextId, graph, executorFunc, loopIteration); scheduleNode(instId, nextId, graph, executorFunc, loopIteration);
} }
} }
}*/
/**
* 某节点执行完成后判断并调度其所有后继节点
*/
public void markCompleted(String instId,
String nodeId,
FlowGraph graph,
FlowItemExecutor.NodeExecutor executorFunc,
List<Integer> iterations) {
List<String> nextNodes = graph.getNextNodes(nodeId);
for (String nextId : nextNodes) {
String key = FlowSchedulerCache.buildKey(instId, nextId, iterations);
// 使用 Set 来记录已完成的前驱节点
Set<String> 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, String nodeId,
FlowGraph graph, FlowGraph graph,
FlowItemExecutor.NodeExecutor executorFunc, FlowItemExecutor.NodeExecutor executorFunc,
@ -112,6 +136,31 @@ public class FlowTaskScheduler {
String key = FlowSchedulerCache.buildKey(instId, nodeId, loopIteration); 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<Integer> iterations) {
String key = FlowSchedulerCache.buildKey(instId, nodeId, iterations);
// 如果已经调度过则不再重复调度 // 如果已经调度过则不再重复调度
if (schedulerCache.trySchedule(key)) { if (schedulerCache.trySchedule(key)) {
executor.submit(() -> { executor.submit(() -> {
@ -132,9 +181,9 @@ public class FlowTaskScheduler {
String nodeId, String nodeId,
FlowGraph graph, FlowGraph graph,
FlowItemExecutor.NodeExecutor executorFunc, FlowItemExecutor.NodeExecutor executorFunc,
int loopIteration) { List<Integer> iterations) {
String key = FlowSchedulerCache.buildKey(instId, nodeId, loopIteration); String key = FlowSchedulerCache.buildKey(instId, nodeId, iterations);
if (schedulerCache.trySchedule(key)) { if (schedulerCache.trySchedule(key)) {
executor.submit(() -> { executor.submit(() -> {
try { try {

View File

@ -3,6 +3,7 @@ package com.cmvr.test.flow.runtime.engine.support;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
@ -34,4 +35,12 @@ public class FlowSchedulerCache {
? StrUtil.format("{}_{}_{}", instId, nodeId, loopIteration) ? StrUtil.format("{}_{}_{}", instId, nodeId, loopIteration)
: StrUtil.format("{}_{}", instId, nodeId); : StrUtil.format("{}_{}", instId, nodeId);
} }
public static String buildKey(String instId, String nodeId, List<Integer> iterations) {
StringBuilder sb = new StringBuilder(instId).append(":").append(nodeId);
for (int it : iterations) {
sb.append(":").append(it);
}
return sb.toString();
}
} }

View File

@ -30,7 +30,8 @@ public class FlowLoggingInterceptor implements FlowMsgPreInterceptor {
// todo 判断一下是否为LOOP重入 // todo 判断一下是否为LOOP重入
if (!(message.getNodeType().equalsIgnoreCase(NodeTypeEnum.LOOP.getCode()) && message.getLoopNum() > 0)) { if (!(message.getNodeType().equalsIgnoreCase(NodeTypeEnum.LOOP.getCode()) && message.getLoopNum() > 0)) {
taskInstHolder.markRunning(message.getInstId(), message.getTaskId(), message.getItemId(), message.getNodeId(), 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); TaskNodeExecuteResult result = next.apply(message);
@ -43,7 +44,7 @@ public class FlowLoggingInterceptor implements FlowMsgPreInterceptor {
return null; 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())); result.getOutputParams().toJSONString(), StrUtil.format("[{}] 执行成功", message.getAction()));
long end = System.currentTimeMillis(); long end = System.currentTimeMillis();

View File

@ -5,6 +5,7 @@ import com.cmvr.test.flow.builder.FlowNodeWrapper;
import com.cmvr.test.flow.builder.FlowParamDef; import com.cmvr.test.flow.builder.FlowParamDef;
import lombok.Data; import lombok.Data;
import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
@ -21,4 +22,5 @@ public class TaskNodeExecuteContext {
private TaskNodeExecuteMessage rootMessage; private TaskNodeExecuteMessage rootMessage;
private Runnable onFinished; private Runnable onFinished;
private int loopIteration; private int loopIteration;
private List<Integer> iterations = new ArrayList<>();
} }

View File

@ -5,25 +5,38 @@ import com.cmvr.test.enums.ActionEnum;
import com.cmvr.test.flow.builder.FlowGraph; import com.cmvr.test.flow.builder.FlowGraph;
import lombok.Data; import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/** /**
* 单个节点的执行输入消息 * 单个节点的执行输入消息
*/ */
@Data @Data
public class TaskNodeExecuteMessage { public class TaskNodeExecuteMessage {
/** 实例ID */ /**
* 实例ID
*/
private String instId; private String instId;
/** 任务ID */ /**
* 任务ID
*/
private String taskId; private String taskId;
/** 检测项ID用于定位流程 */ /**
* 检测项ID用于定位流程
*/
private String itemId; private String itemId;
/** 节点ID */ /**
* 节点ID
*/
private String nodeId; private String nodeId;
/** 待执行检测项数 */ /**
* 待执行检测项数
*/
private int pendingItemCount; private int pendingItemCount;
/** /**
@ -37,25 +50,44 @@ public class TaskNodeExecuteMessage {
*/ */
private transient int loopNum = 0; private transient int loopNum = 0;
/** 节点名称 */ /**
* 全局迭代路径嵌套循环时用
*/
private List<Integer> iterations = new ArrayList<>();
/**
* 节点名称
*/
private String nodeName; private String nodeName;
/** 节点类型START、FUNCTION、END 等) */ /**
* 节点类型STARTFUNCTIONEND
*/
private String nodeType; private String nodeType;
/** 节点行为(动作标识符) */ /**
* 节点行为动作标识符
*/
private ActionEnum action; private ActionEnum action;
/** 是否试运行 */ /**
* 是否试运行
*/
private boolean trial; private boolean trial;
/** 所属终端ID */ /**
* 所属终端ID
*/
private String terminalId; private String terminalId;
/** 节点入参 */ /**
* 节点入参
*/
private JSONObject inputParams; private JSONObject inputParams;
/** 上游输出参数(来自上一个节点的输出) */ /**
* 上游输出参数来自上一个节点的输出
*/
private JSONObject upStreamOutput; private JSONObject upStreamOutput;
} }

View File

@ -64,8 +64,8 @@ public class EdgeCameraOperateService implements EdgeOperateService {
// // 驾驶偏好 // // 驾驶偏好
// imageUrl = "http://192.168.1.100:9000/cmvr-iot/FILE/20250822/1755849654731.jpg"; // 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 = "http://192.168.1.100:9000/cmvr-iot/IMAGE/20250901/1756718594725.jpg";
String imageUrl = edgeCameraService.getRGBDImages(terminalId, deviceId); // String imageUrl = edgeCameraService.getRGBDImages(terminalId, deviceId);
JSONArray imageUrls = new JSONArray(); JSONArray imageUrls = new JSONArray();
if (ObjUtil.isNotEmpty(upstreamOutput)) { if (ObjUtil.isNotEmpty(upstreamOutput)) {

View File

@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.cmvr.common.annotation.Excel; import com.cmvr.common.annotation.Excel;
import com.cmvr.common.core.domain.BaseEntity; import com.cmvr.common.core.domain.BaseEntity;
import com.cmvr.test.enums.LoopNodeTypeEnum;
import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
import lombok.Data; import lombok.Data;
@ -49,4 +50,10 @@ public class TeDetectionItem extends BaseEntity {
@ApiModelProperty("检测项参数配置(JSON)") @ApiModelProperty("检测项参数配置(JSON)")
private String config; private String config;
/**
* #{@link LoopNodeTypeEnum}
*/
@ApiModelProperty("场景标识,默认 NORMAL")
private String sceneCode;
} }

View File

@ -47,6 +47,9 @@ public class TeNodeInst {
@ApiModelProperty("执行日志/备注") @ApiModelProperty("执行日志/备注")
private String message; private String message;
@ApiModelProperty("循环节点的层级")
private String iteration;
@ApiModelProperty("开始时间戳ms") @ApiModelProperty("开始时间戳ms")
private Long startTime; private Long startTime;

View File

@ -70,4 +70,12 @@ public interface ITeDetectionItemService extends IService<TeDetectionItem> {
* @return 结果 * @return 结果
*/ */
public int deleteTeDetectionItemById(String id); public int deleteTeDetectionItemById(String id);
/**
* 根据场景类型查询检测项
*
* @param sceneType 场景类型
* @return 结果
*/
public TeDetectionItem selectTeDetectionItemBySceneType(String sceneType);
} }

View File

@ -23,18 +23,19 @@ public interface ITeNodeInstService extends IService<TeNodeInst> {
/** /**
* 记录节点进入运行状态 * 记录节点进入运行状态
* *
* @param instId 任务实例 ID * @param instId 任务实例 ID
* @param taskId 任务定义 ID * @param taskId 任务定义 ID
* @param itemId 检测项 ID * @param itemId 检测项 ID
* @param nodeId 节点 ID * @param nodeId 节点 ID
* @param nodeType 节点类型 * @param nodeType 节点类型
* @param operate 执行来源 * @param operate 执行来源
* @param action 动作类型 * @param action 动作类型
* @param paramsIn 输入参数已序列化为 JSON 字符串 * @param paramsIn 输入参数已序列化为 JSON 字符串
* @param iterations 迭代次数
*/ */
void logRunning(String instId, String taskId, String itemId, void logRunning(String instId, String taskId, String itemId,
String nodeId, String nodeType, String nodeId, String nodeType,
String operate, String action, String paramsIn); String operate, String action, String paramsIn, List<Integer> iterations);
/** /**
* 节点执行后更新输出参数等信息/记录节点成功完成 * 节点执行后更新输出参数等信息/记录节点成功完成
@ -52,7 +53,7 @@ public interface ITeNodeInstService extends IService<TeNodeInst> {
/** /**
* 删除节点 * 删除节点
*/ */
void delete(String instId, String nodeId); void delete(String instId, String nodeId, List<Integer> iterations);
/** /**
* 更新节点 * 更新节点

View File

@ -12,6 +12,7 @@ import com.cmvr.test.model.vo.TeFlowViewVO;
import com.cmvr.test.service.ITeNodeInstService; import com.cmvr.test.service.ITeNodeInstService;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.Nullable;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.List; import java.util.List;
@ -33,7 +34,7 @@ public class ITeNodeInstServiceImpl extends ServiceImpl<TeNodeInstMapper, TeNode
@Override @Override
public void logRunning(String instId, String taskId, String itemId, public void logRunning(String instId, String taskId, String itemId,
String nodeId, String nodeType, String nodeId, String nodeType,
String operate, String action, String paramsIn) { String operate, String action, String paramsIn, List<Integer> iterations) {
TeNodeInst record = new TeNodeInst(); TeNodeInst record = new TeNodeInst();
record.setInstId(instId); record.setInstId(instId);
@ -47,6 +48,7 @@ public class ITeNodeInstServiceImpl extends ServiceImpl<TeNodeInstMapper, TeNode
record.setParamsIn(paramsIn); record.setParamsIn(paramsIn);
record.setParamsOut("{}"); record.setParamsOut("{}");
record.setStartTime(System.currentTimeMillis()); record.setStartTime(System.currentTimeMillis());
record.setIteration(getIteration(iterations));
this.save(record); this.save(record);
} }
@ -113,19 +115,26 @@ public class ITeNodeInstServiceImpl extends ServiceImpl<TeNodeInstMapper, TeNode
} }
@Override @Override
public void delete(String instId, String nodeId) { public void delete(String instId, String nodeId, List<Integer> iterations) {
String iteration = getIteration(iterations);
LambdaQueryWrapper<TeNodeInst> wrapper = Wrappers.lambdaQuery(); LambdaQueryWrapper<TeNodeInst> wrapper = Wrappers.lambdaQuery();
wrapper.eq(TeNodeInst::getInstId, instId) wrapper.eq(TeNodeInst::getInstId, instId)
.eq(TeNodeInst::getNodeId, nodeId); .eq(TeNodeInst::getNodeId, nodeId)
.eq(TeNodeInst::getIteration, iteration);
this.baseMapper.delete(wrapper); this.baseMapper.delete(wrapper);
} }
@Nullable
private static String getIteration(List<Integer> iterations) {
return StrUtil.join(StrUtil.DASHED, iterations);
}
@Override @Override
public void update(String instId, String nodeId, TaskStatusEnum nodeStatus) { public void update(String instId, String nodeId, TaskStatusEnum nodeStatus) {
LambdaUpdateWrapper<TeNodeInst> wrapper = Wrappers.lambdaUpdate(); LambdaUpdateWrapper<TeNodeInst> wrapper = Wrappers.lambdaUpdate();
wrapper.eq(TeNodeInst::getInstId, instId) wrapper.eq(TeNodeInst::getInstId, instId)
.eq(TeNodeInst::getNodeId, nodeId) .eq(TeNodeInst::getNodeId, nodeId)
.set(TeNodeInst::getStatus,nodeStatus.name()); .set(TeNodeInst::getStatus, nodeStatus.name());
this.update(wrapper); this.update(wrapper);
} }
} }

View File

@ -1,12 +1,13 @@
package com.cmvr.test.service.impl; package com.cmvr.test.service.impl;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject; 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.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cmvr.common.core.domain.entity.SysGroup; import com.cmvr.common.core.domain.entity.SysGroup;
import com.cmvr.common.utils.StringUtils;
import com.cmvr.test.enums.NodeTypeEnum; import com.cmvr.test.enums.NodeTypeEnum;
import com.cmvr.test.mapper.TeDetectionItemMapper; import com.cmvr.test.mapper.TeDetectionItemMapper;
import com.cmvr.test.model.domain.TeDetectionItem; import com.cmvr.test.model.domain.TeDetectionItem;
@ -56,11 +57,13 @@ public class TeDetectionItemServiceImpl extends ServiceImpl<TeDetectionItemMappe
.select(SysGroup::getGroupId) .select(SysGroup::getGroupId)
.select(SysGroup::getGroupName) .select(SysGroup::getGroupName)
.leftJoin(SysGroup.class, SysGroup::getGroupId, TeDetectionItem::getGroupId) .leftJoin(SysGroup.class, SysGroup::getGroupId, TeDetectionItem::getGroupId)
.like(StringUtils.isNotBlank(teDetectionItem.getDetectName()), TeDetectionItem::getDetectName, teDetectionItem.getDetectName()) .eq(StrUtil.isNotBlank(teDetectionItem.getSceneCode()), TeDetectionItem::getSceneCode, teDetectionItem.getSceneCode())
.like(StringUtils.isNotBlank(teDetectionItem.getDetectDesc()), TeDetectionItem::getDetectDesc, teDetectionItem.getDetectDesc()) .like(StrUtil.isNotBlank(teDetectionItem.getDetectName()), TeDetectionItem::getDetectName, teDetectionItem.getDetectName())
.eq(StringUtils.isNotBlank(teDetectionItem.getStatus()), TeDetectionItem::getStatus, teDetectionItem.getStatus()) .like(StrUtil.isNotBlank(teDetectionItem.getDetectDesc()), TeDetectionItem::getDetectDesc, teDetectionItem.getDetectDesc())
.eq(StringUtils.isNotBlank(teDetectionItem.getIsDeploy()), TeDetectionItem::getIsDeploy, teDetectionItem.getIsDeploy()) .eq(StrUtil.isNotBlank(teDetectionItem.getStatus()), TeDetectionItem::getStatus, teDetectionItem.getStatus())
.eq(StringUtils.isNotBlank(teDetectionItem.getDetectVersion()), TeDetectionItem::getDetectVersion, teDetectionItem.getDetectVersion()); .eq(StrUtil.isNotBlank(teDetectionItem.getIsDeploy()), TeDetectionItem::getIsDeploy, teDetectionItem.getIsDeploy())
.eq(StrUtil.isNotBlank(teDetectionItem.getDetectVersion()), TeDetectionItem::getDetectVersion, teDetectionItem.getDetectVersion())
.orderByDesc(TeDetectionItem::getCreateTime);
// 执行查询并返回结果 // 执行查询并返回结果
return this.baseMapper.selectJoinList(TeDetectionItemVO.class, queryWrapper); return this.baseMapper.selectJoinList(TeDetectionItemVO.class, queryWrapper);
@ -180,4 +183,11 @@ public class TeDetectionItemServiceImpl extends ServiceImpl<TeDetectionItemMappe
public int deleteTeDetectionItemById(String id) { public int deleteTeDetectionItemById(String id) {
return this.baseMapper.deleteById(id); return this.baseMapper.deleteById(id);
} }
@Override
public TeDetectionItem selectTeDetectionItemBySceneType(String sceneType) {
LambdaQueryWrapper<TeDetectionItem> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(TeDetectionItem::getSceneCode, sceneType);
return this.getOne(queryWrapper);
}
} }

View File

@ -23,6 +23,12 @@
<artifactId>cmvr-iot-common</artifactId> <artifactId>cmvr-iot-common</artifactId>
</dependency> </dependency>
<!-- 测试流程-->
<dependency>
<groupId>com.cmvr</groupId>
<artifactId>cmvr-iot-test</artifactId>
</dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -0,0 +1,6 @@
package com.cmvr.vi.enums;
public enum ViCorpusEnum {
WAKEUP, //唤醒
TEST //测试
}

View File

@ -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<ViProject> {
}

View File

@ -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<ViScheme> {
}

View File

@ -8,83 +8,55 @@ import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty; import io.swagger.annotations.ApiModelProperty;
import lombok.Data; import lombok.Data;
/**
* 语料库对象 vi_corpus
*
* @author cmvr-iot
*/
@Data @Data
@ApiModel("料库对象") @ApiModel("语音交互--语料库")
public class ViCorpus extends BaseEntity { public class ViCorpus extends BaseEntity {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/**
* 语料ID
*/
@ApiModelProperty("语料ID") @ApiModelProperty("语料ID")
@TableId(type = IdType.AUTO) @TableId(type = IdType.AUTO)
private Long corpusId; private Long corpusId;
/** @ApiModelProperty("测试语料-连续对话的父ID")
* 语料名称 private String parentId;
*/
@Excel(name = "语料名称") @Excel(name = "语料名称")
@ApiModelProperty("语料名称") @ApiModelProperty("语料名称")
private String corpusName; private String corpusName;
/**
* 语料类型WAKEUP唤醒 / TEST测试
*/
@Excel(name = "语料类型", readConverterExp = "WAKEUP唤醒,TEST测试") @Excel(name = "语料类型", readConverterExp = "WAKEUP唤醒,TEST测试")
@ApiModelProperty("语料类型") @ApiModelProperty("语料类型")
private String type; private String type;
/**
* 语音对应的文本
*/
@Excel(name = "语音对应的文本") @Excel(name = "语音对应的文本")
@ApiModelProperty("语音对应的文本") @ApiModelProperty("语音对应的文本")
private String textContent; private String textContent;
/**
* 语音文件存放路径
*/
@Excel(name = "语音文件存放路径") @Excel(name = "语音文件存放路径")
@ApiModelProperty("语音文件存放路径") @ApiModelProperty("语音文件存放路径")
private String audioPath; private String audioPath;
/**
* 音色
*/
@Excel(name = "音色") @Excel(name = "音色")
@ApiModelProperty("音色") @ApiModelProperty("音色")
private String voiceType; private String voiceType;
/**
* 方言
*/
@Excel(name = "方言") @Excel(name = "方言")
@ApiModelProperty("方言") @ApiModelProperty("方言")
private String dialect; private String dialect;
/** // @Excel(name = "预期结果(预留)")
* 预期结果预留
*/
// @Excel(name = "预期结果(预留)")
@ApiModelProperty("预期结果(预留)") @ApiModelProperty("预期结果(预留)")
private String expectedResult; private String expectedResult;
/**
* 同一父语料下的顺序
*/
@Excel(name = "同一父语料下的顺序") @Excel(name = "同一父语料下的顺序")
@ApiModelProperty("同一父语料下的顺序") @ApiModelProperty("同一父语料下的顺序")
private Integer sortOrder; private Integer sortOrder;
/** @Excel(name = "是否连续0连续 1单次")
* 状态0正常 1停用 @ApiModelProperty("是否连续0连续 1单次,测试语料时使用")
*/ private Integer continuous = 1;
@Excel(name = "状态", readConverterExp = "0=正常,1=停用") @Excel(name = "状态", readConverterExp = "0=正常,1=停用")
@ApiModelProperty(value = "状态", notes = "0=正常,1=停用") @ApiModelProperty(value = "状态", notes = "0=正常,1=停用")
private String status; private String status;

View File

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

View File

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

View File

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

View File

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

View File

@ -2,6 +2,8 @@ package com.cmvr.vi.service;
import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.IService;
import com.cmvr.vi.model.domain.ViCorpus; 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; import java.util.List;
@ -22,10 +24,18 @@ public interface IViCorpusService extends IService<ViCorpus> {
/** /**
* 查询语料库列表 * 查询语料库列表
* *
* @param viCorpus 语料库 * @param viCorpusVO 语料库
* @return 语料库集合 * @return 语料库集合
*/ */
public List<ViCorpus> selectViCorpusList(ViCorpus viCorpus); public List<ViCorpus> selectViCorpusList(ViCorpusVO viCorpusVO);
/**
* 查询连续对话列表
*
* @param viCorpusVO 语料库
* @return 语料库集合
*/
public List<ViContinuousCorpusVO> selectContinuousList(ViCorpusVO viCorpusVO);
/** /**
* 新增语料库 * 新增语料库
@ -35,6 +45,14 @@ public interface IViCorpusService extends IService<ViCorpus> {
*/ */
public int insertViCorpus(ViCorpus viCorpus); public int insertViCorpus(ViCorpus viCorpus);
/**
* 批量新增语料库
*
* @param viCorpus 语料库
* @return 结果
*/
public boolean insertBatch(List<ViCorpus> viCorpus);
/** /**
* 修改语料库 * 修改语料库
* *
@ -51,4 +69,20 @@ public interface IViCorpusService extends IService<ViCorpus> {
*/ */
public int deleteViCorpusByCorpusIds(Long[] corpusIds); public int deleteViCorpusByCorpusIds(Long[] corpusIds);
/**
* 批量新增/修改语料库
*/
int insertOrUpdateBatch(List<ViCorpus> data);
/**
* 新增连续对话
*/
boolean insertContinuousViCorpus(ViContinuousCorpusVO viContinuousCorpusVO);
/**
* 修改连续对话
*/
int updateContinuousViCorpus(ViContinuousCorpusVO viContinuousCorpusVO);
int deleteViCorpusByParentIds(String[] parentIds);
} }

View File

@ -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<ViProject> {
/**
* 查询语音交互-项目
*
* @param projectId 语音交互-项目主键
* @return 语音交互-项目
*/
public ViProject selectViProjectByProjectId(String projectId);
/**
* 查询语音交互-项目列表
*
* @param viProject 语音交互-项目
* @return 语音交互-项目集合
*/
public List<ViProject> 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);
}

View File

@ -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<ViScheme> {
/**
* 查询语音交互-方案
*
* @param schemeId 语音交互-方案主键
* @return 语音交互-方案
*/
public ViScheme selectViSchemeBySchemeId(String schemeId);
/**
* 查询语音交互-方案列表
*
* @param viScheme 语音交互-方案
* @return 语音交互-方案集合
*/
public List<ViScheme> 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);
}

View File

@ -1,15 +1,27 @@
package com.cmvr.vi.service.impl; 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.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 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.mapper.ViCorpusMapper;
import com.cmvr.vi.model.domain.ViCorpus; 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.cmvr.vi.service.IViCorpusService;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/** /**
* 语料库Service业务层处理 * 语料库Service业务层处理
@ -19,61 +31,193 @@ import java.util.List;
@Service @Service
public class IViCorpusServiceImpl extends ServiceImpl<ViCorpusMapper, ViCorpus> implements IViCorpusService { public class IViCorpusServiceImpl extends ServiceImpl<ViCorpusMapper, ViCorpus> implements IViCorpusService {
/**
* 查询语料库
*
* @param corpusId 语料库主键
* @return 语料库
*/
@Override @Override
public ViCorpus selectViCorpusByCorpusId(Long corpusId) { public ViCorpus selectViCorpusByCorpusId(Long corpusId) {
return this.getById(corpusId); return this.getById(corpusId);
} }
/**
* 查询语料库列表
*
* @param viCorpus 语料库
* @return 语料库
*/
@Override @Override
public List<ViCorpus> selectViCorpusList(ViCorpus viCorpus) { public List<ViCorpus> selectViCorpusList(ViCorpusVO viCorpusVO) {
LambdaQueryWrapper<ViCorpus> wrapper = Wrappers.lambdaQuery(); LambdaQueryWrapper<ViCorpus> 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); return this.list(wrapper);
} }
/**
* 新增语料库 @Override
* public List<ViContinuousCorpusVO> selectContinuousList(ViCorpusVO viCorpusVO) {
* @param viCorpus 语料库 // 查询父级
* @return 结果 MPJLambdaWrapper<ViCorpus> 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<ViContinuousCorpusVO> list = this.baseMapper.selectJoinList(ViContinuousCorpusVO.class, wrapper);
// 查询 children parentId 关联
for (ViContinuousCorpusVO vo : list) {
List<ViCorpus> 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 @Override
public int insertViCorpus(ViCorpus viCorpus) { public int insertViCorpus(ViCorpus viCorpus) {
viCorpus.setParentId(IdUtils.fastSimpleUUID());
return this.baseMapper.insert(viCorpus); return this.baseMapper.insert(viCorpus);
} }
/** @Override
* 修改语料库 public boolean insertBatch(List<ViCorpus> data) {
* data.forEach(v -> v.setParentId(IdUtils.fastSimpleUUID()));
* @param viCorpus 语料库 return this.saveBatch(data);
* @return 结果 }
*/
@Override @Override
public int updateViCorpus(ViCorpus viCorpus) { public int updateViCorpus(ViCorpus viCorpus) {
return this.baseMapper.updateById(viCorpus); return this.baseMapper.updateById(viCorpus);
} }
/**
* 批量删除语料库
*
* @param corpusIds 需要删除的语料库主键
* @return 结果
*/
@Override @Override
public int deleteViCorpusByCorpusIds(Long[] corpusIds) { public int deleteViCorpusByCorpusIds(Long[] corpusIds) {
return this.baseMapper.deleteBatchIds(Arrays.asList(corpusIds)); return this.baseMapper.deleteBatchIds(Arrays.asList(corpusIds));
} }
@Override
public int insertOrUpdateBatch(List<ViCorpus> data) {
if (CollUtil.isEmpty(data)) {
throw new IllegalArgumentException("data must not be null");
}
// 非空 id 更新
List<ViCorpus> updateList = data.stream()
.filter(item -> item.getCorpusId() != null)
.collect(Collectors.toList());
// id 新增
List<ViCorpus> 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<ViCorpus> 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<Long> dbIds = this.list(new LambdaQueryWrapper<ViCorpus>()
.select(ViCorpus::getCorpusId)
.eq(ViCorpus::getParentId, parentId))
.stream()
.map(ViCorpus::getCorpusId)
.collect(Collectors.toList());
List<ViCorpus> children = viContinuousCorpusVO.getChildren();
// 新增更新保留
List<ViCorpus> addList = new ArrayList<>();
List<ViCorpus> updateList = new ArrayList<>();
Set<Long> 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<Long> 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<ViCorpus>()
.in(ViCorpus::getParentId, Arrays.asList(parentIds))
);
}
} }

View File

@ -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<ViProjectMapper, ViProject> implements IViProjectService {
@Override
public ViProject selectViProjectByProjectId(String projectId) {
return this.getById(projectId);
}
@Override
public List<ViProject> selectViProjectList(ViProject viProject) {
LambdaQueryWrapper<ViProject> 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 "";
}
}

View File

@ -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<ViSchemeMapper, ViScheme> implements IViSchemeService {
private final ITeTaskOrchestrationService teTaskOrchestrationService;
private final ITeDetectionItemService detectionItemService;
@Override
public ViScheme selectViSchemeBySchemeId(String schemeId) {
return this.getById(schemeId);
}
@Override
public List<ViScheme> selectViSchemeList(ViScheme viScheme) {
LambdaQueryWrapper<ViScheme> 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<TeQueryTaskOrchestraItemVO> 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<String> schemeIdList = Arrays.asList(schemeIds);
// 查要删除的方案获取对应 taskId 和流程 itemId
List<ViScheme> schemes = this.baseMapper.selectBatchIds(schemeIdList);
int delete = this.baseMapper.deleteBatchIds(schemeIdList);
// 每个 taskId 单独归一化一次
Map<String, List<ViScheme>> taskSchemeMap = schemes.stream()
.collect(Collectors.groupingBy(ViScheme::getProjectId));
for (String taskId : taskSchemeMap.keySet()) {
List<TeQueryTaskOrchestraItemVO> items = teTaskOrchestrationService.queryByTaskId(taskId);
if (items != null && !items.isEmpty()) {
// 找出要删的流程 itemId
List<String> 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<TeQueryTaskOrchestraItemVO> 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<String> itemIds = items.stream()
.map(TeQueryTaskOrchestraItemVO::getItemId)
.collect(Collectors.toList());
TeTaskOrchestraVO orchestraVO = new TeTaskOrchestraVO();
orchestraVO.setTaskId(taskId);
orchestraVO.setItemIds(itemIds);
teTaskOrchestrationService.insertTeTaskOrchestration(orchestraVO);
}
}