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.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<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获取语料库详细信息")
@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<ViCorpus> 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<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')")
@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<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.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<String> 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<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) {
throw new GlobalException(e.getMessage());
}
return filePath;
}
/**
* 将本地文件转换为 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() {
System.out.println("这是什么线程:" + Thread.currentThread().getName());
String id = findStartNodeId();
return getNode(id);
}

View File

@ -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<Integer> 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,

View File

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

View File

@ -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<Integer> 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<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 {
latch.await(); // 等待子图执行完毕后再进行下一轮

View File

@ -53,12 +53,11 @@ public class FlowItemExecutor {
String startNodeId = graph.findStartNodeId();
List<FlowParamDef> 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<Integer> iterations) {
// 主图中开始节点的 inputParams
List<FlowParamDef> 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<FlowParamDef> startInputDefs,
TaskNodeExecuteMessage rootMessage,
Runnable onFinished,
int loopIteration) {
List<Integer> 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<Integer> 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<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);
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<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();
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);
}

View File

@ -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<String> 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<Integer> iterations) {
String instId = context.getInstId();
FlowNodeWrapper startNode = graph.findSubStartNodeId();
List<String> 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<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,
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<Integer> 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<Integer> iterations) {
String key = FlowSchedulerCache.buildKey(instId, nodeId, loopIteration);
String key = FlowSchedulerCache.buildKey(instId, nodeId, iterations);
if (schedulerCache.trySchedule(key)) {
executor.submit(() -> {
try {

View File

@ -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<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重入
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();

View File

@ -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<Integer> iterations = new ArrayList<>();
}

View File

@ -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<Integer> iterations = new ArrayList<>();
/**
* 节点名称
*/
private String nodeName;
/** 节点类型START、FUNCTION、END 等) */
/**
* 节点类型STARTFUNCTIONEND
*/
private String nodeType;
/** 节点行为(动作标识符) */
/**
* 节点行为动作标识符
*/
private ActionEnum action;
/** 是否试运行 */
/**
* 是否试运行
*/
private boolean trial;
/** 所属终端ID */
/**
* 所属终端ID
*/
private String terminalId;
/** 节点入参 */
/**
* 节点入参
*/
private JSONObject inputParams;
/** 上游输出参数(来自上一个节点的输出) */
/**
* 上游输出参数来自上一个节点的输出
*/
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";
// }
// 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)) {

View File

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

View File

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

View File

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

View File

@ -31,10 +31,11 @@ public interface ITeNodeInstService extends IService<TeNodeInst> {
* @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<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 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<TeNodeInstMapper, TeNode
@Override
public 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<Integer> iterations) {
TeNodeInst record = new TeNodeInst();
record.setInstId(instId);
@ -47,6 +48,7 @@ public class ITeNodeInstServiceImpl extends ServiceImpl<TeNodeInstMapper, TeNode
record.setParamsIn(paramsIn);
record.setParamsOut("{}");
record.setStartTime(System.currentTimeMillis());
record.setIteration(getIteration(iterations));
this.save(record);
}
@ -113,19 +115,26 @@ public class ITeNodeInstServiceImpl extends ServiceImpl<TeNodeInstMapper, TeNode
}
@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();
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<Integer> iterations) {
return StrUtil.join(StrUtil.DASHED, iterations);
}
@Override
public void update(String instId, String nodeId, TaskStatusEnum nodeStatus) {
LambdaUpdateWrapper<TeNodeInst> 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);
}
}

View File

@ -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<TeDetectionItemMappe
.select(SysGroup::getGroupId)
.select(SysGroup::getGroupName)
.leftJoin(SysGroup.class, SysGroup::getGroupId, TeDetectionItem::getGroupId)
.like(StringUtils.isNotBlank(teDetectionItem.getDetectName()), TeDetectionItem::getDetectName, teDetectionItem.getDetectName())
.like(StringUtils.isNotBlank(teDetectionItem.getDetectDesc()), TeDetectionItem::getDetectDesc, teDetectionItem.getDetectDesc())
.eq(StringUtils.isNotBlank(teDetectionItem.getStatus()), TeDetectionItem::getStatus, teDetectionItem.getStatus())
.eq(StringUtils.isNotBlank(teDetectionItem.getIsDeploy()), TeDetectionItem::getIsDeploy, teDetectionItem.getIsDeploy())
.eq(StringUtils.isNotBlank(teDetectionItem.getDetectVersion()), TeDetectionItem::getDetectVersion, teDetectionItem.getDetectVersion());
.eq(StrUtil.isNotBlank(teDetectionItem.getSceneCode()), TeDetectionItem::getSceneCode, teDetectionItem.getSceneCode())
.like(StrUtil.isNotBlank(teDetectionItem.getDetectName()), TeDetectionItem::getDetectName, teDetectionItem.getDetectName())
.like(StrUtil.isNotBlank(teDetectionItem.getDetectDesc()), TeDetectionItem::getDetectDesc, teDetectionItem.getDetectDesc())
.eq(StrUtil.isNotBlank(teDetectionItem.getStatus()), TeDetectionItem::getStatus, teDetectionItem.getStatus())
.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);
@ -180,4 +183,11 @@ public class TeDetectionItemServiceImpl extends ServiceImpl<TeDetectionItemMappe
public int deleteTeDetectionItemById(String 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>
</dependency>
<!-- 测试流程-->
<dependency>
<groupId>com.cmvr</groupId>
<artifactId>cmvr-iot-test</artifactId>
</dependency>
</dependencies>
</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 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;

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.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<ViCorpus> {
/**
* 查询语料库列表
*
* @param viCorpus 语料库
* @param viCorpusVO 语料库
* @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);
/**
* 批量新增语料库
*
* @param viCorpus 语料库
* @return 结果
*/
public boolean insertBatch(List<ViCorpus> viCorpus);
/**
* 修改语料库
*
@ -51,4 +69,20 @@ public interface IViCorpusService extends IService<ViCorpus> {
*/
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;
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<ViCorpusMapper, ViCorpus> implements IViCorpusService {
/**
* 查询语料库
*
* @param corpusId 语料库主键
* @return 语料库
*/
@Override
public ViCorpus selectViCorpusByCorpusId(Long corpusId) {
return this.getById(corpusId);
}
/**
* 查询语料库列表
*
* @param viCorpus 语料库
* @return 语料库
*/
@Override
public List<ViCorpus> selectViCorpusList(ViCorpus viCorpus) {
public List<ViCorpus> selectViCorpusList(ViCorpusVO viCorpusVO) {
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);
}
/**
* 新增语料库
*
* @param viCorpus 语料库
* @return 结果
*/
@Override
public List<ViContinuousCorpusVO> selectContinuousList(ViCorpusVO viCorpusVO) {
// 查询父级
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
public int insertViCorpus(ViCorpus viCorpus) {
viCorpus.setParentId(IdUtils.fastSimpleUUID());
return this.baseMapper.insert(viCorpus);
}
/**
* 修改语料库
*
* @param viCorpus 语料库
* @return 结果
*/
@Override
public boolean insertBatch(List<ViCorpus> 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<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);
}
}