feat: 语音交互项目执行、可视化

This commit is contained in:
stream 2025-09-25 16:44:08 +08:00
parent 43a3c416a7
commit f60db6cd85
12 changed files with 185 additions and 116 deletions

View File

@ -67,9 +67,21 @@ public class ViProjectController extends BaseController {
return toAjax(viProjectService.deleteViProjectByProjectIds(projectIds));
}
@ApiOperation("查询项目执行时运行参数")
@GetMapping("/queryExecuteRunParams")
public AjaxResult queryExecuteRunParams(@RequestParam("projectId") String projectId) {
return AjaxResult.ok(viProjectService.queryExecuteRunParams(projectId));
}
@ApiOperation("执行项目")
@PostMapping("/execute")
public AjaxResult execute(@RequestBody TeTaskExecuteProjectVO taskExecuteProjectVO) {
return AjaxResult.ok(viProjectService.execute(taskExecuteProjectVO));
return AjaxResult.ok(viProjectService.executeProject(taskExecuteProjectVO));
}
@ApiOperation("查询最近一次项目执行实例id")
@GetMapping("/queryExecuteInstId/{projectId}")
public AjaxResult queryExecuteInstId(@PathVariable("projectId") String projectId) {
return AjaxResult.ok(viProjectService.queryExecuteInstId(projectId));
}
}

View File

@ -1,11 +1,31 @@
package com.cmvr.test.enums;
import com.cmvr.common.exception.GlobalException;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 流程场景类型
*/
@Getter
@AllArgsConstructor
public enum FlowSceneTypeEnum {
NORMAL, // 普通
VI_WAKEUP_CORPUS, // 语音交互-唤醒场景
VI_SINGLE_CORPUS, // 语音交互-单次对话场景
VI_CONTINUOUS_CORPUS // 语音交互-连续对话场景
}
NORMAL(0, "普通流程场景", "NORMAL"),
VI_WAKEUP_CORPUS(1, "语音交互-唤醒场景", "VI_WAKEUP_CORPUS"),
VI_SINGLE_CORPUS(2, "语音交互-单次对话场景", "VI_SINGLE_CORPUS"),
VI_CONTINUOUS_CORPUS(3, "语音交互-连续对话场景", "VI_CONTINUOUS_CORPUS");
private final int code; // 数字类型
private final String desc; // 描述
private final String sceneCode; // 数据库里存的 sceneCode
// 根据 code 获取枚举
public static FlowSceneTypeEnum fromCode(int code) {
for (FlowSceneTypeEnum e : values()) {
if (e.code == code) {
return e;
}
}
throw new GlobalException("场景类型参数错误: " + code);
}
}

View File

@ -5,7 +5,6 @@ import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.cmvr.common.exception.GlobalException;
import com.cmvr.test.enums.ActionEnum;
@ -45,47 +44,19 @@ public class FlowTaskRuntimeEntry implements FlowTaskRuntimeService {
@Transactional(rollbackFor = Exception.class)
public String executeTask(TeTaskExecuteNormalVO taskExecuteNormalVO) {
String terminalId = taskExecuteNormalVO.getTerminalId();
String taskId = taskExecuteNormalVO.getTaskId();
// 查询任务详情检测项信息
List<TeQueryTaskDetailDTO> details = taskOrchestrationService.queryTaskDetail(taskId);
int count = (int) details.stream().filter(dto -> ObjUtil.equals(dto.getIsDeploy(), "1")).count();
if (count > 0) {
throw new GlobalException("当前任务存在未发布的检测项!");
}
// 检查终端状态
if (taskInstHolder.isTerminalLocked(terminalId)) {
throw new GlobalException("当前终端正在执行其他任务,请稍后再试");
}
// 创建任务实例
TeTaskInst instance = createAndSaveTaskInstance(taskId, terminalId, RunModeEnum.NORMAL);
String instId = instance.getId();
try {
// 注册上下文
registerTaskContext(instId, taskId, terminalId, null, RunModeEnum.NORMAL, taskExecuteNormalVO.getRunParams());
// 异步提交执行
flowTaskAsyncDispatcher.submit(instId, taskId, terminalId, RunModeEnum.NORMAL, details);
return instId;
} catch (Exception e) {
taskInstHolder.markFailed(
instId, taskId, null, null, null,
ActionEnum.NONE.getOperate(), ActionEnum.NONE.getAction(),
JSON.toJSONString(taskExecuteNormalVO),
"任务执行异常:" + StrUtil.sub(ExceptionUtil.getMessage(e), 0, 480)
);
throw new GlobalException("任务执行异常:" + e.getMessage());
}
return executeTaskInternal(
taskExecuteNormalVO.getTerminalId(),
taskExecuteNormalVO.getTaskId(),
RunModeEnum.NORMAL,
taskExecuteNormalVO.getRunParams(),
taskExecuteNormalVO
);
}
@Transactional(rollbackFor = Exception.class)
public String executeTrialTask(TeTaskExecuteTrailVO taskExecuteTrailVO) {
JSONObject runParams = taskExecuteTrailVO.getRunParams();
String terminalId = runParams.getString("terminalId");
String terminalId = taskExecuteTrailVO.getTerminalId();
if (StrUtil.isEmpty(terminalId)) {
throw new GlobalException("终端ID不能为空");
}
@ -137,45 +108,50 @@ public class FlowTaskRuntimeEntry implements FlowTaskRuntimeService {
@Transactional(rollbackFor = Exception.class)
public String executeProjectTask(TeTaskExecuteProjectVO taskExecuteProjectVO) {
JSONObject runParams = taskExecuteProjectVO.getRunParams();
String terminalId = runParams.getString("terminalId");
return executeTaskInternal(
taskExecuteProjectVO.getTerminalId(),
taskExecuteProjectVO.getProjectId(),
RunModeEnum.PROJECT,
taskExecuteProjectVO.getRunParams(),
taskExecuteProjectVO
);
}
@Transactional(rollbackFor = Exception.class)
public String executeTaskInternal(String terminalId, String taskId,
RunModeEnum runMode, JSONObject runParams, Object originalVO) {
if (StrUtil.isEmpty(terminalId)) {
throw new GlobalException("终端ID不能为空");
}
// 检查终端状态
if (taskInstHolder.isTerminalLocked(terminalId)) {
throw new GlobalException("当前终端正在执行其他任务,请稍后再试");
}
String taskId = taskExecuteProjectVO.getProjectId();
// 查询任务详情方案对应流程信息
// 查询任务详情检测项信息
List<TeQueryTaskDetailDTO> details = taskOrchestrationService.queryTaskDetail(taskId);
JSONArray prjInfo = taskExecuteProjectVO.getPrjInfo();
for (int i = 0; i < details.size(); i++) {
JSONObject jsonObject = prjInfo.getJSONObject(i);
TeQueryTaskDetailDTO detail = details.get(i);
detail.setSchemeInfo(jsonObject);
int count = (int) details.stream().filter(dto -> ObjUtil.equals(dto.getIsDeploy(), "1")).count();
if (count > 0) {
throw new GlobalException("当前任务存在未发布的检测项!");
}
// 创建并保存任务实例
TeTaskInst instance = createAndSaveTaskInstance(taskId, terminalId, RunModeEnum.PROJECT);
// 创建任务实例
TeTaskInst instance = createAndSaveTaskInstance(taskId, terminalId, runMode);
String instId = instance.getId();
try {
// 注册上下文
registerTaskContext(instId, taskId, terminalId, null, RunModeEnum.PROJECT, taskExecuteProjectVO.getRunParams());
registerTaskContext(instId, taskId, terminalId, null, runMode, runParams);
// 异步提交执行
flowTaskAsyncDispatcher.submit(instId, taskId, terminalId, RunModeEnum.PROJECT, details);
flowTaskAsyncDispatcher.submit(instId, taskId, terminalId, runMode, details);
return instId;
} catch (Exception e) {
taskInstHolder.markFailed(
instId, taskId, null, null, null,
ActionEnum.NONE.getOperate(), ActionEnum.NONE.getAction(),
JSON.toJSONString(taskExecuteProjectVO),
"项目执行异常:" + StrUtil.sub(ExceptionUtil.getMessage(e), 0, 480)
JSON.toJSONString(originalVO),
"任务执行异常:" + StrUtil.sub(ExceptionUtil.getMessage(e), 0, 480)
);
throw new GlobalException("项目执行异常:" + e.getMessage());
throw new GlobalException("任务执行异常:" + e.getMessage());
}
}

View File

@ -8,7 +8,9 @@ import com.cmvr.common.core.domain.BaseEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
@ApiModel("任务配置信息")
public class TeTaskConfigInfo extends BaseEntity {

View File

@ -0,0 +1,17 @@
package com.cmvr.test.model.vo;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
@EqualsAndHashCode(callSuper = true)
@Data
public class TeQueryProjectOrchestraItemVO extends TeQueryTaskOrchestraItemVO{
@ApiModelProperty("检测项ID")
private String schemeId;
@ApiModelProperty("检测项名称")
private String schemeName;
}

View File

@ -1,6 +1,5 @@
package com.cmvr.test.model.vo;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
@ -16,9 +15,10 @@ public class TeTaskExecuteProjectVO {
@NotEmpty(message = "项目ID不能为空")
private String projectId;
@ApiModelProperty("终端ID")
@NotEmpty(message = "终端ID不能为空")
private String terminalId;
@ApiModelProperty(value = "运行时参数")
private JSONObject runParams = new JSONObject();
@ApiModelProperty(value = "项目运行时方案及语料信息")
private JSONArray prjInfo;
}

View File

@ -19,6 +19,10 @@ public class TeTaskExecuteTrailVO {
@NotEmpty(message = "流程JSON数据不可为空")
private String flowData;
@ApiModelProperty("终端ID")
@NotEmpty(message = "终端ID不能为空")
private String terminalId;
@ApiModelProperty(value = "运行时参数")
private JSONObject runParams = new JSONObject();
}

View File

@ -49,7 +49,7 @@ public class TeTaskConfigInfoServiceImpl extends ServiceImpl<TeTaskConfigInfoMap
.select(SysGroup::getGroupId)
.select(SysGroup::getGroupName)
.leftJoin(SysGroup.class, SysGroup::getGroupId, TeTaskConfigInfo::getGroupId)
.eq(TeTaskConfigInfo::getRunMode, RunModeEnum.NORMAL)
.eq(TeTaskConfigInfo::getRunMode, RunModeEnum.NORMAL) // 只查询普通
.like(StringUtils.isNotBlank(teTaskConfigInfo.getTaskDesc()), TeTaskConfigInfo::getTaskDesc, teTaskConfigInfo.getTaskDesc())
.like(StringUtils.isNotBlank(teTaskConfigInfo.getTaskName()), TeTaskConfigInfo::getTaskName, teTaskConfigInfo.getTaskName())
.eq(StringUtils.isNotBlank(teTaskConfigInfo.getGroupId()), TeTaskConfigInfo::getGroupId, teTaskConfigInfo.getGroupId())

View File

@ -7,9 +7,11 @@ import com.cmvr.common.core.domain.BaseEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.Date;
@EqualsAndHashCode(callSuper = true)
@Data
@ApiModel("语音交互--项目表")
public class ViProject extends BaseEntity {
@ -72,7 +74,6 @@ public class ViProject extends BaseEntity {
@ApiModelProperty("送样日期")
private Date sampleDate;
@Excel(name = "状态", readConverterExp = "0=正常,1=停用")
@ApiModelProperty(value = "状态", notes = "0=正常,1=停用")
@ApiModelProperty(value = "状态", notes = "状态0已执行 1未执行")
private String status;
}

View File

@ -1,7 +1,7 @@
package com.cmvr.vi.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.cmvr.test.model.vo.TeTaskExecuteNormalVO;
import com.cmvr.test.model.vo.TeQueryProjectOrchestraItemVO;
import com.cmvr.test.model.vo.TeTaskExecuteProjectVO;
import com.cmvr.vi.model.domain.ViProject;
@ -53,8 +53,19 @@ public interface IViProjectService extends IService<ViProject> {
*/
public int deleteViProjectByProjectIds(String[] projectIds);
/**
* 查询项目执行时运行参数
*/
public List<TeQueryProjectOrchestraItemVO> queryExecuteRunParams(String projectId);
/**
* 执行项目
*/
public String execute(TeTaskExecuteProjectVO taskExecuteProjectVO);
public String executeProject(TeTaskExecuteProjectVO taskExecuteProjectVO);
/**
* 查询最近一次项目执行记录
*/
public String queryExecuteInstId(String projectId);
}

View File

@ -1,30 +1,34 @@
package com.cmvr.vi.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
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.enums.RunModeEnum;
import com.cmvr.test.flow.runtime.engine.FlowTaskRuntimeService;
import com.cmvr.test.model.domain.TeDetectionItem;
import com.cmvr.test.model.domain.TeTaskConfigInfo;
import com.cmvr.test.model.domain.TeTaskInst;
import com.cmvr.test.model.vo.TeQueryProjectOrchestraItemVO;
import com.cmvr.test.model.vo.TeTaskExecuteNormalVO;
import com.cmvr.test.model.vo.TeTaskExecuteProjectVO;
import com.cmvr.test.service.ITeDetectionItemService;
import com.cmvr.test.service.ITeTaskConfigInfoService;
import com.cmvr.test.service.ITeTaskInstService;
import com.cmvr.vi.mapper.ViProjectMapper;
import com.cmvr.vi.model.domain.ViCorpus;
import com.cmvr.vi.model.domain.ViProject;
import com.cmvr.vi.model.domain.ViScheme;
import com.cmvr.vi.service.IViCorpusService;
import com.cmvr.vi.service.IViProjectService;
import com.cmvr.vi.service.IViSchemeService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
@ -38,8 +42,9 @@ public class ViProjectServiceImpl extends ServiceImpl<ViProjectMapper, ViProject
private final FlowTaskRuntimeService flowTaskRuntimeService;
private final IViSchemeService viSchemeService;
private final IViCorpusService viCorpusService;
private final ITeDetectionItemService detectionItemService;
private final ITeTaskConfigInfoService taskConfigInfoService;
private final ITeTaskInstService taskInstService;
@Override
public ViProject selectViProjectByProjectId(String projectId) {
@ -78,33 +83,68 @@ public class ViProjectServiceImpl extends ServiceImpl<ViProjectMapper, ViProject
}
@Override
public String execute(TeTaskExecuteProjectVO taskExecuteProjectVO) {
String projectId = taskExecuteProjectVO.getProjectId();
// 查询方案
public List<TeQueryProjectOrchestraItemVO> queryExecuteRunParams(String projectId) {
// 方案信息
List<ViScheme> viSchemes = viSchemeService.querySchemesByProjectId(projectId);
JSONArray jsonArray = new JSONArray();
for (ViScheme scheme : viSchemes) {
// 唤醒语料
ViCorpus wakeCorpus = viCorpusService.queryCorpusByParentId(scheme.getWakeId());
List<TeQueryProjectOrchestraItemVO> result = new ArrayList<>();
// 测试语料
List<ViCorpus> testCorpus = Collections.emptyList();
if (scheme.getSceneType() != 1) {
List<String> corpusIds = StrUtil.split(scheme.getCorpusIds(), StrUtil.COMMA);
if (CollUtil.isNotEmpty(corpusIds)) {
testCorpus = viCorpusService.queryCorpusInParentIds(corpusIds);
}
for (ViScheme scheme : viSchemes) {
// 根据场景类型查找对应检测项流程
String sceneTypeKey = FlowSceneTypeEnum.fromCode(scheme.getSceneType()).getSceneCode();
TeDetectionItem item = detectionItemService.selectTeDetectionItemBySceneType(sceneTypeKey);
if (item == null) {
continue;
}
JSONObject object = new JSONObject()
.fluentPut("scheme", scheme)
.fluentPut("wake", wakeCorpus)
.fluentPut("test", testCorpus);
TeQueryProjectOrchestraItemVO vo = new TeQueryProjectOrchestraItemVO();
vo.setSchemeId(scheme.getSchemeId());
vo.setSchemeName(scheme.getSchemeName());
vo.setItemId(item.getId());
vo.setConfig(item.getConfig());
vo.setItemName(item.getDetectName());
vo.setOrderNum(scheme.getSort());
jsonArray.fluentAdd(object);
result.add(vo);
}
taskExecuteProjectVO.setPrjInfo(jsonArray);
return flowTaskRuntimeService.executeProjectTask(taskExecuteProjectVO);
// 重新归一化 1..N
for (int i = 0; i < result.size(); i++) {
result.get(i).setOrderNum(i + 1);
}
return result;
}
@Override
public String executeProject(TeTaskExecuteProjectVO taskExecuteProjectVO) {
TeTaskExecuteNormalVO normalVO = new TeTaskExecuteNormalVO();
normalVO.setTaskId(taskExecuteProjectVO.getProjectId());
normalVO.setRunParams(taskExecuteProjectVO.getRunParams());
normalVO.setTerminalId(taskExecuteProjectVO.getTerminalId());
String instId = flowTaskRuntimeService.executeTask(normalVO);
this.update(
new LambdaUpdateWrapper<ViProject>()
.eq(ViProject::getProjectId, taskExecuteProjectVO.getProjectId())
.set(ViProject::getStatus, "0")
);
return instId;
}
@Override
public String queryExecuteInstId(String projectId) {
LambdaQueryWrapper<ViProject> wrapperProject = Wrappers.lambdaQuery(ViProject.class);
wrapperProject.eq(ViProject::getProjectId, projectId)
.eq(ViProject::getStatus, "0");
ViProject project = this.getOne(wrapperProject);
if (ObjUtil.isEmpty(project)) {
throw new GlobalException(StrUtil.format("项目id为 [{}] 的任务不存在或未执行!", projectId));
}
LambdaQueryWrapper<TeTaskInst> wrapper = Wrappers.lambdaQuery(TeTaskInst.class);
wrapper.eq(TeTaskInst::getTaskId, projectId)
.orderByDesc(TeTaskInst::getCreateTime)
.last("LIMIT 1");
TeTaskInst latestTaskInst = taskInstService.getOne(wrapper);
return latestTaskInst.getId();
}
}

View File

@ -5,7 +5,6 @@ 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;
@ -35,7 +34,7 @@ import java.util.stream.Collectors;
public class ViSchemeServiceImpl extends ServiceImpl<ViSchemeMapper, ViScheme> implements IViSchemeService {
private final ITeTaskOrchestrationService teTaskOrchestrationService;
private final ITeDetectionItemService detectionItemService;
private final ITeDetectionItemService detectionItemService;
@Override
public ViScheme selectViSchemeBySchemeId(String schemeId) {
@ -61,14 +60,7 @@ public class ViSchemeServiceImpl extends ServiceImpl<ViSchemeMapper, ViScheme> i
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("场景类型参数错误!");
}
String key = FlowSceneTypeEnum.fromCode(viScheme.getSceneType()).getSceneCode();
TeDetectionItem item = detectionItemService.selectTeDetectionItemBySceneType(key);
String itemId = item.getId();
@ -122,13 +114,7 @@ public class ViSchemeServiceImpl extends ServiceImpl<ViSchemeMapper, ViScheme> i
// 找出要删的流程 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("场景类型参数错误!");
}
String key = FlowSceneTypeEnum.fromCode(s.getSceneType()).getSceneCode();
return detectionItemService.selectTeDetectionItemBySceneType(key).getId();
})
.collect(Collectors.toList());