feat(agv): 添加AGV协议定义和重构命令接口

- 新增agv.proto文件定义AGV相关消息结构包括位姿、速度、电池状态等
- 新增统一2D/3D地图格式定义支持栅格、点云、体素等多种地图类型
- 重构agv_command.proto将原有命令接口替换为新的运行状态查询接口
- 添加导航控制、速度设置、地图管理等标准化命令接口
- 整合站点管理、路径规划、任务状态等AGV核心功能协议
- 引入运动约束选项和适配器参数扩展支持不同厂商设备特性
This commit is contained in:
lixiaolong 2026-07-09 16:01:36 +08:00
parent 46fb4fecde
commit 736c510270
41 changed files with 44456 additions and 94726 deletions

View File

@ -89,6 +89,12 @@
<groupId>com.cmvr</groupId>
<artifactId>cmvr-iot-aima</artifactId>
</dependency>
<!-- 智能座舱语音语料采集-->
<dependency>
<groupId>com.cmvr</groupId>
<artifactId>cmvr-iot-tts</artifactId>
</dependency>
</dependencies>
<properties>
<env>dev</env>

View File

@ -1,9 +1,8 @@
package com.cmvr.web.controller.api;
import cmvr.api.AgvCommand;
import cmvr.msgs.*;
import com.cmvr.common.core.domain.AjaxResult;
import com.cmvr.edge.client.model.EdgeCommonVO;
import com.cmvr.edge.client.model.agv.*;
import com.cmvr.edge.client.service.EdgeAgvService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
@ -14,11 +13,13 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* 边缘系统AGV控制器
*
* @author cmvr-iot
* @since 2026-07-01
* @since 2026-07-02
*/
@Api(tags = "边缘--AGV")
@RestController
@ -28,139 +29,225 @@ public class EdgeAgvController {
private final EdgeAgvService edgeAgvService;
@ApiOperation("获取AGV状态信息")
@GetMapping("/getStatusInfo")
public AjaxResult getStatusInfo(EdgeCommonVO vo) {
AgvCommand.AgvStatusInfo statusInfo = edgeAgvService.getStatusInfo(vo);
// 直接返回对象Jackson会自动序列化
return AjaxResult.ok(statusInfo);
@ApiOperation("获取AGV运行时状态")
@GetMapping("/getRuntimeState")
public AjaxResult getRuntimeState(EdgeCommonVO vo) {
Agv.AgvRuntimeState state = edgeAgvService.getRuntimeState(vo);
// 转换为Map返回
java.util.Map<String, Object> resultMap = new java.util.HashMap<>();
resultMap.put("timestamp", state.getTimestamp());
resultMap.put("mode", state.getMode());
resultMap.put("connected", state.getConnected());
resultMap.put("localized", state.getLocalized());
resultMap.put("moving", state.getMoving());
resultMap.put("fault", state.getFault());
resultMap.put("emergencyStopped", state.getEmergencyStopped());
// 位置信息
if (state.hasPose()) {
java.util.Map<String, Object> poseMap = new java.util.HashMap<>();
poseMap.put("x", state.getPose().getX());
poseMap.put("y", state.getPose().getY());
poseMap.put("theta", state.getPose().getTheta());
resultMap.put("pose", poseMap);
}
@ApiOperation("获取机器人位置")
@GetMapping("/getRobotLocation")
public AjaxResult getRobotLocation(EdgeCommonVO vo) {
AgvCommand.AgvRobotLocation location = edgeAgvService.getRobotLocation(vo);
return AjaxResult.ok(location);
// 速度信息
if (state.hasVelocity()) {
java.util.Map<String, Object> velocityMap = new java.util.HashMap<>();
velocityMap.put("vx", state.getVelocity().getVx());
velocityMap.put("vy", state.getVelocity().getVy());
velocityMap.put("wz", state.getVelocity().getWz());
resultMap.put("velocity", velocityMap);
}
@ApiOperation("获取地图状态")
@GetMapping("/getMapStatus")
public AjaxResult getMapStatus(EdgeCommonVO vo) {
AgvCommand.AgvMapStatus mapStatus = edgeAgvService.getMapStatus(vo);
return AjaxResult.ok(mapStatus);
// 电池信息
if (state.hasBattery()) {
java.util.Map<String, Object> batteryMap = new java.util.HashMap<>();
batteryMap.put("percentage", state.getBattery().getPercentage());
batteryMap.put("voltage", state.getBattery().getVoltage());
batteryMap.put("current", state.getBattery().getCurrent());
batteryMap.put("temperature", state.getBattery().getTemperature());
batteryMap.put("charging", state.getBattery().getCharging());
resultMap.put("battery", batteryMap);
}
resultMap.put("currentMap", state.getCurrentMap());
resultMap.put("currentStation", state.getCurrentStation());
resultMap.put("lastError", state.getLastError());
return AjaxResult.ok(resultMap);
}
@ApiOperation("获取导航状态")
@GetMapping("/getNavigationStatus")
public AjaxResult getNavigationStatus(EdgeCommonVO vo) {
Agv.AgvNavigationStatus status = edgeAgvService.getNavigationStatus(vo);
// 转换为Map返回
java.util.Map<String, Object> resultMap = new java.util.HashMap<>();
resultMap.put("state", status.getState());
resultMap.put("type", status.getType());
resultMap.put("progress", status.getProgress());
resultMap.put("message", status.getMessage());
return AjaxResult.ok(resultMap);
}
@ApiOperation("紧急停止")
@GetMapping("/emergencyStop")
public AjaxResult emergencyStop(EdgeCommonVO vo) {
edgeAgvService.emergencyStop(vo);
return AjaxResult.ok();
}
@ApiOperation("清除故障")
@GetMapping("/clearFault")
public AjaxResult clearFault(EdgeCommonVO vo) {
edgeAgvService.clearFault(vo);
return AjaxResult.ok();
}
@ApiOperation("导航到指定位置")
@PostMapping("/navigateToPose")
public AjaxResult navigateToPose(EdgeCommonVO vo,
@ApiParam("X坐标") double x,
@ApiParam("Y坐标") double y,
@ApiParam("角度") double theta) {
Agv.AgvPose2d pose = Agv.AgvPose2d.newBuilder()
.setX(x)
.setY(y)
.setTheta(theta)
.build();
edgeAgvService.navigateToPose(vo, pose);
return AjaxResult.ok();
}
@ApiOperation("导航到站点")
@GetMapping("/navigateToStation")
public AjaxResult navigateToStation(EdgeCommonVO vo,
@ApiParam("站点ID") String stationId) {
edgeAgvService.navigateToStation(vo, stationId);
return AjaxResult.ok();
}
@ApiOperation("暂停导航")
@GetMapping("/pauseNavigation")
public AjaxResult pauseNavigation(EdgeCommonVO vo) {
edgeAgvService.pauseNavigation(vo);
return AjaxResult.ok();
}
@ApiOperation("恢复导航")
@GetMapping("/resumeNavigation")
public AjaxResult resumeNavigation(EdgeCommonVO vo) {
edgeAgvService.resumeNavigation(vo);
return AjaxResult.ok();
}
@ApiOperation("取消导航")
@GetMapping("/cancelNavigation")
public AjaxResult cancelNavigation(EdgeCommonVO vo) {
edgeAgvService.cancelNavigation(vo);
return AjaxResult.ok();
}
@ApiOperation("设置速度")
@PostMapping("/setVelocity")
public AjaxResult setVelocity(EdgeCommonVO vo,
@ApiParam("线速度X") double vx,
@ApiParam("线速度Y") double vy,
@ApiParam("角速度") double wz) {
Agv.AgvVelocity velocity = Agv.AgvVelocity.newBuilder()
.setVx(vx)
.setVy(vy)
.setWz(wz)
.build();
edgeAgvService.setVelocity(vo, velocity);
return AjaxResult.ok();
}
@ApiOperation("停止速度控制")
@GetMapping("/stopVelocityControl")
public AjaxResult stopVelocityControl(EdgeCommonVO vo) {
edgeAgvService.stopVelocityControl(vo);
return AjaxResult.ok();
}
@ApiOperation("列出所有地图")
@GetMapping("/listMaps")
public AjaxResult listMaps(EdgeCommonVO vo) {
List<String> maps = edgeAgvService.listMaps(vo);
return AjaxResult.ok(maps);
}
@ApiOperation("列出所有站点")
@GetMapping("/listStations")
public AjaxResult listStations(EdgeCommonVO vo) {
List<Agv.AgvStation> stations = edgeAgvService.listStations(vo);
// 转换为List<Map>返回
List<java.util.Map<String, Object>> stationList = new java.util.ArrayList<>();
for (Agv.AgvStation station : stations) {
java.util.Map<String, Object> stationMap = new java.util.HashMap<>();
stationMap.put("id", station.getId());
stationMap.put("type", station.getType());
stationMap.put("description", station.getDescription());
if (station.hasPose()) {
java.util.Map<String, Object> poseMap = new java.util.HashMap<>();
poseMap.put("x", station.getPose().getX());
poseMap.put("y", station.getPose().getY());
poseMap.put("theta", station.getPose().getTheta());
stationMap.put("pose", poseMap);
}
stationList.add(stationMap);
}
return AjaxResult.ok(stationList);
}
@ApiOperation("切换地图")
@GetMapping("/switchMap")
public AjaxResult switchMap(EdgeCommonVO vo,
@ApiParam("地图名称") String mapName) {
edgeAgvService.switchMap(vo, mapName);
return AjaxResult.ok();
}
@ApiOperation("上传地图")
@PostMapping("/uploadMap")
public AjaxResult uploadMap(EdgeCommonVO vo,
@ApiParam("地图名称") String mapName,
@ApiParam("地图内容") String content) {
edgeAgvService.uploadMap(vo, mapName, content);
return AjaxResult.ok();
}
@ApiOperation("下载地图")
@GetMapping("/downloadMap")
public AjaxResult downloadMap(EdgeCommonVO vo,
@ApiParam("地图名称") String mapName) {
AgvCommand.AgvDownloadMapResult result = edgeAgvService.robotConfigDownloadMap(vo, mapName);
String content = edgeAgvService.downloadMap(vo, mapName);
return AjaxResult.ok(content);
}
@ApiOperation("开始建图")
@PostMapping("/startMapping")
public AjaxResult startMapping(EdgeCommonVO vo,
@ApiParam("建图维度0:未指定 1:2D 2:3D 3:2D+3D") int dimension,
@ApiParam("地图名称(可选)") String mapName,
@ApiParam("是否实时建图") boolean realTime) {
Agv.AgvMapDimension mapDimension = Agv.AgvMapDimension.forNumber(dimension);
String sessionId = edgeAgvService.startMapping(vo, mapDimension, mapName, realTime);
java.util.Map<String, Object> result = new java.util.HashMap<>();
result.put("sessionId", sessionId);
return AjaxResult.ok(result);
}
@ApiOperation("上传地图")
@PostMapping("/uploadMap")
public AjaxResult uploadMap(EdgeCommonVO vo,
@ApiParam("地图内容") String mapContent) {
AgvCommand.AgvUploadMapResult result = edgeAgvService.robotConfigUploadMap(vo, mapContent);
java.util.Map<String, Object> resultMap = new java.util.HashMap<>();
resultMap.put("retCode", result.getRetCode());
resultMap.put("errMsg", result.getErrMsg());
return AjaxResult.ok(resultMap);
}
@ApiOperation("获取电池状态")
@GetMapping("/getBatteryStatus")
public AjaxResult getBatteryStatus(EdgeCommonVO vo) {
AgvCommand.AgvBatteryStatus batteryStatus = edgeAgvService.getBatteryStatus(vo);
return AjaxResult.ok(batteryStatus);
}
@ApiOperation("导航到指定站点")
@GetMapping("/goToStation")
public AjaxResult goToStation(EdgeCommonVO vo,
@ApiParam("起始站点ID") String sourceId,
@ApiParam("目标站点ID") String targetId,
@ApiParam("任务ID") String taskId,
@ApiParam("操作类型") String operation,
@ApiParam("顶升高度") Double jackHeight) {
AgvCommand.RobotGoTargetResData result = edgeAgvService.robotGoTarget(
vo, sourceId, targetId, taskId, operation, jackHeight
);
return AjaxResult.ok(result);
}
@ApiOperation("自由导航到坐标点")
@GetMapping("/freeGo")
public AjaxResult freeGo(EdgeCommonVO vo,
@ApiParam("X坐标") double x,
@ApiParam("Y坐标") double y,
@ApiParam("角度") double theta,
@ApiParam("任务ID") String taskId) {
// 构建自由导航点
AgvCommand.FreeGoPoint freeGoPoint = AgvCommand.FreeGoPoint.newBuilder()
.setX(x)
.setY(y)
.setTheta(theta)
.build();
AgvCommand.RobotGoTargetResData result = edgeAgvService.robotGoTargetWithFreeGo(
vo, freeGoPoint, taskId
);
return AjaxResult.ok(result);
}
@ApiOperation("暂停导航任务")
@GetMapping("/pauseTask")
public AjaxResult pauseTask(EdgeCommonVO vo) {
edgeAgvService.robotTaskPause(vo);
@ApiOperation("停止建图")
@GetMapping("/stopMapping")
public AjaxResult stopMapping(EdgeCommonVO vo) {
edgeAgvService.stopMapping(vo);
return AjaxResult.ok();
}
@ApiOperation("继续导航任务")
@GetMapping("/resumeTask")
public AjaxResult resumeTask(EdgeCommonVO vo) {
edgeAgvService.robotTaskResume(vo);
return AjaxResult.ok();
}
@ApiOperation("取消导航任务")
@GetMapping("/cancelTask")
public AjaxResult cancelTask(EdgeCommonVO vo) {
edgeAgvService.robotTaskCancel(vo);
return AjaxResult.ok();
}
@ApiOperation("查询当前导航状态")
@GetMapping("/getTaskStatus")
public AjaxResult getTaskStatus(EdgeCommonVO vo,
@ApiParam("是否简化返回") boolean simple) {
AgvCommand.RobotStatusTaskResData taskData = edgeAgvService.getRobotStatusTaskCurrent(vo, simple);
return AjaxResult.ok(taskData);
}
@ApiOperation("查询站点列表")
@GetMapping("/getStationList")
public AjaxResult getStationList(EdgeCommonVO vo) {
AgvCommand.QueryStationListResult result = edgeAgvService.queryStationList(vo);
return AjaxResult.ok(result);
}
@ApiOperation("切换地图")
@GetMapping("/loadMap")
public AjaxResult loadMap(EdgeCommonVO vo,
@ApiParam("地图名称") String mapName) {
AgvCommand.RobotLoadMapResult result = edgeAgvService.robotLoadMap(vo, mapName);
java.util.Map<String, Object> resultMap = new java.util.HashMap<>();
resultMap.put("retCode", result.getRetCode());
resultMap.put("errMsg", result.getErrMsg());
return AjaxResult.ok(resultMap);
}
@ApiOperation("查询地图加载状态")
@GetMapping("/getLoadMapStatus")
public AjaxResult getLoadMapStatus(EdgeCommonVO vo) {
AgvCommand.RobotQueryLoadMapStatusResult result = edgeAgvService.queryLoadMapStatus(vo);
return AjaxResult.ok(result);
}
}

View File

@ -3,7 +3,7 @@ package com.cmvr.web.controller.inspection;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import cmvr.api.AgvCommand;
import cmvr.msgs.Agv;
import com.cmvr.inspection.domain.vo.InspectionRobotVo;
import io.swagger.annotations.*;
import org.springframework.security.access.prepost.PreAuthorize;
@ -114,8 +114,8 @@ public class InspectionRobotController extends BaseController
@GetMapping("/{robotId}/maps")
public AjaxResult getRobotMapList(@PathVariable String robotId)
{
AgvCommand.AgvMapStatus robotMapList = inspectionRobotService.getRobotMapList(robotId);
return success(robotMapList.getMapsList());
List<String> robotMapList = inspectionRobotService.getRobotMapList(robotId);
return success(robotMapList);
}
/**
@ -180,7 +180,7 @@ public class InspectionRobotController extends BaseController
@GetMapping("/{robotId}/location")
public AjaxResult getRobotRealtimeLocation(@PathVariable String robotId)
{
AgvCommand.AgvRobotLocation location = inspectionRobotService.getRobotRealtimeLocation(robotId);
Agv.AgvPose2d location = inspectionRobotService.getRobotRealtimeLocation(robotId);
return success(location);
}
@ -195,15 +195,47 @@ public class InspectionRobotController extends BaseController
@PathVariable @ApiParam("机器人ID") String robotId,
@RequestParam @ApiParam("X坐标") Double x,
@RequestParam @ApiParam("Y坐标") Double y,
@RequestParam(required = false) @ApiParam("朝向角(弧度)") Double theta,
@RequestParam(required = false) @ApiParam("任务ID") String taskId
@RequestParam(required = false) @ApiParam("朝向角(弧度)") Double theta
)
{
AgvCommand.RobotGoTargetResData result = inspectionRobotService.navigateToPosition(
robotId, x, y, theta, taskId
inspectionRobotService.navigateToPosition(
robotId, x, y, theta
);
return success();
}
/**
* 开始建图
*/
@ApiOperation("开始建图")
@PreAuthorize("@ss.hasPermi('inspection:robot:edit')")
@Log(title = "开始建图", businessType = BusinessType.OTHER)
@PostMapping("/{robotId}/start-mapping")
public AjaxResult startMapping(
@PathVariable @ApiParam("机器人ID") String robotId,
@RequestParam @ApiParam("建图维度0:未指定 1:2D 2:3D 3:2D+3D") int dimension,
@RequestParam(required = false) @ApiParam("地图名称(可选)") String mapName,
@RequestParam(defaultValue = "true") @ApiParam("是否实时建图") boolean realTime
)
{
String sessionId = inspectionRobotService.startMapping(robotId, dimension, mapName, realTime);
java.util.Map<String, Object> result = new java.util.HashMap<>();
result.put("sessionId", sessionId);
return success(result);
}
/**
* 停止建图
*/
@ApiOperation("停止建图")
@PreAuthorize("@ss.hasPermi('inspection:robot:edit')")
@Log(title = "停止建图", businessType = BusinessType.OTHER)
@PostMapping("/{robotId}/stop-mapping")
public AjaxResult stopMapping(@PathVariable @ApiParam("机器人ID") String robotId)
{
inspectionRobotService.stopMapping(robotId);
return success();
}
}

View File

@ -0,0 +1,214 @@
package com.cmvr.web.controller.tts;
import com.cmvr.common.annotation.Log;
import com.cmvr.common.core.controller.BaseController;
import com.cmvr.common.core.domain.AjaxResult;
import com.cmvr.common.core.page.TableDataInfo;
import com.cmvr.common.enums.BusinessType;
import com.cmvr.tts.domain.CorpusCategory;
import com.cmvr.tts.domain.CorpusInfo;
import com.cmvr.tts.service.ICorpusInfoService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
/**
* 车载语料数据Controller
*
* @author cmvr
*/
@RestController
@RequestMapping("/tts/corpus")
@Api(tags = "智能座舱-车载语料管理")
public class CorpusInfoController extends BaseController {
@Autowired
private ICorpusInfoService corpusInfoService;
/**
* 查询语料列表
*/
@ApiOperation("查询语料列表")
@PreAuthorize("@ss.hasPermi('tts:corpus:list')")
@GetMapping("/list")
public TableDataInfo list(CorpusInfo corpus) {
startPage();
return corpusInfoService.selectCorpusList(corpus);
}
/**
* 获取语料详细信息
*/
@ApiOperation("获取语料详细信息")
@PreAuthorize("@ss.hasPermi('tts:corpus:query')")
@GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable("id") String id) {
return success(corpusInfoService.selectCorpusById(id));
}
/**
* 新增语料
*/
@ApiOperation("新增语料")
@PreAuthorize("@ss.hasPermi('tts:corpus:add')")
@Log(title = "车载语料", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody CorpusInfo corpus) {
return toAjax(corpusInfoService.insertCorpus(corpus));
}
/**
* 修改语料
*/
@ApiOperation("修改语料")
@PreAuthorize("@ss.hasPermi('tts:corpus:edit')")
@Log(title = "车载语料", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody CorpusInfo corpus) {
return toAjax(corpusInfoService.updateCorpus(corpus));
}
/**
* 删除语料
*/
@ApiOperation("删除语料")
@PreAuthorize("@ss.hasPermi('tts:corpus:remove')")
@Log(title = "车载语料", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable String[] ids) {
return toAjax(corpusInfoService.deleteCorpusByIds(ids));
}
/**
* 获取音频流
*/
@ApiOperation("获取音频流")
@PreAuthorize("@ss.hasPermi('tts:corpus:query')")
@GetMapping("/audio/{id}")
public void getAudio(@PathVariable String id, HttpServletResponse response) {
corpusInfoService.getAudioStream(id, response);
}
/**
* 上传语料音频
*/
@ApiOperation("上传语料音频")
@PreAuthorize("@ss.hasPermi('tts:corpus:edit')")
@Log(title = "车载语料", businessType = BusinessType.UPDATE)
@PostMapping("/audio/upload/{id}")
public AjaxResult uploadAudio(@PathVariable String id, @RequestParam("file") MultipartFile file) {
String path = corpusInfoService.uploadCorpusAudio(id, file);
return success(path);
}
/**
* 批量导入语料
*/
@ApiOperation("批量导入语料")
@PreAuthorize("@ss.hasPermi('tts:corpus:import')")
@Log(title = "车载语料", businessType = BusinessType.IMPORT)
@PostMapping("/import")
public AjaxResult importData(@RequestParam("file") MultipartFile file) {
Map<String, Object> result = corpusInfoService.importCorpus(file);
return success(result);
}
/**
* 查询看板统计数据
*/
@ApiOperation("查询看板统计数据")
@PreAuthorize("@ss.hasPermi('tts:corpus:query')")
@GetMapping("/dashboard/stats")
public AjaxResult getDashboardStats() {
return success(corpusInfoService.getDashboardStats());
}
/**
* 按语种统计
*/
@ApiOperation("按语种统计")
@PreAuthorize("@ss.hasPermi('tts:corpus:query')")
@GetMapping("/stats/language")
public AjaxResult getStatsByLanguage() {
return success(corpusInfoService.getStatsByLanguage());
}
/**
* 按方言统计
*/
@ApiOperation("按方言统计")
@PreAuthorize("@ss.hasPermi('tts:corpus:query')")
@GetMapping("/stats/dialect")
public AjaxResult getStatsByDialect() {
return success(corpusInfoService.getStatsByDialect());
}
/**
* 按情绪统计
*/
@ApiOperation("按情绪统计")
@PreAuthorize("@ss.hasPermi('tts:corpus:query')")
@GetMapping("/stats/emotion")
public AjaxResult getStatsByEmotion() {
return success(corpusInfoService.getStatsByEmotion());
}
/**
* 按分类统计
*/
@ApiOperation("按分类统计")
@PreAuthorize("@ss.hasPermi('tts:corpus:query')")
@GetMapping("/stats/category")
public AjaxResult getStatsByCategory() {
return success(corpusInfoService.getStatsByCategory());
}
/**
* 查询分类树
*/
@ApiOperation("查询分类树")
@PreAuthorize("@ss.hasPermi('tts:corpus:query')")
@GetMapping("/category/tree")
public AjaxResult getCategoryTree() {
return success(corpusInfoService.getCategoryTree());
}
/**
* 新增分类
*/
@ApiOperation("新增分类")
@PreAuthorize("@ss.hasPermi('tts:corpus:add')")
@Log(title = "语料分类", businessType = BusinessType.INSERT)
@PostMapping("/category")
public AjaxResult addCategory(@RequestBody CorpusCategory category) {
return toAjax(corpusInfoService.insertCategory(category));
}
/**
* 修改分类
*/
@ApiOperation("修改分类")
@PreAuthorize("@ss.hasPermi('tts:corpus:edit')")
@Log(title = "语料分类", businessType = BusinessType.UPDATE)
@PutMapping("/category")
public AjaxResult editCategory(@RequestBody CorpusCategory category) {
return toAjax(corpusInfoService.updateCategory(category));
}
/**
* 删除分类
*/
@ApiOperation("删除分类")
@PreAuthorize("@ss.hasPermi('tts:corpus:remove')")
@Log(title = "语料分类", businessType = BusinessType.DELETE)
@DeleteMapping("/category/{id}")
public AjaxResult removeCategory(@PathVariable String id) {
return toAjax(corpusInfoService.deleteCategoryById(id));
}
}

View File

@ -0,0 +1,253 @@
package com.cmvr.web.controller.tts;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.cmvr.common.annotation.Log;
import com.cmvr.common.core.controller.BaseController;
import com.cmvr.common.core.domain.AjaxResult;
import com.cmvr.common.core.page.TableDataInfo;
import com.cmvr.common.enums.BusinessType;
import com.cmvr.llm.service.LLMAiAgentPlatformService;
import com.cmvr.tts.domain.TtsSynthesizeTask;
import com.cmvr.tts.domain.vo.LlmQueryVo;
import com.cmvr.tts.service.ITtsSynthesizeTaskService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
* TTS合成任务Controller
*
* @author cmvr
*/
@RestController
@RequestMapping("/tts/task")
@Api(tags = "智能座舱-TTS合成任务管理")
@RequiredArgsConstructor
public class TtsSynthesizeTaskController extends BaseController {
@Autowired
private ITtsSynthesizeTaskService ttsTaskService;
private final LLMAiAgentPlatformService llmAiAgentPlatformService;
/**
* 查询TTS合成任务列表
*/
@ApiOperation("查询TTS合成任务列表")
@PreAuthorize("@ss.hasPermi('tts:task:list')")
@GetMapping("/list")
public TableDataInfo list(TtsSynthesizeTask task) {
startPage();
return ttsTaskService.selectTtsTaskList(task);
}
/**
* 获取TTS合成任务详细信息
*/
@ApiOperation("获取TTS合成任务详细信息")
@PreAuthorize("@ss.hasPermi('tts:task:query')")
@GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable("id") String id) {
return success(ttsTaskService.selectTtsTaskById(id));
}
/**
* 新增TTS合成任务
*/
@ApiOperation("新增TTS合成任务")
@PreAuthorize("@ss.hasPermi('tts:task:add')")
@Log(title = "TTS合成任务", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody TtsSynthesizeTask task) {
return success(ttsTaskService.insertTtsTask(task));
}
/**
* 批量新增TTS合成任务
*/
@ApiOperation("批量新增TTS合成任务")
@PreAuthorize("@ss.hasPermi('tts:task:add')")
@Log(title = "TTS合成任务", businessType = BusinessType.INSERT)
@PostMapping("/batch")
public AjaxResult batchAdd(@RequestBody List<TtsSynthesizeTask> tasks) {
return toAjax(ttsTaskService.batchInsertTtsTask(tasks));
}
/**
* 修改TTS合成任务
*/
@ApiOperation("修改TTS合成任务")
@PreAuthorize("@ss.hasPermi('tts:task:edit')")
@Log(title = "TTS合成任务", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@RequestBody TtsSynthesizeTask task) {
return toAjax(ttsTaskService.updateTtsTask(task));
}
/**
* 删除TTS合成任务
*/
@ApiOperation("删除TTS合成任务")
@PreAuthorize("@ss.hasPermi('tts:task:remove')")
@Log(title = "TTS合成任务", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable String[] ids) {
return toAjax(ttsTaskService.deleteTtsTaskByIds(ids));
}
/**
* 重生成TTS任务
*/
@ApiOperation("重生成TTS任务")
@PreAuthorize("@ss.hasPermi('tts:task:edit')")
@Log(title = "TTS合成任务", businessType = BusinessType.UPDATE)
@PostMapping("/regenerate/{id}")
public AjaxResult regenerate(@PathVariable String id) {
return toAjax(ttsTaskService.regenerateTask(id));
}
/**
* 获取音频文件流
*/
@ApiOperation("获取音频文件流")
@PreAuthorize("@ss.hasPermi('tts:task:query')")
@GetMapping("/audio/{id}")
public void getAudio(@PathVariable String id, HttpServletResponse response) {
ttsTaskService.getAudioStream(id, response);
}
/**
* 查询看板统计数据
*/
@ApiOperation("查询看板统计数据")
@PreAuthorize("@ss.hasPermi('tts:task:query')")
@GetMapping("/dashboard/stats")
public AjaxResult getDashboardStats() {
return success(ttsTaskService.getDashboardStats());
}
/**
* 按语种统计
*/
@ApiOperation("按语种统计")
@PreAuthorize("@ss.hasPermi('tts:task:query')")
@GetMapping("/stats/language")
public AjaxResult getStatsByLanguage() {
return success(ttsTaskService.getStatsByLanguage());
}
/**
* 按音色统计
*/
@ApiOperation("按音色统计")
@PreAuthorize("@ss.hasPermi('tts:task:query')")
@GetMapping("/stats/voice")
public AjaxResult getStatsByVoice() {
return success(ttsTaskService.getStatsByVoice());
}
/**
* 按情绪统计
*/
@ApiOperation("按情绪统计")
@PreAuthorize("@ss.hasPermi('tts:task:query')")
@GetMapping("/stats/emotion")
public AjaxResult getStatsByEmotion() {
return success(ttsTaskService.getStatsByEmotion());
}
/**
* 近7日趋势
*/
@ApiOperation("近7日趋势")
@PreAuthorize("@ss.hasPermi('tts:task:query')")
@GetMapping("/stats/trend")
public AjaxResult getLast7DaysTrend() {
return success(ttsTaskService.getLast7DaysTrend());
}
/**
* 文本泛化
*/
@ApiOperation("文本泛化")
@PreAuthorize("@ss.hasPermi('tts:task:query')")
@PostMapping("/llm/query")
public AjaxResult llmQuery(@RequestBody LlmQueryVo request) {
// 闲聊key d9672ql4shh4136opsfg
// 泛化key d966pq54shh4nveh8d20
JSONObject result = llmAiAgentPlatformService.query(
null,
request.getLanguage() + "," + request.getCount() + "," + request.getText(),
"d96ammd4shh4nvehb6mg",
false,
null
);
// 解析LLM返回的结果提取JSON数组
String resultStr = result.getString("result");
if (resultStr != null && !resultStr.isEmpty()) {
// 尝试多种格式解析
JSONArray jsonArray = parseJsonArray(resultStr);
return success(jsonArray);
}
return success(new JSONArray());
}
/**
* 解析JSON数组兼容多种格式
*/
private JSONArray parseJsonArray(String text) {
if (text == null || text.isEmpty()) {
return new JSONArray();
}
// 1. 直接尝试解析为JSON数组
try {
return JSONArray.parseArray(text);
} catch (Exception e) {
// 继续尝试其他格式
}
// 2. 移除markdown代码块标记
String cleaned = text.replace("```json", "").replace("```", "").trim();
try {
return JSONArray.parseArray(cleaned);
} catch (Exception e) {
// 继续尝试其他格式
}
// 3. 尝试从文本中提取JSON数组匹配 [...] 格式
int startIdx = text.indexOf("[");
int endIdx = text.lastIndexOf("]");
if (startIdx != -1 && endIdx != -1 && endIdx > startIdx) {
String jsonStr = text.substring(startIdx, endIdx + 1);
try {
return JSONArray.parseArray(jsonStr);
} catch (Exception e) {
// 继续尝试其他格式
}
}
// 4. 如果都失败返回包含原始文本的数组
return new JSONArray().fluentAdd(text);
}
/**
* 保存任务到语料库
*/
@ApiOperation("保存任务到语料库")
@PreAuthorize("@ss.hasPermi('tts:task:edit')")
@Log(title = "TTS合成任务", businessType = BusinessType.INSERT)
@PostMapping("/Nsave-to-corpus/{taskId}/{categoryId}")
public AjaxResult saveToCorpus(@PathVariable String taskId, @PathVariable String categoryId) {
return toAjax(ttsTaskService.saveToCorpus(taskId, categoryId));
}
}

View File

@ -132,3 +132,8 @@ flowise:
abort: http://192.168.0.108:3000/api/v1/chatmessage/abort/f99329e9-b33d-437d-90ed-69eaa8a05418/
query: http://192.168.0.108:3000/api/v1/executions/
api-key: pg61JW6W_GXyqmqSoURJ4mlSRrCMRzGLBJli_w3BFkg
# TTS外部接口配置
tts:
external-api:
url: http://192.168.0.102:9003/generate_advanced_audio

View File

@ -1,162 +1,167 @@
package com.cmvr.edge.client.service;
import cmvr.api.AgvCommand;
import cmvr.msgs.Agv;
import cmvr.msgs.Agv.AgvRuntimeState;
import com.cmvr.edge.client.model.EdgeCommonVO;
import java.util.List;
/**
* 边缘系统AGV服务
*
* @author cmvr-iot
* @since 2026-06-11
* @since 2026-07-02
*/
public interface EdgeAgvService {
/**
* 获取AGV状态信息
* 获取AGV运行时状态
*
* @param edgeCommonVO 边缘通用参数
* @return AGV状态信息
* @return AGV运行时状态
*/
AgvCommand.AgvStatusInfo getStatusInfo(EdgeCommonVO edgeCommonVO);
AgvRuntimeState getRuntimeState(EdgeCommonVO edgeCommonVO);
/**
* 获取机器人位置
* 获取导航状态
*
* @param edgeCommonVO 边缘通用参数
* @return 机器人位置
* @return 导航状态
*/
AgvCommand.AgvRobotLocation getRobotLocation(EdgeCommonVO edgeCommonVO);
Agv.AgvNavigationStatus getNavigationStatus(EdgeCommonVO edgeCommonVO);
/**
* 获取地图状态地图列表
* 紧急停止
*
* @param edgeCommonVO 边缘通用参数
* @return 地图状态
*/
AgvCommand.AgvMapStatus getMapStatus(EdgeCommonVO edgeCommonVO);
void emergencyStop(EdgeCommonVO edgeCommonVO);
/**
* 下载地图从机器人下载地图到服务器
* 清除故障
*
* @param edgeCommonVO 边缘通用参数
*/
void clearFault(EdgeCommonVO edgeCommonVO);
/**
* 导航到指定位置
*
* @param edgeCommonVO 边缘通用参数
* @param pose 目标位姿
*/
void navigateToPose(EdgeCommonVO edgeCommonVO, Agv.AgvPose2d pose);
/**
* 导航到站点
*
* @param edgeCommonVO 边缘通用参数
* @param stationId 站点ID
*/
void navigateToStation(EdgeCommonVO edgeCommonVO, String stationId);
/**
* 跟随路径
*
* @param edgeCommonVO 边缘通用参数
* @param pathSegments 路径段列表
*/
void followPath(EdgeCommonVO edgeCommonVO, List<Agv.AgvPathSegment> pathSegments);
/**
* 暂停导航
*
* @param edgeCommonVO 边缘通用参数
*/
void pauseNavigation(EdgeCommonVO edgeCommonVO);
/**
* 恢复导航
*
* @param edgeCommonVO 边缘通用参数
*/
void resumeNavigation(EdgeCommonVO edgeCommonVO);
/**
* 取消导航
*
* @param edgeCommonVO 边缘通用参数
*/
void cancelNavigation(EdgeCommonVO edgeCommonVO);
/**
* 设置速度
*
* @param edgeCommonVO 边缘通用参数
* @param velocity 速度
*/
void setVelocity(EdgeCommonVO edgeCommonVO, Agv.AgvVelocity velocity);
/**
* 停止速度控制
*
* @param edgeCommonVO 边缘通用参数
*/
void stopVelocityControl(EdgeCommonVO edgeCommonVO);
/**
* 列出所有地图
*
* @param edgeCommonVO 边缘通用参数
* @return 地图列表
*/
List<String> listMaps(EdgeCommonVO edgeCommonVO);
/**
* 列出所有站点
*
* @param edgeCommonVO 边缘通用参数
* @return 站点列表
*/
List<Agv.AgvStation> listStations(EdgeCommonVO edgeCommonVO);
/**
* 切换地图
*
* @param edgeCommonVO 边缘通用参数
* @param mapName 地图名称
* @return 下载结果包含地图内容
*/
AgvCommand.AgvDownloadMapResult robotConfigDownloadMap(EdgeCommonVO edgeCommonVO, String mapName);
void switchMap(EdgeCommonVO edgeCommonVO, String mapName);
/**
* 上传地图从服务器上传地图到机器人
* 上传地图
*
* @param edgeCommonVO 边缘通用参数
* @param mapContent 地图内容
* @return 上传结果
* @param mapName 地图名称
* @param content 地图内容
*/
AgvCommand.AgvUploadMapResult robotConfigUploadMap(EdgeCommonVO edgeCommonVO, String mapContent);
void uploadMap(EdgeCommonVO edgeCommonVO, String mapName, String content);
/**
* 获取电池状态
* 下载地图
*
* @param edgeCommonVO 边缘通用参数
* @return 电池状态
* @param mapName 地图名称
* @return 地图内容
*/
AgvCommand.AgvBatteryStatus getBatteryStatus(EdgeCommonVO edgeCommonVO);
String downloadMap(EdgeCommonVO edgeCommonVO, String mapName);
/**
* 单点站点自动规划导航命令码3051
* 从起始站点自动规划路径到目标站点支持多种操作类型顶升货叉滚筒等
* 注意此命令会取消当前正在执行的任务严禁用于多车调度场景
* 开始建图
*
* @param edgeCommonVO 边缘通用参数
* @param sourceId 起始站点ID"SELF_POSITION"表示当前位置
* @param targetId 目标站点ID"SELF_POSITION"表示原地执行操作
* @param taskId 任务ID可选建议提供
* @param operation 操作类型"JackLoad"顶升装载"ForkUnload"货叉卸载等可选
* @param jackHeight 顶升高度仅当operation为顶升相关时有效可选
* @return 导航任务下发结果
* @param dimension 建图维度2D/3D/两者
* @param mapName 地图名称可选
* @param realTime 是否实时建图
* @return 会话ID
*/
AgvCommand.RobotGoTargetResData robotGoTarget(EdgeCommonVO edgeCommonVO, String sourceId, String targetId,
String taskId, String operation, Double jackHeight);
String startMapping(EdgeCommonVO edgeCommonVO, Agv.AgvMapDimension dimension, String mapName, boolean realTime);
/**
* 自由导航到指定坐标位置命令码3051使用freego参数
* 直接导航到指定的XY坐标和角度不依赖站点
* 注意此命令会取消当前正在执行的任务仅支持双轮差速底盘
* 停止建图
*
* @param edgeCommonVO 边缘通用参数
* @param freeGoPoint 自由导航目标点包含xytheta
* @param taskId 任务ID可选建议提供
* @return 导航任务下发结果
*/
AgvCommand.RobotGoTargetResData robotGoTargetWithFreeGo(EdgeCommonVO edgeCommonVO,
AgvCommand.FreeGoPoint freeGoPoint,
String taskId);
/**
* 暂停当前导航任务命令码3001
* 暂停AGV当前正在执行的导航任务AGV将减速停止
*
* @param edgeCommonVO 边缘通用参数
* @return 暂停结果
*/
AgvCommand.RobotTaskPauseCommand.Feedback.Result robotTaskPause(EdgeCommonVO edgeCommonVO);
/**
* 继续当前导航任务命令码3002
* 恢复之前被暂停的导航任务AGV将继续执行
*
* @param edgeCommonVO 边缘通用参数
* @return 继续结果
*/
AgvCommand.RobotTaskResumeCommand.Feedback.Result robotTaskResume(EdgeCommonVO edgeCommonVO);
/**
* 取消当前导航任务命令码3003
* 完全取消当前正在执行或暂停的导航任务
*
* @param edgeCommonVO 边缘通用参数
* @return 取消结果
*/
AgvCommand.RobotTaskCancelCommand.Feedback.Result robotTaskCancel(EdgeCommonVO edgeCommonVO);
/**
* 查询当前实时导航状态命令码1020
* 获取AGV当前导航任务的详细状态包括任务进度已经过站点剩余站点等
*
* @param edgeCommonVO 边缘通用参数
* @param simple 是否仅返回任务状态true=仅task_statusfalse=全量信息
* @return 导航状态信息
*/
AgvCommand.RobotStatusTaskResData getRobotStatusTaskCurrent(EdgeCommonVO edgeCommonVO, boolean simple);
/**
* 查询当前地图站点列表命令码1301
* 获取当前加载地图中的所有站点信息包括站点坐标类型描述等
*
* @param edgeCommonVO 边缘通用参数
* @return 站点列表信息
*/
AgvCommand.QueryStationListResult queryStationList(EdgeCommonVO edgeCommonVO);
/**
* 切换载入地图命令码2022
* 将AGV切换到指定的地图目标地图必须已存在于机器人中
* 切换后需要等待地图加载完成才能执行导航任务
*
* @param edgeCommonVO 边缘通用参数
* @param mapName 目标地图名称必填仅允许字母数字-_
* @return 切换地图结果
*/
AgvCommand.RobotLoadMapResult robotLoadMap(EdgeCommonVO edgeCommonVO, String mapName);
/**
* 查询地图载入状态命令码1022
* 查询当前地图加载的状态可用于判断地图是否加载完成
* 状态值0=失败1=成功2=加载中加载中时禁止执行重定位操作
*
* @param edgeCommonVO 边缘通用参数
* @return 地图加载状态
*/
AgvCommand.RobotQueryLoadMapStatusResult queryLoadMapStatus(EdgeCommonVO edgeCommonVO);
void stopMapping(EdgeCommonVO edgeCommonVO);
}

View File

@ -2,6 +2,8 @@ package com.cmvr.edge.client.service.impl;
import cmvr.api.AgvCommand;
import cmvr.api.AgvServiceGrpc;
import cmvr.api.Common;
import cmvr.msgs.Agv;
import cn.hutool.core.util.StrUtil;
import com.cmvr.common.exception.GlobalException;
import com.cmvr.edge.client.manage.GrpcServiceManager;
@ -12,11 +14,13 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* AGV服务实现类
*
* @author cmvr-iot
* @since 2026-06-11
* @since 2026-07-02
*/
@Slf4j
@Service
@ -26,281 +30,225 @@ public class EdgeAgvServiceImpl implements EdgeAgvService {
private final GrpcServiceManager grpcServiceManager;
@Override
public AgvCommand.AgvStatusInfo getStatusInfo(EdgeCommonVO edgeCommonVO) {
public Agv.AgvRuntimeState getRuntimeState(EdgeCommonVO edgeCommonVO) {
AgvServiceGrpc.AgvServiceBlockingStub stub = grpcServiceManager.getGrpcClient(
edgeCommonVO.getTerminalId(), AgvServiceGrpc.AgvServiceBlockingStub.class);
AgvCommand.GetAgvStatusInfoCommand.Request request = AgvCommand.GetAgvStatusInfoCommand.Request.newBuilder()
AgvCommand.AgvRuntimeStateCommand.Request request = AgvCommand.AgvRuntimeStateCommand.Request.newBuilder()
.setHeader(EdgeCommonUtil.buildRequest(edgeCommonVO.getDeviceId()))
.build();
return executeGrpcCall(() -> stub.getStatusInfo(request)).getStatus();
}
@Override
public AgvCommand.AgvRobotLocation getRobotLocation(EdgeCommonVO edgeCommonVO) {
AgvServiceGrpc.AgvServiceBlockingStub stub = grpcServiceManager.getGrpcClient(
edgeCommonVO.getTerminalId(), AgvServiceGrpc.AgvServiceBlockingStub.class);
AgvCommand.RobotStatusLocCommand.Request request = AgvCommand.RobotStatusLocCommand.Request.newBuilder()
.setHeader(EdgeCommonUtil.buildRequest(edgeCommonVO.getDeviceId()))
.build();
return executeGrpcCall(() -> stub.getRobotLocation(request)).getStatus();
return executeGrpcCall(() -> stub.getRuntimeState(request)).getState();
}
@Override
public AgvCommand.AgvMapStatus getMapStatus(EdgeCommonVO edgeCommonVO) {
public Agv.AgvNavigationStatus getNavigationStatus(EdgeCommonVO edgeCommonVO) {
AgvServiceGrpc.AgvServiceBlockingStub stub = grpcServiceManager.getGrpcClient(
edgeCommonVO.getTerminalId(), AgvServiceGrpc.AgvServiceBlockingStub.class);
AgvCommand.RobotStatusMapCommand.Request request = AgvCommand.RobotStatusMapCommand.Request.newBuilder()
AgvCommand.AgvNavigationStatusCommand.Request request = AgvCommand.AgvNavigationStatusCommand.Request.newBuilder()
.setHeader(EdgeCommonUtil.buildRequest(edgeCommonVO.getDeviceId()))
.build();
return executeGrpcCall(() -> stub.getMapStatus(request)).getStatus();
return executeGrpcCall(() -> stub.getNavigationStatus(request)).getStatus();
}
@Override
public AgvCommand.AgvDownloadMapResult robotConfigDownloadMap(EdgeCommonVO edgeCommonVO, String mapName) {
public void emergencyStop(EdgeCommonVO edgeCommonVO) {
AgvServiceGrpc.AgvServiceBlockingStub stub = grpcServiceManager.getGrpcClient(
edgeCommonVO.getTerminalId(), AgvServiceGrpc.AgvServiceBlockingStub.class);
Common.CommandHeader.Request request = EdgeCommonUtil.buildRequest(edgeCommonVO.getDeviceId());
executeGrpcCall(() -> stub.emergencyStop(request));
}
@Override
public void clearFault(EdgeCommonVO edgeCommonVO) {
AgvServiceGrpc.AgvServiceBlockingStub stub = grpcServiceManager.getGrpcClient(
edgeCommonVO.getTerminalId(), AgvServiceGrpc.AgvServiceBlockingStub.class);
Common.CommandHeader.Request request = EdgeCommonUtil.buildRequest(edgeCommonVO.getDeviceId());
executeGrpcCall(() -> stub.clearFault(request));
}
@Override
public void navigateToPose(EdgeCommonVO edgeCommonVO, Agv.AgvPose2d pose) {
if (pose == null) {
throw new GlobalException("目标位姿不能为空");
}
AgvServiceGrpc.AgvServiceBlockingStub stub = grpcServiceManager.getGrpcClient(
edgeCommonVO.getTerminalId(), AgvServiceGrpc.AgvServiceBlockingStub.class);
AgvCommand.AgvNavigateToPoseCommand.Request request = AgvCommand.AgvNavigateToPoseCommand.Request.newBuilder()
.setHeader(EdgeCommonUtil.buildRequest(edgeCommonVO.getDeviceId()))
.setPose(pose)
.build();
executeGrpcCall(() -> stub.navigateToPose(request));
}
@Override
public void navigateToStation(EdgeCommonVO edgeCommonVO, String stationId) {
if (StrUtil.isBlank(stationId)) {
throw new GlobalException("站点ID不能为空");
}
AgvServiceGrpc.AgvServiceBlockingStub stub = grpcServiceManager.getGrpcClient(
edgeCommonVO.getTerminalId(), AgvServiceGrpc.AgvServiceBlockingStub.class);
AgvCommand.AgvNavigateToStationCommand.Request request = AgvCommand.AgvNavigateToStationCommand.Request.newBuilder()
.setHeader(EdgeCommonUtil.buildRequest(edgeCommonVO.getDeviceId()))
.setStationId(stationId)
.build();
executeGrpcCall(() -> stub.navigateToStation(request));
}
@Override
public void followPath(EdgeCommonVO edgeCommonVO, List<Agv.AgvPathSegment> pathSegments) {
if (pathSegments == null || pathSegments.isEmpty()) {
throw new GlobalException("路径段不能为空");
}
AgvServiceGrpc.AgvServiceBlockingStub stub = grpcServiceManager.getGrpcClient(
edgeCommonVO.getTerminalId(), AgvServiceGrpc.AgvServiceBlockingStub.class);
AgvCommand.AgvFollowPathCommand.Request request = AgvCommand.AgvFollowPathCommand.Request.newBuilder()
.setHeader(EdgeCommonUtil.buildRequest(edgeCommonVO.getDeviceId()))
.addAllPath(pathSegments)
.build();
executeGrpcCall(() -> stub.followPath(request));
}
@Override
public void pauseNavigation(EdgeCommonVO edgeCommonVO) {
AgvServiceGrpc.AgvServiceBlockingStub stub = grpcServiceManager.getGrpcClient(
edgeCommonVO.getTerminalId(), AgvServiceGrpc.AgvServiceBlockingStub.class);
Common.CommandHeader.Request request = EdgeCommonUtil.buildRequest(edgeCommonVO.getDeviceId());
executeGrpcCall(() -> stub.pauseNavigation(request));
}
@Override
public void resumeNavigation(EdgeCommonVO edgeCommonVO) {
AgvServiceGrpc.AgvServiceBlockingStub stub = grpcServiceManager.getGrpcClient(
edgeCommonVO.getTerminalId(), AgvServiceGrpc.AgvServiceBlockingStub.class);
Common.CommandHeader.Request request = EdgeCommonUtil.buildRequest(edgeCommonVO.getDeviceId());
executeGrpcCall(() -> stub.resumeNavigation(request));
}
@Override
public void cancelNavigation(EdgeCommonVO edgeCommonVO) {
AgvServiceGrpc.AgvServiceBlockingStub stub = grpcServiceManager.getGrpcClient(
edgeCommonVO.getTerminalId(), AgvServiceGrpc.AgvServiceBlockingStub.class);
Common.CommandHeader.Request request = EdgeCommonUtil.buildRequest(edgeCommonVO.getDeviceId());
executeGrpcCall(() -> stub.cancelNavigation(request));
}
@Override
public void setVelocity(EdgeCommonVO edgeCommonVO, Agv.AgvVelocity velocity) {
if (velocity == null) {
throw new GlobalException("速度不能为空");
}
AgvServiceGrpc.AgvServiceBlockingStub stub = grpcServiceManager.getGrpcClient(
edgeCommonVO.getTerminalId(), AgvServiceGrpc.AgvServiceBlockingStub.class);
AgvCommand.AgvSetVelocityCommand.Request request = AgvCommand.AgvSetVelocityCommand.Request.newBuilder()
.setHeader(EdgeCommonUtil.buildRequest(edgeCommonVO.getDeviceId()))
.setVelocity(velocity)
.build();
executeGrpcCall(() -> stub.setVelocity(request));
}
@Override
public void stopVelocityControl(EdgeCommonVO edgeCommonVO) {
AgvServiceGrpc.AgvServiceBlockingStub stub = grpcServiceManager.getGrpcClient(
edgeCommonVO.getTerminalId(), AgvServiceGrpc.AgvServiceBlockingStub.class);
Common.CommandHeader.Request request = EdgeCommonUtil.buildRequest(edgeCommonVO.getDeviceId());
executeGrpcCall(() -> stub.stopVelocityControl(request));
}
@Override
public List<String> listMaps(EdgeCommonVO edgeCommonVO) {
AgvServiceGrpc.AgvServiceBlockingStub stub = grpcServiceManager.getGrpcClient(
edgeCommonVO.getTerminalId(), AgvServiceGrpc.AgvServiceBlockingStub.class);
AgvCommand.AgvListMapsCommand.Request request = AgvCommand.AgvListMapsCommand.Request.newBuilder()
.setHeader(EdgeCommonUtil.buildRequest(edgeCommonVO.getDeviceId()))
.build();
return executeGrpcCall(() -> stub.listMaps(request)).getMapsList();
}
@Override
public List<Agv.AgvStation> listStations(EdgeCommonVO edgeCommonVO) {
AgvServiceGrpc.AgvServiceBlockingStub stub = grpcServiceManager.getGrpcClient(
edgeCommonVO.getTerminalId(), AgvServiceGrpc.AgvServiceBlockingStub.class);
AgvCommand.AgvListStationsCommand.Request request = AgvCommand.AgvListStationsCommand.Request.newBuilder()
.setHeader(EdgeCommonUtil.buildRequest(edgeCommonVO.getDeviceId()))
.build();
return executeGrpcCall(() -> stub.listStations(request)).getStationsList();
}
@Override
public void switchMap(EdgeCommonVO edgeCommonVO, String mapName) {
if (StrUtil.isBlank(mapName)) {
throw new GlobalException("地图名称不能为空");
}
AgvServiceGrpc.AgvServiceBlockingStub stub = grpcServiceManager.getGrpcClient(
edgeCommonVO.getTerminalId(), AgvServiceGrpc.AgvServiceBlockingStub.class);
AgvCommand.RobotConfigDownloadMapRequestData requestData = AgvCommand.RobotConfigDownloadMapRequestData.newBuilder()
AgvCommand.AgvMapCommand.Request request = AgvCommand.AgvMapCommand.Request.newBuilder()
.setHeader(EdgeCommonUtil.buildRequest(edgeCommonVO.getDeviceId()))
.setMapName(mapName)
.build();
AgvCommand.RobotConfigDownloadMapCommand.Request request = AgvCommand.RobotConfigDownloadMapCommand.Request.newBuilder()
.setHeader(EdgeCommonUtil.buildRequest(edgeCommonVO.getDeviceId()))
.setData(requestData)
.build();
AgvCommand.AgvDownloadMapResult result = executeGrpcCall(() -> stub.robotConfigDownloadMap(request)).getStatus();
if (result.getRetCode() != 0) {
throw new GlobalException("下载地图失败: " + result.getErrMsg());
}
return result;
executeGrpcCall(() -> stub.switchMap(request));
}
@Override
public AgvCommand.AgvUploadMapResult robotConfigUploadMap(EdgeCommonVO edgeCommonVO, String mapContent) {
if (StrUtil.isBlank(mapContent)) {
public void uploadMap(EdgeCommonVO edgeCommonVO, String mapName, String content) {
if (StrUtil.isBlank(mapName)) {
throw new GlobalException("地图名称不能为空");
}
if (StrUtil.isBlank(content)) {
throw new GlobalException("地图内容不能为空");
}
AgvServiceGrpc.AgvServiceBlockingStub stub = grpcServiceManager.getGrpcClient(
edgeCommonVO.getTerminalId(), AgvServiceGrpc.AgvServiceBlockingStub.class);
AgvCommand.RobotConfigUploadMapRequestData requestData = AgvCommand.RobotConfigUploadMapRequestData.newBuilder()
.setMapContent(mapContent)
.build();
AgvCommand.RobotConfigUploadMapCommand.Request request = AgvCommand.RobotConfigUploadMapCommand.Request.newBuilder()
.setHeader(EdgeCommonUtil.buildRequest(edgeCommonVO.getDeviceId()))
.setData(requestData)
.build();
AgvCommand.AgvUploadMapResult result = executeGrpcCall(() -> stub.robotConfigUploadMap(request)).getStatus();
if (result.getRetCode() != 0) {
throw new GlobalException("上传地图失败: " + result.getErrMsg());
}
return result;
}
@Override
public AgvCommand.AgvBatteryStatus getBatteryStatus(EdgeCommonVO edgeCommonVO) {
AgvServiceGrpc.AgvServiceBlockingStub stub = grpcServiceManager.getGrpcClient(
edgeCommonVO.getTerminalId(), AgvServiceGrpc.AgvServiceBlockingStub.class);
AgvCommand.RobotStatusBatteryRequestData requestData = AgvCommand.RobotStatusBatteryRequestData.newBuilder()
.setSimple(true)
.build();
AgvCommand.RobotStatusBatteryCommand.Request request = AgvCommand.RobotStatusBatteryCommand.Request.newBuilder()
.setHeader(EdgeCommonUtil.buildRequest(edgeCommonVO.getDeviceId()))
.setData(requestData)
.build();
return executeGrpcCall(() -> stub.getBatteryStatus(request)).getStatus();
}
@Override
public AgvCommand.RobotGoTargetResData robotGoTarget(EdgeCommonVO edgeCommonVO, String sourceId, String targetId,
String taskId, String operation, Double jackHeight) {
if (StrUtil.isBlank(sourceId)) {
throw new GlobalException("起始站点ID不能为空");
}
if (StrUtil.isBlank(targetId)) {
throw new GlobalException("目标站点ID不能为空");
}
AgvServiceGrpc.AgvServiceBlockingStub stub = grpcServiceManager.getGrpcClient(
edgeCommonVO.getTerminalId(), AgvServiceGrpc.AgvServiceBlockingStub.class);
// 构建请求数据
AgvCommand.RobotGoTargetReqData.Builder dataBuilder = AgvCommand.RobotGoTargetReqData.newBuilder()
.setSourceId(sourceId)
.setId(targetId);
// 设置可选参数
if (StrUtil.isNotBlank(taskId)) {
dataBuilder.setTaskId(taskId);
}
if (StrUtil.isNotBlank(operation)) {
dataBuilder.setOperation(operation);
}
if (jackHeight != null) {
dataBuilder.setJackHeight(jackHeight);
}
AgvCommand.RobotGoTargetCommand.Request request = AgvCommand.RobotGoTargetCommand.Request.newBuilder()
AgvCommand.AgvMapCommand.Request request = AgvCommand.AgvMapCommand.Request.newBuilder()
.setHeader(EdgeCommonUtil.buildRequest(edgeCommonVO.getDeviceId()))
.setData(dataBuilder.build())
.setMapName(mapName)
.setContent(content)
.build();
AgvCommand.RobotGoTargetResData result = executeGrpcCall(() -> stub.robotGoTarget(request)).getData();
if (result.getRetCode() != 0) {
throw new GlobalException("单点导航任务下发失败: " + result.getErrMsg());
}
return result;
executeGrpcCall(() -> stub.uploadMap(request));
}
@Override
public AgvCommand.RobotGoTargetResData robotGoTargetWithFreeGo(EdgeCommonVO edgeCommonVO,
AgvCommand.FreeGoPoint freeGoPoint,
String taskId) {
if (freeGoPoint == null) {
throw new GlobalException("自由导航目标点不能为空");
}
AgvServiceGrpc.AgvServiceBlockingStub stub = grpcServiceManager.getGrpcClient(
edgeCommonVO.getTerminalId(), AgvServiceGrpc.AgvServiceBlockingStub.class);
// 构建请求数据使用freego参数
AgvCommand.RobotGoTargetReqData.Builder dataBuilder = AgvCommand.RobotGoTargetReqData.newBuilder()
.setSourceId("SELF_POSITION") // 从当前位置开始
.setId("SELF_POSITION") // 不使用站点ID
.setFreego(freeGoPoint); // 设置自由导航点
// 设置可选参数
if (StrUtil.isNotBlank(taskId)) {
dataBuilder.setTaskId(taskId);
}
AgvCommand.RobotGoTargetCommand.Request request = AgvCommand.RobotGoTargetCommand.Request.newBuilder()
.setHeader(EdgeCommonUtil.buildRequest(edgeCommonVO.getDeviceId()))
.setData(dataBuilder.build())
.build();
AgvCommand.RobotGoTargetResData result = executeGrpcCall(() -> stub.robotGoTarget(request)).getData();
if (result.getRetCode() != 0) {
throw new GlobalException("自由导航任务下发失败: " + result.getErrMsg());
}
return result;
}
@Override
public AgvCommand.RobotTaskPauseCommand.Feedback.Result robotTaskPause(EdgeCommonVO edgeCommonVO) {
AgvServiceGrpc.AgvServiceBlockingStub stub = grpcServiceManager.getGrpcClient(
edgeCommonVO.getTerminalId(), AgvServiceGrpc.AgvServiceBlockingStub.class);
AgvCommand.RobotTaskPauseCommand.Request request = AgvCommand.RobotTaskPauseCommand.Request.newBuilder()
.setHeader(EdgeCommonUtil.buildRequest(edgeCommonVO.getDeviceId()))
.build();
AgvCommand.RobotTaskPauseCommand.Feedback.Result result = executeGrpcCall(() -> stub.robotTaskPause(request)).getStatus();
if (result.getRetCode() != 0) {
throw new GlobalException("暂停导航任务失败: " + result.getErrMsg());
}
return result;
}
@Override
public AgvCommand.RobotTaskResumeCommand.Feedback.Result robotTaskResume(EdgeCommonVO edgeCommonVO) {
AgvServiceGrpc.AgvServiceBlockingStub stub = grpcServiceManager.getGrpcClient(
edgeCommonVO.getTerminalId(), AgvServiceGrpc.AgvServiceBlockingStub.class);
AgvCommand.RobotTaskResumeCommand.Request request = AgvCommand.RobotTaskResumeCommand.Request.newBuilder()
.setHeader(EdgeCommonUtil.buildRequest(edgeCommonVO.getDeviceId()))
.build();
AgvCommand.RobotTaskResumeCommand.Feedback.Result result = executeGrpcCall(() -> stub.robotTaskResume(request)).getStatus();
if (result.getRetCode() != 0) {
throw new GlobalException("继续导航任务失败: " + result.getErrMsg());
}
return result;
}
@Override
public AgvCommand.RobotTaskCancelCommand.Feedback.Result robotTaskCancel(EdgeCommonVO edgeCommonVO) {
AgvServiceGrpc.AgvServiceBlockingStub stub = grpcServiceManager.getGrpcClient(
edgeCommonVO.getTerminalId(), AgvServiceGrpc.AgvServiceBlockingStub.class);
AgvCommand.RobotTaskCancelCommand.Request request = AgvCommand.RobotTaskCancelCommand.Request.newBuilder()
.setHeader(EdgeCommonUtil.buildRequest(edgeCommonVO.getDeviceId()))
.build();
AgvCommand.RobotTaskCancelCommand.Feedback.Result result = executeGrpcCall(() -> stub.robotTaskCancel(request)).getStatus();
if (result.getRetCode() != 0) {
throw new GlobalException("取消导航任务失败: " + result.getErrMsg());
}
return result;
}
@Override
public AgvCommand.RobotStatusTaskResData getRobotStatusTaskCurrent(EdgeCommonVO edgeCommonVO, boolean simple) {
AgvServiceGrpc.AgvServiceBlockingStub stub = grpcServiceManager.getGrpcClient(
edgeCommonVO.getTerminalId(), AgvServiceGrpc.AgvServiceBlockingStub.class);
AgvCommand.RobotStatusTaskReqData requestData = AgvCommand.RobotStatusTaskReqData.newBuilder()
.setSimple(simple)
.build();
AgvCommand.RobotStatusTaskCurrentCommand.Request request = AgvCommand.RobotStatusTaskCurrentCommand.Request.newBuilder()
.setHeader(EdgeCommonUtil.buildRequest(edgeCommonVO.getDeviceId()))
.setData(requestData)
.build();
return executeGrpcCall(() -> stub.robotStatusTaskCurrent(request)).getData();
}
@Override
public AgvCommand.QueryStationListResult queryStationList(EdgeCommonVO edgeCommonVO) {
AgvServiceGrpc.AgvServiceBlockingStub stub = grpcServiceManager.getGrpcClient(
edgeCommonVO.getTerminalId(), AgvServiceGrpc.AgvServiceBlockingStub.class);
AgvCommand.QueryStationListCommand.Request request = AgvCommand.QueryStationListCommand.Request.newBuilder()
.setHeader(EdgeCommonUtil.buildRequest(edgeCommonVO.getDeviceId()))
.build();
AgvCommand.QueryStationListResult result = executeGrpcCall(() -> stub.queryStationList(request)).getStatus();
if (result.getRetCode() != 0) {
throw new GlobalException("查询站点列表失败: " + result.getErrMsg());
}
return result;
}
@Override
public AgvCommand.RobotLoadMapResult robotLoadMap(EdgeCommonVO edgeCommonVO, String mapName) {
public String downloadMap(EdgeCommonVO edgeCommonVO, String mapName) {
if (StrUtil.isBlank(mapName)) {
throw new GlobalException("地图名称不能为空");
}
AgvServiceGrpc.AgvServiceBlockingStub stub = grpcServiceManager.getGrpcClient(
edgeCommonVO.getTerminalId(), AgvServiceGrpc.AgvServiceBlockingStub.class);
AgvCommand.RobotLoadMapRequestData requestData = AgvCommand.RobotLoadMapRequestData.newBuilder()
AgvCommand.AgvMapCommand.Request request = AgvCommand.AgvMapCommand.Request.newBuilder()
.setHeader(EdgeCommonUtil.buildRequest(edgeCommonVO.getDeviceId()))
.setMapName(mapName)
.build();
AgvCommand.RobotLoadMapCommand.Request request = AgvCommand.RobotLoadMapCommand.Request.newBuilder()
.setHeader(EdgeCommonUtil.buildRequest(edgeCommonVO.getDeviceId()))
.setData(requestData)
.build();
AgvCommand.RobotLoadMapResult result = executeGrpcCall(() -> stub.robotLoadMap(request)).getStatus();
if (result.getRetCode() != 0) {
throw new GlobalException("切换地图失败: " + result.getErrMsg());
}
return result;
return executeGrpcCall(() -> stub.downloadMap(request)).getContent();
}
@Override
public AgvCommand.RobotQueryLoadMapStatusResult queryLoadMapStatus(EdgeCommonVO edgeCommonVO) {
public String startMapping(EdgeCommonVO edgeCommonVO, Agv.AgvMapDimension dimension, String mapName, boolean realTime) {
AgvServiceGrpc.AgvServiceBlockingStub stub = grpcServiceManager.getGrpcClient(
edgeCommonVO.getTerminalId(), AgvServiceGrpc.AgvServiceBlockingStub.class);
AgvCommand.RobotQueryLoadMapStatusCommand.Request request = AgvCommand.RobotQueryLoadMapStatusCommand.Request.newBuilder()
AgvCommand.AgvStartMappingCommand.Request.Builder requestBuilder = AgvCommand.AgvStartMappingCommand.Request.newBuilder()
.setHeader(EdgeCommonUtil.buildRequest(edgeCommonVO.getDeviceId()))
.build();
return executeGrpcCall(() -> stub.queryLoadMapStatus(request)).getStatus();
.setDimension(dimension != null ? dimension : Agv.AgvMapDimension.AGV_MAP_DIMENSION_UNSPECIFIED)
.setRealTime(realTime);
if (StrUtil.isNotBlank(mapName)) {
requestBuilder.setMapName(mapName);
}
AgvCommand.AgvStartMappingCommand.Request request = requestBuilder.build();
return executeGrpcCall(() -> stub.startMapping(request)).getSessionId();
}
@Override
public void stopMapping(EdgeCommonVO edgeCommonVO) {
AgvServiceGrpc.AgvServiceBlockingStub stub = grpcServiceManager.getGrpcClient(
edgeCommonVO.getTerminalId(), AgvServiceGrpc.AgvServiceBlockingStub.class);
Common.CommandHeader.Request request = EdgeCommonUtil.buildRequest(edgeCommonVO.getDeviceId());
executeGrpcCall(() -> stub.stopMapping(request));
}
/**

View File

@ -23,70 +23,64 @@ public final class AgvServiceOuterClass {
descriptor;
static {
java.lang.String[] descriptorData = {
"\n\032cmvr/api/agv_service.proto\022\010cmvr.api\032\032" +
"cmvr/api/agv_command.proto2\244\021\n\nAgvServic" +
"e\022f\n\rGetStatusInfo\022).cmvr.api.GetAgvStat" +
"usInfoCommand.Request\032*.cmvr.api.GetAgvS" +
"tatusInfoCommand.Feedback\022m\n\020GetBatteryS" +
"tatus\022+.cmvr.api.RobotStatusBatteryComma" +
"nd.Request\032,.cmvr.api.RobotStatusBattery" +
"Command.Feedback\022e\n\020GetRobotLocation\022\'.c" +
"mvr.api.RobotStatusLocCommand.Request\032(." +
"cmvr.api.RobotStatusLocCommand.Feedback\022" +
"{\n\026RobotConfigDownloadMap\022/.cmvr.api.Rob" +
"otConfigDownloadMapCommand.Request\0320.cmv" +
"r.api.RobotConfigDownloadMapCommand.Feed" +
"back\022a\n\014GetMapStatus\022\'.cmvr.api.RobotSta" +
"tusMapCommand.Request\032(.cmvr.api.RobotSt" +
"atusMapCommand.Feedback\022u\n\024RobotConfigUp" +
"loadMap\022-.cmvr.api.RobotConfigUploadMapC" +
"ommand.Request\032..cmvr.api.RobotConfigUpl" +
"oadMapCommand.Feedback\022f\n\017RobotConfigLoc" +
"k\022(.cmvr.api.RobotConfigLockCommand.Requ" +
"est\032).cmvr.api.RobotConfigLockCommand.Fe" +
"edback\022y\n\024GetCurrentLockStatus\022/.cmvr.ap" +
"i.RobotStatusCurrentLockCommand.Request\032" +
"0.cmvr.api.RobotStatusCurrentLockCommand" +
".Feedback\022o\n\022RobotMotionControl\022+.cmvr.a" +
"pi.RobotMotionControlCommand.Request\032,.c" +
"mvr.api.RobotMotionControlCommand.Feedba" +
"ck\022]\n\014RobotLoadMap\022%.cmvr.api.RobotLoadM" +
"apCommand.Request\032&.cmvr.api.RobotLoadMa" +
"pCommand.Feedback\022y\n\022QueryLoadMapStatus\022" +
"0.cmvr.api.RobotQueryLoadMapStatusComman" +
"d.Request\0321.cmvr.api.RobotQueryLoadMapSt" +
"atusCommand.Feedback\022i\n\020QueryStationList" +
"\022).cmvr.api.QueryStationListCommand.Requ" +
"est\032*.cmvr.api.QueryStationListCommand.F" +
"eedback\022l\n\021RobotGoTargetList\022*.cmvr.api." +
"RobotGoTargetListCommand.Request\032+.cmvr." +
"api.RobotGoTargetListCommand.Feedback\022{\n" +
"\026RobotStatusTaskCurrent\022/.cmvr.api.Robot" +
"StatusTaskCurrentCommand.Request\0320.cmvr." +
"api.RobotStatusTaskCurrentCommand.Feedba" +
"ck\022{\n\026RobotStatusTaskPackage\022/.cmvr.api." +
"RobotStatusTaskPackageCommand.Request\0320." +
"cmvr.api.RobotStatusTaskPackageCommand.F" +
"eedback\022`\n\rRobotGoTarget\022&.cmvr.api.Robo" +
"tGoTargetCommand.Request\032\'.cmvr.api.Robo" +
"tGoTargetCommand.Feedback\022i\n\020RobotContro" +
"lStop\022).cmvr.api.RobotControlStopCommand" +
".Request\032*.cmvr.api.RobotControlStopComm" +
"and.Feedback\022c\n\016RobotTaskPause\022\'.cmvr.ap" +
"i.RobotTaskPauseCommand.Request\032(.cmvr.a" +
"pi.RobotTaskPauseCommand.Feedback\022f\n\017Rob" +
"otTaskResume\022(.cmvr.api.RobotTaskResumeC" +
"ommand.Request\032).cmvr.api.RobotTaskResum" +
"eCommand.Feedback\022f\n\017RobotTaskCancel\022(.c" +
"mvr.api.RobotTaskCancelCommand.Request\032)" +
".cmvr.api.RobotTaskCancelCommand.Feedbac" +
"kb\006proto3"
"\n\032cmvr/api/agv_service.proto\022\010cmvr.api\032\025" +
"cmvr/api/common.proto\032\032cmvr/api/agv_comm" +
"and.proto2\320\016\n\nAgvService\022f\n\017getRuntimeSt" +
"ate\022(.cmvr.api.AgvRuntimeStateCommand.Re" +
"quest\032).cmvr.api.AgvRuntimeStateCommand." +
"Feedback\022r\n\023getNavigationStatus\022,.cmvr.a" +
"pi.AgvNavigationStatusCommand.Request\032-." +
"cmvr.api.AgvNavigationStatusCommand.Feed" +
"back\022R\n\remergencyStop\022\037.cmvr.api.Command" +
"Header.Request\032 .cmvr.api.CommandHeader." +
"Feedback\022O\n\nclearFault\022\037.cmvr.api.Comman" +
"dHeader.Request\032 .cmvr.api.CommandHeader" +
".Feedback\022i\n\016navigateToPose\022*.cmvr.api.A" +
"gvNavigateToPoseCommand.Request\032+.cmvr.a" +
"pi.AgvNavigateToPoseCommand.Feedback\022r\n\021" +
"navigateToStation\022-.cmvr.api.AgvNavigate" +
"ToStationCommand.Request\032..cmvr.api.AgvN" +
"avigateToStationCommand.Feedback\022]\n\nfoll" +
"owPath\022&.cmvr.api.AgvFollowPathCommand.R" +
"equest\032\'.cmvr.api.AgvFollowPathCommand.F" +
"eedback\022T\n\017pauseNavigation\022\037.cmvr.api.Co" +
"mmandHeader.Request\032 .cmvr.api.CommandHe" +
"ader.Feedback\022U\n\020resumeNavigation\022\037.cmvr" +
".api.CommandHeader.Request\032 .cmvr.api.Co" +
"mmandHeader.Feedback\022U\n\020cancelNavigation" +
"\022\037.cmvr.api.CommandHeader.Request\032 .cmvr" +
".api.CommandHeader.Feedback\022`\n\013setVeloci" +
"ty\022\'.cmvr.api.AgvSetVelocityCommand.Requ" +
"est\032(.cmvr.api.AgvSetVelocityCommand.Fee" +
"dback\022X\n\023stopVelocityControl\022\037.cmvr.api." +
"CommandHeader.Request\032 .cmvr.api.Command" +
"Header.Feedback\022W\n\010listMaps\022$.cmvr.api.A" +
"gvListMapsCommand.Request\032%.cmvr.api.Agv" +
"ListMapsCommand.Feedback\022c\n\014listStations" +
"\022(.cmvr.api.AgvListStationsCommand.Reque" +
"st\032).cmvr.api.AgvListStationsCommand.Fee" +
"dback\022N\n\tswitchMap\022\037.cmvr.api.AgvMapComm" +
"and.Request\032 .cmvr.api.AgvMapCommand.Fee" +
"dback\022N\n\tuploadMap\022\037.cmvr.api.AgvMapComm" +
"and.Request\032 .cmvr.api.AgvMapCommand.Fee" +
"dback\022P\n\013downloadMap\022\037.cmvr.api.AgvMapCo" +
"mmand.Request\032 .cmvr.api.AgvMapCommand.F" +
"eedback\022c\n\014startMapping\022(.cmvr.api.AgvSt" +
"artMappingCommand.Request\032).cmvr.api.Agv" +
"StartMappingCommand.Feedback\022\\\n\tstreamMa" +
"p\022%.cmvr.api.AgvMapStreamCommand.Request" +
"\032&.cmvr.api.AgvMapStreamCommand.Feedback" +
"0\001\022P\n\013stopMapping\022\037.cmvr.api.CommandHead" +
"er.Request\032 .cmvr.api.CommandHeader.Feed" +
"backb\006proto3"
};
descriptor = com.google.protobuf.Descriptors.FileDescriptor
.internalBuildGeneratedFileFrom(descriptorData,
new com.google.protobuf.Descriptors.FileDescriptor[] {
cmvr.api.Common.getDescriptor(),
cmvr.api.AgvCommand.getDescriptor(),
});
cmvr.api.Common.getDescriptor();
cmvr.api.AgvCommand.getDescriptor();
}

File diff suppressed because it is too large Load Diff

View File

@ -1,74 +1,72 @@
/**
* @file agv_service.proto
* @brief AGV服务的gRPC接口AGV相关命令
* gRPCAGVServiceImpl
*/
syntax = "proto3";
import "cmvr/api/agv_command.proto";
package cmvr.api;
import "cmvr/api/common.proto";
import "cmvr/api/agv_command.proto";
// AGV
// AGV
service AgvService {
//
rpc GetStatusInfo(GetAgvStatusInfoCommand.Request) returns (GetAgvStatusInfoCommand.Feedback);
// AGV
rpc getRuntimeState(AgvRuntimeStateCommand.Request) returns (AgvRuntimeStateCommand.Feedback);
rpc GetBatteryStatus(RobotStatusBatteryCommand.Request) returns (RobotStatusBatteryCommand.Feedback);
//
rpc getNavigationStatus(AgvNavigationStatusCommand.Request) returns (AgvNavigationStatusCommand.Feedback);
rpc GetRobotLocation(RobotStatusLocCommand.Request) returns (RobotStatusLocCommand.Feedback);
//
rpc emergencyStop(CommandHeader.Request) returns (CommandHeader.Feedback);
rpc RobotConfigDownloadMap(RobotConfigDownloadMapCommand.Request) returns (RobotConfigDownloadMapCommand.Feedback);
//
rpc clearFault(CommandHeader.Request) returns (CommandHeader.Feedback);
// 姿姿 x/y theta
rpc navigateToPose(AgvNavigateToPoseCommand.Request) returns (AgvNavigateToPoseCommand.Feedback);
rpc GetMapStatus(RobotStatusMapCommand.Request) returns (RobotStatusMapCommand.Feedback);
//
rpc navigateToStation(AgvNavigateToStationCommand.Request) returns (AgvNavigateToStationCommand.Feedback);
//
rpc followPath(AgvFollowPathCommand.Request) returns (AgvFollowPathCommand.Feedback);
rpc RobotConfigUploadMap(RobotConfigUploadMapCommand.Request) returns (RobotConfigUploadMapCommand.Feedback);
//
rpc pauseNavigation(CommandHeader.Request) returns (CommandHeader.Feedback);
//
rpc RobotConfigLock(RobotConfigLockCommand.Request) returns (RobotConfigLockCommand.Feedback);
//
rpc resumeNavigation(CommandHeader.Request) returns (CommandHeader.Feedback);
//
rpc GetCurrentLockStatus(RobotStatusCurrentLockCommand.Request) returns (RobotStatusCurrentLockCommand.Feedback);
//
rpc cancelNavigation(CommandHeader.Request) returns (CommandHeader.Feedback);
//
rpc RobotMotionControl(RobotMotionControlCommand.Request) returns (RobotMotionControlCommand.Feedback);
// vx/vy /wz /
rpc setVelocity(AgvSetVelocityCommand.Request) returns (AgvSetVelocityCommand.Feedback);
// 2022
rpc RobotLoadMap(RobotLoadMapCommand.Request) returns (RobotLoadMapCommand.Feedback);
//
rpc stopVelocityControl(CommandHeader.Request) returns (CommandHeader.Feedback);
// AGV
rpc listMaps(AgvListMapsCommand.Request) returns (AgvListMapsCommand.Feedback);
// 1022
rpc QueryLoadMapStatus(RobotQueryLoadMapStatusCommand.Request) returns (RobotQueryLoadMapStatusCommand.Feedback);
//
rpc listStations(AgvListStationsCommand.Request) returns (AgvListStationsCommand.Feedback);
// 使
rpc switchMap(AgvMapCommand.Request) returns (AgvMapCommand.Feedback);
// 1301
rpc QueryStationList(QueryStationListCommand.Request) returns (QueryStationListCommand.Feedback);
// AGV
rpc uploadMap(AgvMapCommand.Request) returns (AgvMapCommand.Feedback);
//
rpc downloadMap(AgvMapCommand.Request) returns (AgvMapCommand.Feedback);
// 3066
rpc RobotGoTargetList(RobotGoTargetListCommand.Request) returns (RobotGoTargetListCommand.Feedback);
// / 2D3D
// AGV
rpc startMapping(AgvStartMappingCommand.Request) returns (AgvStartMappingCommand.Feedback);
// 1020 robot_status_task_req
// 1020 robot_status_task_req
rpc RobotStatusTaskCurrent(RobotStatusTaskCurrentCommand.Request) returns (RobotStatusTaskCurrentCommand.Feedback);
// resume_token
// AGV update_type
rpc streamMap(AgvMapStreamCommand.Request) returns (stream AgvMapStreamCommand.Feedback);
// 1110 robot_status_task_status_package_req
rpc RobotStatusTaskPackage(RobotStatusTaskPackageCommand.Request) returns (RobotStatusTaskPackageCommand.Feedback);
// 3051 robot_task_gotarget_req 0x0BEB
rpc RobotGoTarget(RobotGoTargetCommand.Request) returns (RobotGoTargetCommand.Feedback);
// 0x07D0
rpc RobotControlStop(RobotControlStopCommand.Request) returns (RobotControlStopCommand.Feedback);
// 3001 (0x0BB9)
rpc RobotTaskPause(RobotTaskPauseCommand.Request) returns (RobotTaskPauseCommand.Feedback);
// 3002 (0x0BBA)
rpc RobotTaskResume(RobotTaskResumeCommand.Request) returns (RobotTaskResumeCommand.Feedback);
// 3003 (0x0BBB)
rpc RobotTaskCancel(RobotTaskCancelCommand.Request) returns (RobotTaskCancelCommand.Feedback);
// /
rpc stopMapping(CommandHeader.Request) returns (CommandHeader.Feedback);
}

View File

@ -0,0 +1,310 @@
syntax = "proto3";
package cmvr.msgs;
// AGV 姿
message AgvPose2d {
// X
double x = 1;
// Y
double y = 2;
//
double theta = 3;
}
// AGV
message AgvVelocity {
// X 线/
double vx = 1;
// Y 线/
double vy = 2;
// Z /
double wz = 3;
}
// AGV
message AgvBatteryState {
// [0, 1] 0.8 80%
double percentage = 1;
//
double voltage = 2;
// AGV
double current = 3;
//
double temperature = 4;
//
bool charging = 5;
}
//
message AgvMotionOptions {
// 线/0 使 AGV
double max_speed = 1;
// /0 使 AGV
double max_angular_speed = 2;
// 线/^20 使 AGV
double max_acceleration = 3;
// /^20 使 AGV
double max_angular_acceleration = 4;
// 0 使 AGV
double reach_distance = 5;
// 0 使 AGV
double reach_angle = 6;
// [0, 1]1
double speed_ratio = 7;
// true
bool asynchronous = 8;
}
// AGV
message AgvAdapterParams {
// 使 AGV
map<string, string> values = 1;
}
// AGV
message AgvRuntimeState {
// Unix
double timestamp = 1;
// AgvMode
int32 mode = 2;
// AGV
bool connected = 3;
//
bool localized = 4;
// AGV
bool moving = 5;
// AGV
bool fault = 6;
// AGV
bool emergency_stopped = 7;
// 姿
AgvPose2d pose = 8;
//
AgvVelocity velocity = 9;
//
AgvBatteryState battery = 10;
//
string current_map = 11;
// id
string current_station = 12;
//
string last_error = 13;
}
//
message AgvStation {
// id
string id = 1;
//
string type = 2;
// 姿
AgvPose2d pose = 3;
//
string description = 4;
}
//
message AgvPathSegment {
// id
string source_station = 1;
// id
string target_station = 2;
}
// 2D3D
// AGV
enum AgvMapDimension {
//
AGV_MAP_DIMENSION_UNSPECIFIED = 0;
// 2D
AGV_MAP_2D = 1;
// 3D
AGV_MAP_3D = 2;
// 2D 3D
AGV_MAP_2D_AND_3D = 3;
}
//
enum AgvMapUpdateType {
//
AGV_MAP_UPDATE_UNSPECIFIED = 0;
//
AGV_MAP_UPDATE_SNAPSHOT = 1;
//
AGV_MAP_UPDATE_INCREMENTAL = 2;
//
AGV_MAP_UPDATE_RESET = 3;
}
// 2D/3D
enum AgvMapObjectType {
//
AGV_MAP_OBJECT_UNSPECIFIED = 0;
//
AGV_MAP_OBJECT_STATION = 1;
// 线线线线
AGV_MAP_OBJECT_LINE = 2;
//
AGV_MAP_OBJECT_AREA = 3;
//
AGV_MAP_OBJECT_QR_TAG = 4;
//
AGV_MAP_OBJECT_REFLECTOR = 5;
//
AGV_MAP_OBJECT_BIN_LOCATION = 6;
//
AGV_MAP_OBJECT_EXTERNAL_DEVICE = 7;
}
//
message AgvMapPoint3D {
// X
double x = 1;
// Y
double y = 2;
// Z 2D 0
double z = 3;
}
// 2D index = y * width + x
message AgvUnifiedMap2D {
// "map"
string frame_id = 1;
// Unix
double timestamp = 2;
// /
double resolution = 3;
//
uint32 width = 4;
//
uint32 height = 5;
// (0, 0) /姿x/y theta
AgvPose2d origin = 6;
// -1 0 100
repeated int32 data = 7;
// 线
repeated AgvMapObject objects = 8;
}
// 3D
message AgvMapPointSample3D {
// X
double x = 1;
// Y
double y = 2;
// Z
double z = 3;
// 0
float intensity = 4;
// 线/ 0
uint32 ring = 5;
// 0
double time_offset = 6;
}
// 3D AgvUnifiedMap3D.voxel_resolution
message AgvMapVoxel3D {
// X
int32 x = 1;
// Y
int32 y = 2;
// Z
int32 z = 3;
// [0, 1] -1
float probability = 4;
}
// 3D
// normal.x * x + normal.y * y + normal.z * z + d = 0
message AgvMapPlane3D {
//
AgvMapPoint3D center = 1;
//
AgvMapPoint3D normal = 2;
//
double d = 3;
//
double radius = 4;
}
// 使
message AgvMapObject {
// id
string id = 1;
//
AgvMapObjectType type = 2;
// 使 1 线使使
repeated AgvMapPoint3D points = 3;
// 0
double heading = 4;
// AGV使
map<string, string> properties = 5;
}
// 3D
// 3D
message AgvUnifiedMap3D {
// "map"
string frame_id = 1;
// Unix
double timestamp = 2;
// / 0
double voxel_resolution = 3;
//
repeated AgvMapPointSample3D points = 4;
//
repeated AgvMapVoxel3D voxels = 5;
//
repeated AgvMapPlane3D planes = 6;
// 3D
repeated AgvMapObject objects = 7;
}
// AGV map_2d map_3d
//
message AgvUnifiedMapUpdate {
// id map_name AGV
string map_id = 1;
// id
string session_id = 2;
// 0 1
uint64 sequence = 3;
//
string resume_token = 4;
//
AgvMapDimension dimension = 5;
//
AgvMapUpdateType update_type = 6;
// "map"
string frame_id = 7;
// Unix
double timestamp = 8;
//
bool snapshot_begin = 9;
//
bool snapshot_end = 10;
// 0
uint32 chunk_index = 11;
// 0
uint32 chunk_count = 12;
oneof payload {
// 2D 2D
AgvUnifiedMap2D map_2d = 20;
// 3D 3D
AgvUnifiedMap3D map_3d = 21;
}
}
//
message AgvNavigationStatus {
// AgvTaskState
int32 state = 1;
// AgvTaskType
int32 type = 2;
// [0, 1] 0
double progress = 3;
//
string message = 4;
}

View File

@ -22,6 +22,8 @@ import java.io.FileOutputStream;
import java.io.InputStream;
import java.nio.file.Files;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Component
@ -197,7 +199,7 @@ public class MinioService {
* @param bucketName bucket名称
* @param path 路径
* @param fileName 文件名
* @return
* @return true/false
*/
public Boolean isExist(String bucketName, String path, String fileName) {
try {
@ -236,5 +238,68 @@ public class MinioService {
}
}
/**
* 上传文件流
*
* @param bucketName bucket名称
* @param objectName 对象名称完整路径
* @param inputStream 输入流
* @param size 文件大小
* @param contentType 内容类型
*/
public void uploadStream(String bucketName, String objectName, InputStream inputStream, long size, String contentType) {
try {
Map<String, String> extraHeaders = new HashMap<>();
extraHeaders.put("x-amz-acl", "public-read");
PutObjectArgs objectArgs = PutObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.stream(inputStream, size, -1)
.extraHeaders(extraHeaders)
.contentType(contentType)
.build();
minioClient.putObject(objectArgs);
} catch (Exception e) {
log.error("上传文件流失败, objectName: {}", objectName, e);
throw new RuntimeException("上传文件流失败", e);
}
}
/**
* 删除文件
*
* @param bucketName bucket名称
* @param objectName 对象名称
*/
public void deleteFile(String bucketName, String objectName) {
try {
minioClient.removeObject(RemoveObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.build());
} catch (Exception e) {
log.error("删除文件失败, objectName: {}", objectName, e);
throw new RuntimeException("删除文件失败", e);
}
}
/**
* 获取文件输入流
*
* @param bucketName bucket名称
* @param objectName 对象名称
* @return 输入流
*/
public InputStream getObject(String bucketName, String objectName) {
try {
return minioClient.getObject(GetObjectArgs.builder()
.bucket(bucketName)
.object(objectName)
.build());
} catch (Exception e) {
log.error("获取文件失败, objectName: {}", objectName, e);
throw new RuntimeException("获取文件失败", e);
}
}
}

View File

@ -1,16 +1,21 @@
package com.cmvr.common.utils.http;
import cn.hutool.json.JSONUtil;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ConnectException;
import java.net.HttpURLConnection;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
import java.security.cert.X509Certificate;
import java.util.Map;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
@ -271,4 +276,90 @@ public class HttpUtils
return true;
}
}
/**
* 向指定 URL 发送POST方法的请求返回字节数组用于接收文件流
*
* @param url 发送请求的 URL
* @param params 请求参数
* @return 所代表远程资源的响应结果字节数组
*/
public static byte[] sendPostReturnBytes(String url, Map<String, Object> params)
{
HttpURLConnection conn = null;
try
{
log.info("sendPostReturnBytes - {}", url);
URL realUrl = new URL(url);
conn = (HttpURLConnection) realUrl.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("accept", "*/*");
conn.setRequestProperty("connection", "Keep-Alive");
conn.setRequestProperty("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)");
conn.setRequestProperty("Content-Type", "application/json;charset=utf-8");
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setConnectTimeout(30000);
conn.setReadTimeout(60000);
// 发送请求参数
String jsonParams = JSONUtil.toJsonStr(params);
try (OutputStream os = conn.getOutputStream())
{
os.write(jsonParams.getBytes(StandardCharsets.UTF_8));
os.flush();
}
// 读取响应
int responseCode = conn.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK)
{
try (InputStream is = conn.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream())
{
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1)
{
baos.write(buffer, 0, len);
}
byte[] result = baos.toByteArray();
log.info("recv bytes length: {}", result.length);
return result;
}
}
else
{
log.error("HTTP error code: {}", responseCode);
throw new RuntimeException("HTTP error code: " + responseCode);
}
}
catch (ConnectException e)
{
log.error("调用HttpUtils.sendPostReturnBytes ConnectException, url=" + url, e);
throw new RuntimeException("连接外部TTS服务失败", e);
}
catch (SocketTimeoutException e)
{
log.error("调用HttpUtils.sendPostReturnBytes SocketTimeoutException, url=" + url, e);
throw new RuntimeException("外部TTS服务超时", e);
}
catch (IOException e)
{
log.error("调用HttpUtils.sendPostReturnBytes IOException, url=" + url, e);
throw new RuntimeException("IO异常", e);
}
catch (Exception e)
{
log.error("调用HttpUtils.sendPostReturnBytes Exception, url=" + url, e);
throw new RuntimeException("调用外部TTS服务异常", e);
}
finally
{
if (conn != null)
{
conn.disconnect();
}
}
}
}

View File

@ -3,10 +3,10 @@ package com.cmvr.inspection.service;
import java.util.List;
import cmvr.msgs.Agv;
import com.baomidou.mybatisplus.extension.service.IService;
import com.cmvr.inspection.domain.InspectionRobot;
import com.cmvr.inspection.domain.vo.InspectionRobotVo;
import cmvr.api.AgvCommand;
/**
* 巡检机器人Service接口
@ -60,9 +60,9 @@ public interface IInspectionRobotService extends IService<InspectionRobot>
* 获取机器人地图列表从AGV获取
*
* @param robotId 机器人ID
* @return 地图状态信息
* @return 地图列表
*/
AgvCommand.AgvMapStatus getRobotMapList(String robotId);
List<String> getRobotMapList(String robotId);
/**
* 绑定机器人地图
@ -78,7 +78,7 @@ public interface IInspectionRobotService extends IService<InspectionRobot>
*
* @param robotId 机器人ID
* @param mapName 地图名称
* @return 地图ID
* @return 地图内容
*/
String downloadMapFromRobot(String robotId, String mapName);
@ -102,9 +102,9 @@ public interface IInspectionRobotService extends IService<InspectionRobot>
* 获取机器人实时位置
*
* @param robotId 机器人ID
* @return 机器人位置信息包含XY坐标角度当前站点等
* @return 机器人位置信息
*/
AgvCommand.AgvRobotLocation getRobotRealtimeLocation(String robotId);
Agv.AgvPose2d getRobotRealtimeLocation(String robotId);
/**
* 导航到指定位置坐标
@ -113,10 +113,25 @@ public interface IInspectionRobotService extends IService<InspectionRobot>
* @param x X坐标
* @param y Y坐标
* @param theta 朝向角弧度
* @param taskId 任务ID可选
* @return 导航任务下发结果
*/
AgvCommand.RobotGoTargetResData navigateToPosition(String robotId, Double x, Double y,
Double theta, String taskId);
void navigateToPosition(String robotId, Double x, Double y, Double theta);
/**
* 开始建图
*
* @param robotId 机器人ID
* @param dimension 建图维度0:未指定 1:2D 2:3D 3:2D+3D
* @param mapName 地图名称可选
* @param realTime 是否实时建图
* @return 会话ID
*/
String startMapping(String robotId, int dimension, String mapName, boolean realTime);
/**
* 停止建图
*
* @param robotId 机器人ID
*/
void stopMapping(String robotId);
}

View File

@ -1,12 +1,11 @@
package com.cmvr.inspection.service.impl;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import cmvr.api.AgvCommand;
import cmvr.msgs.Agv;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cmvr.common.utils.SecurityUtils;
import com.cmvr.device.domain.DeDeviceTerminalConfig;
@ -134,10 +133,10 @@ public class InspectionRobotServiceImpl extends ServiceImpl<InspectionRobotMappe
* 获取机器人地图列表从AGV获取
*
* @param robotId 机器人ID
* @return 地图状态信息
* @return 地图列表
*/
@Override
public AgvCommand.AgvMapStatus getRobotMapList(String robotId)
public List<String> getRobotMapList(String robotId)
{
InspectionRobot robot = inspectionRobotMapper.selectInspectionRobotById(robotId);
if (robot == null) {
@ -151,7 +150,7 @@ public class InspectionRobotServiceImpl extends ServiceImpl<InspectionRobotMappe
edgeCommonVO.setTerminalId(robot.getTerminalId());
edgeCommonVO.setDeviceId(robot.getRobotCode());
return edgeAgvService.getMapStatus(edgeCommonVO);
return edgeAgvService.listMaps(edgeCommonVO);
}
/**
@ -186,7 +185,7 @@ public class InspectionRobotServiceImpl extends ServiceImpl<InspectionRobotMappe
*
* @param robotId 机器人ID
* @param mapName 地图名称
* @return 地图ID
* @return 地图内容
*/
@Override
public String downloadMapFromRobot(String robotId, String mapName)
@ -200,26 +199,7 @@ public class InspectionRobotServiceImpl extends ServiceImpl<InspectionRobotMappe
edgeCommonVO.setTerminalId(robot.getTerminalId());
edgeCommonVO.setDeviceId(robot.getRobotCode());
AgvCommand.AgvDownloadMapResult result = edgeAgvService.robotConfigDownloadMap(edgeCommonVO, mapName);
// 保存地图到数据库
// InspectionMap inspectionMap = new InspectionMap();
// inspectionMap.setId(UUID.randomUUID().toString().replace("-", ""));
// inspectionMap.setMapName(mapName);
// inspectionMap.setMapSourceRobotId(robotId);
// inspectionMap.setMapSourceName(mapName);
// TODO: 将地图内容保存到文件系统并返回文件路径
// String filePath = saveMapToFile(result.getMapContent(), mapName);
// inspectionMap.setMapFilePath(filePath);
// inspectionMap.setStatus("0");
// inspectionMap.setCreateTime(DateUtils.getNowDate());
// inspectionMap.setCreateBy(SecurityUtils.getUsername());
//
// inspectionMapMapper.insertInspectionMap(inspectionMap);
return result.getMapContent();
return edgeAgvService.downloadMap(edgeCommonVO, mapName);
}
/**
@ -262,15 +242,8 @@ public class InspectionRobotServiceImpl extends ServiceImpl<InspectionRobotMappe
sourceEdgeVO.setTerminalId(sourceRobot.getTerminalId());
sourceEdgeVO.setDeviceId(sourceRobot.getRobotCode());
AgvCommand.AgvDownloadMapResult downloadResult = edgeAgvService.robotConfigDownloadMap(
sourceEdgeVO, map.getMapSourceName()
);
String mapContent = edgeAgvService.downloadMap(sourceEdgeVO, map.getMapSourceName());
if (downloadResult.getRetCode() != 0) {
throw new GlobalException("从来源机器人下载地图失败: " + downloadResult.getErrMsg());
}
String mapContent = downloadResult.getMapContent();
if (StrUtil.isBlank(mapContent)) {
throw new GlobalException("下载的地图内容为空");
}
@ -280,13 +253,7 @@ public class InspectionRobotServiceImpl extends ServiceImpl<InspectionRobotMappe
targetEdgeVO.setTerminalId(targetRobot.getTerminalId());
targetEdgeVO.setDeviceId(targetRobot.getRobotCode());
AgvCommand.AgvUploadMapResult uploadResult = edgeAgvService.robotConfigUploadMap(
targetEdgeVO, mapContent
);
if (uploadResult.getRetCode() != 0) {
throw new GlobalException("上传地图到目标机器人失败: " + uploadResult.getErrMsg());
}
edgeAgvService.uploadMap(targetEdgeVO, map.getMapSourceName(), mapContent);
return "上传成功";
}
@ -310,16 +277,19 @@ public class InspectionRobotServiceImpl extends ServiceImpl<InspectionRobotMappe
edgeCommonVO.setDeviceId(robot.getRobotCode());
try {
// 获取电池状态
cmvr.api.AgvCommand.AgvBatteryStatus batteryStatus = edgeAgvService.getBatteryStatus(edgeCommonVO);
robot.setBatteryLevel((int)(batteryStatus.getBatteryLevel() * 100));
// 获取运行时状态包含电池位置等信息
Agv.AgvRuntimeState runtimeState = edgeAgvService.getRuntimeState(edgeCommonVO);
// 获取位置信息
cmvr.api.AgvCommand.AgvRobotLocation location = edgeAgvService.getRobotLocation(edgeCommonVO);
robot.setCurrentPosition(location.getX() + "," + location.getY() + "," + location.getAngle());
// 设置电池电量
if (runtimeState.hasBattery()) {
robot.setBatteryLevel((int)(runtimeState.getBattery().getPercentage() * 100));
}
// 获取状态信息
cmvr.api.AgvCommand.AgvStatusInfo statusInfo = edgeAgvService.getStatusInfo(edgeCommonVO);
// 设置位置信息
if (runtimeState.hasPose()) {
Agv.AgvPose2d pose = runtimeState.getPose();
robot.setCurrentPosition(pose.getX() + "," + pose.getY() + "," + pose.getTheta());
}
robot.setUpdateTime(DateUtils.getNowDate());
inspectionRobotMapper.updateInspectionRobot(robot);
@ -336,10 +306,10 @@ public class InspectionRobotServiceImpl extends ServiceImpl<InspectionRobotMappe
* 获取机器人实时位置
*
* @param robotId 机器人ID
* @return 机器人位置信息包含XY坐标角度当前站点等
* @return 机器人位置信息
*/
@Override
public AgvCommand.AgvRobotLocation getRobotRealtimeLocation(String robotId)
public Agv.AgvPose2d getRobotRealtimeLocation(String robotId)
{
InspectionRobot robot = inspectionRobotMapper.selectInspectionRobotById(robotId);
if (robot == null) {
@ -350,7 +320,8 @@ public class InspectionRobotServiceImpl extends ServiceImpl<InspectionRobotMappe
edgeCommonVO.setTerminalId(robot.getTerminalId());
edgeCommonVO.setDeviceId(robot.getRobotCode());
return edgeAgvService.getRobotLocation(edgeCommonVO);
Agv.AgvRuntimeState runtimeState = edgeAgvService.getRuntimeState(edgeCommonVO);
return runtimeState.hasPose() ? runtimeState.getPose() : null;
}
/**
@ -360,12 +331,9 @@ public class InspectionRobotServiceImpl extends ServiceImpl<InspectionRobotMappe
* @param x X坐标
* @param y Y坐标
* @param theta 朝向角弧度
* @param taskId 任务ID可选
* @return 导航任务下发结果
*/
@Override
public AgvCommand.RobotGoTargetResData navigateToPosition(String robotId, Double x, Double y,
Double theta, String taskId)
public void navigateToPosition(String robotId, Double x, Double y, Double theta)
{
InspectionRobot robot = inspectionRobotMapper.selectInspectionRobotById(robotId);
if (robot == null) {
@ -376,8 +344,8 @@ public class InspectionRobotServiceImpl extends ServiceImpl<InspectionRobotMappe
throw new GlobalException("X和Y坐标不能为空");
}
// 构建自由导航点
AgvCommand.FreeGoPoint freeGoPoint = AgvCommand.FreeGoPoint.newBuilder()
// 构建目标位姿
Agv.AgvPose2d pose = Agv.AgvPose2d.newBuilder()
.setX(x)
.setY(y)
.setTheta(theta != null ? theta : 0.0)
@ -387,8 +355,53 @@ public class InspectionRobotServiceImpl extends ServiceImpl<InspectionRobotMappe
edgeCommonVO.setTerminalId(robot.getTerminalId());
edgeCommonVO.setDeviceId(robot.getRobotCode());
// 调用边缘端接口使用freego参数进行自由导航
return edgeAgvService.robotGoTargetWithFreeGo(edgeCommonVO, freeGoPoint, taskId);
// 调用边缘端接口进行导航
edgeAgvService.navigateToPose(edgeCommonVO, pose);
}
/**
* 开始建图
*
* @param robotId 机器人ID
* @param dimension 建图维度0:未指定 1:2D 2:3D 3:2D+3D
* @param mapName 地图名称可选
* @param realTime 是否实时建图
* @return 会话ID
*/
@Override
public String startMapping(String robotId, int dimension, String mapName, boolean realTime)
{
InspectionRobot robot = inspectionRobotMapper.selectInspectionRobotById(robotId);
if (robot == null) {
throw new GlobalException("机器人不存在");
}
EdgeCommonVO edgeCommonVO = new EdgeCommonVO();
edgeCommonVO.setTerminalId(robot.getTerminalId());
edgeCommonVO.setDeviceId(robot.getRobotCode());
Agv.AgvMapDimension mapDimension = Agv.AgvMapDimension.forNumber(dimension);
return edgeAgvService.startMapping(edgeCommonVO, mapDimension, mapName, realTime);
}
/**
* 停止建图
*
* @param robotId 机器人ID
*/
@Override
public void stopMapping(String robotId)
{
InspectionRobot robot = inspectionRobotMapper.selectInspectionRobotById(robotId);
if (robot == null) {
throw new GlobalException("机器人不存在");
}
EdgeCommonVO edgeCommonVO = new EdgeCommonVO();
edgeCommonVO.setTerminalId(robot.getTerminalId());
edgeCommonVO.setDeviceId(robot.getRobotCode());
edgeAgvService.stopMapping(edgeCommonVO);
}
}

View File

@ -1,6 +1,6 @@
package com.cmvr.test.flow.runtime.operator.edge;
import cmvr.api.AgvCommand;
import cmvr.msgs.Agv;
import com.alibaba.fastjson2.JSONObject;
import com.cmvr.common.exception.GlobalException;
import com.cmvr.edge.client.model.EdgeCommonVO;
@ -17,7 +17,7 @@ import org.springframework.stereotype.Service;
* 处理AGV相关的设备行为如移动到指定位置
*
* @author cmvr-iot
* @since 2026-06-29
* @since 2026-07-02
*/
@Slf4j
@Service
@ -52,15 +52,14 @@ public class EdgeAgvOperateService implements EdgeOperateService {
Double x = inputParams.getDouble("x");
Double y = inputParams.getDouble("y");
Double theta = inputParams.getDouble("theta");
String taskId = inputParams.getString("taskId");
// 参数验证
if (x == null || y == null) {
throw new GlobalException("X和Y坐标不能为空");
}
// 构建自由导航点
AgvCommand.FreeGoPoint freeGoPoint = AgvCommand.FreeGoPoint.newBuilder()
// 构建目标位姿
Agv.AgvPose2d pose = Agv.AgvPose2d.newBuilder()
.setX(x)
.setY(y)
.setTheta(theta != null ? theta : 0.0)
@ -72,10 +71,9 @@ public class EdgeAgvOperateService implements EdgeOperateService {
edgeCommonVO.setDeviceId(deviceId);
// 直接调用边缘端AGV服务执行导航
AgvCommand.RobotGoTargetResData result =
edgeAgvService.robotGoTargetWithFreeGo(edgeCommonVO, freeGoPoint, taskId);
edgeAgvService.navigateToPose(edgeCommonVO, pose);
log.info("AGV移动任务下发成功,返回码: {}", result.getRetCode());
log.info("AGV移动任务下发成功");
break;
}

View File

@ -1,5 +1,6 @@
package com.cmvr.test.service;
import cmvr.msgs.Agv;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSONObject;
@ -14,7 +15,6 @@ import com.cmvr.edge.client.service.EdgeMicrophoneService;
import com.cmvr.edge.client.service.EdgeSpeakerService;
import com.cmvr.edge.client.service.EdgeAgvService;
import com.cmvr.llm.service.LLMAiAgentPlatformService;
import com.cmvr.llm.service.LLMAiTtsService;
import com.cmvr.test.enums.ActionEnum;
import com.cmvr.test.model.vo.FlowActionRequestVO;
import lombok.RequiredArgsConstructor;
@ -118,29 +118,26 @@ public class FlowActionExecutorService {
Double x = payload.getDouble("x");
Double y = payload.getDouble("y");
Double theta = payload.getDouble("theta");
String taskId = payload.getString("taskId");
// 参数验证
if (x == null || y == null) {
throw new GlobalException("X和Y坐标不能为空");
}
// 构建自由导航点
cmvr.api.AgvCommand.FreeGoPoint freeGoPoint = cmvr.api.AgvCommand.FreeGoPoint.newBuilder()
// 构建目标位姿
Agv.AgvPose2d pose = Agv.AgvPose2d.newBuilder()
.setX(x)
.setY(y)
.setTheta(theta != null ? theta : 0.0)
.build();
// 直接调用边缘端AGV服务
cmvr.api.AgvCommand.RobotGoTargetResData result =
edgeAgvService.robotGoTargetWithFreeGo(edgeCommonVO, freeGoPoint, taskId);
edgeAgvService.navigateToPose(edgeCommonVO, pose);
log.info("AGV移动任务下发成功,返回码: {}", result.getRetCode());
log.info("AGV移动任务下发成功");
// 返回结果字符串
return String.format("AGV移动任务下发成功, retCode=%d, errMsg=%s",
result.getRetCode(), result.getErrMsg());
return "AGV移动任务下发成功";
}
default:

33
cmvr-iot-tts/pom.xml Normal file
View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.cmvr</groupId>
<artifactId>cmvr-iot</artifactId>
<version>3.8.9</version>
</parent>
<artifactId>cmvr-iot-tts</artifactId>
<description>
智能座舱语音语料采集模块
</description>
<dependencies>
<!-- 通用工具-->
<dependency>
<groupId>com.cmvr</groupId>
<artifactId>cmvr-iot-common</artifactId>
</dependency>
<!-- 系统模块-->
<dependency>
<groupId>com.cmvr</groupId>
<artifactId>cmvr-iot-system</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,47 @@
package com.cmvr.tts.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.cmvr.common.core.domain.BaseEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 语料分类对象 tts_corpus_category
*
* @author cmvr
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("tts_corpus_category")
@ApiModel("语料分类请求对象")
public class CorpusCategory extends BaseEntity {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@ApiModelProperty("主键ID")
@TableId(value = "id")
private String id;
/**
* 父分类ID
*/
@ApiModelProperty("父分类ID")
private String parentId;
/**
* 分类名称
*/
@ApiModelProperty("分类名称")
private String categoryName;
/**
* 排序
*/
@ApiModelProperty("排序")
private Integer sortOrder;
}

View File

@ -0,0 +1,109 @@
package com.cmvr.tts.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
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.math.BigDecimal;
/**
* 车载语料数据对象 tts_corpus_info
*
* @author cmvr
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("tts_corpus_info")
@ApiModel("车载语料数据请求对象")
public class CorpusInfo extends BaseEntity {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@ApiModelProperty("主键ID")
@TableId(value = "id")
private String id;
/**
* 文本内容
*/
@ApiModelProperty("文本内容")
private String text;
/**
* 拼音
*/
@ApiModelProperty("拼音")
private String pinyin;
/**
* 所属分类ID
*/
@ApiModelProperty("所属分类ID")
private String categoryId;
/**
* 语种
*/
@ApiModelProperty("语种")
private String language;
/**
* 方言
*/
@ApiModelProperty("方言")
private String dialect;
/**
* 情绪
*/
@ApiModelProperty("情绪")
private String emotion;
/**
* 音色
*/
@ApiModelProperty("音色")
private String voice;
/**
* 说话人
*/
@ApiModelProperty("说话人")
private String speaker;
/**
* 适用场景
*/
@ApiModelProperty("适用场景")
private String scene;
/**
* 音频文件路径
*/
@ApiModelProperty("音频文件路径")
private String audioFilePath;
/**
* 音频时长()
*/
@ApiModelProperty("音频时长(秒)")
private BigDecimal audioDuration;
/**
* 文件大小(字节)
*/
@ApiModelProperty("文件大小(字节)")
private Long fileSize;
/**
* ID列表(用于批量查询)
*/
@ApiModelProperty("ID列表")
private String[] ids;
}

View File

@ -0,0 +1,133 @@
package com.cmvr.tts.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
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.math.BigDecimal;
/**
* TTS合成任务对象 tts_synthesize_task
*
* @author cmvr
*/
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("tts_synthesize_task")
@ApiModel("TTS合成任务请求对象")
public class TtsSynthesizeTask extends BaseEntity {
private static final long serialVersionUID = 1L;
/**
* 主键ID
*/
@ApiModelProperty("主键ID")
@TableId(value = "id")
private String id;
/**
* 任务编码
*/
@ApiModelProperty("任务编码")
private String taskCode;
/**
* 合成文本
*/
@ApiModelProperty("合成文本")
private String text;
/**
* 语种
*/
@ApiModelProperty("语种")
private String language;
/**
* 方言
*/
@ApiModelProperty("方言")
private String dialect;
/**
* 音色
*/
@ApiModelProperty("音色")
private String voice;
/**
* 情绪
*/
@ApiModelProperty("情绪")
private String emotion;
/**
* 语速
*/
@ApiModelProperty("语速")
private BigDecimal speed;
/**
* 音量
*/
@ApiModelProperty("音量")
private BigDecimal volume;
/**
* 音高
*/
@ApiModelProperty("音高")
private BigDecimal pitch;
/**
* 采样率
*/
@ApiModelProperty("采样率")
private String sampleRate;
/**
* 输出格式
*/
@ApiModelProperty("输出格式")
private String outputFormat;
/**
* 说话风格模板名称
*/
@ApiModelProperty("说话风格模板名称")
private String styleTemplate;
/**
* 任务状态(0待处理 1合成中 2成功已完成 3失败)
*/
@ApiModelProperty("任务状态(0待处理 1合成中 2成功已完成 3失败)")
private String status;
/**
* 音频文件存储路径
*/
@ApiModelProperty("音频文件存储路径")
private String audioFilePath;
/**
* 音频时长()
*/
@ApiModelProperty("音频时长(秒)")
private BigDecimal audioDuration;
/**
* 文件大小(字节)
*/
@ApiModelProperty("文件大小(字节)")
private Long fileSize;
/**
* 失败原因
*/
@ApiModelProperty("失败原因")
private String errorMsg;
}

View File

@ -0,0 +1,46 @@
package com.cmvr.tts.domain.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
/**
* 语料分类树形VO对象
*
* @author cmvr
*/
@Data
@ApiModel("语料分类树形视图对象")
public class CorpusCategoryTreeVo {
/**
* 主键ID
*/
@ApiModelProperty("主键ID")
private String id;
/**
* 父分类ID
*/
@ApiModelProperty("父分类ID")
private String parentId;
/**
* 分类名称
*/
@ApiModelProperty("分类名称")
private String categoryName;
/**
* 排序
*/
@ApiModelProperty("排序")
private Integer sortOrder;
/**
* 子分类列表
*/
@ApiModelProperty("子分类列表")
private List<CorpusCategoryTreeVo> children;
}

View File

@ -0,0 +1,160 @@
package com.cmvr.tts.domain.vo;
import com.cmvr.common.annotation.Excel;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
/**
* 车载语料数据VO对象(包含关联信息)
*
* @author cmvr
*/
@Data
@ApiModel("车载语料数据视图对象")
public class CorpusInfoVo {
/**
* 主键ID
*/
@ApiModelProperty("主键ID")
private String id;
/**
* 文本内容
*/
@Excel(name = "文本内容")
@ApiModelProperty("文本内容")
private String text;
/**
* 拼音
*/
@ApiModelProperty("拼音")
private String pinyin;
/**
* 所属分类ID
*/
@ApiModelProperty("所属分类ID")
private String categoryId;
/**
* 分类名称
*/
@Excel(name = "分类名称")
@ApiModelProperty("分类名称")
private String categoryName;
/**
* 语种
*/
@ApiModelProperty("语种")
private String language;
/**
* 语种名称
*/
@Excel(name = "语种")
@ApiModelProperty("语种名称")
private String languageName;
/**
* 方言
*/
@ApiModelProperty("方言")
private String dialect;
/**
* 情绪
*/
@ApiModelProperty("情绪")
private String emotion;
/**
* 情绪名称
*/
@Excel(name = "情绪")
@ApiModelProperty("情绪名称")
private String emotionName;
/**
* 音色
*/
@ApiModelProperty("音色")
private String voice;
/**
* 音色名称
*/
@Excel(name = "音色")
@ApiModelProperty("音色名称")
private String voiceName;
/**
* 说话人
*/
@ApiModelProperty("说话人")
private String speaker;
/**
* 适用场景
*/
@ApiModelProperty("适用场景")
private String scene;
/**
* 音频文件路径
*/
@ApiModelProperty("音频文件路径")
private String audioFilePath;
/**
* 音频时长()
*/
@Excel(name = "音频时长")
@ApiModelProperty("音频时长(秒)")
private BigDecimal audioDuration;
/**
* 文件大小(字节)
*/
@Excel(name = "文件大小")
@ApiModelProperty("文件大小(字节)")
private Long fileSize;
/**
* 创建者
*/
@ApiModelProperty("创建者")
private String createBy;
/**
* 创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@ApiModelProperty("创建时间")
private Date createTime;
/**
* 更新者
*/
@ApiModelProperty("更新者")
private String updateBy;
/**
* 更新时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@ApiModelProperty("更新时间")
private Date updateTime;
/**
* 备注
*/
@ApiModelProperty("备注")
private String remark;
}

View File

@ -0,0 +1,33 @@
package com.cmvr.tts.domain.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* LLM查询请求对象
*
* @author cmvr
*/
@Data
@ApiModel("LLM查询请求对象")
public class LlmQueryVo {
/**
* 查询文本
*/
@ApiModelProperty("查询文本")
private String text;
/**
* 语种
*/
@ApiModelProperty("语种")
private String language;
/**
* 数量
*/
@ApiModelProperty("数量")
private Integer count;
}

View File

@ -0,0 +1,191 @@
package com.cmvr.tts.domain.vo;
import com.cmvr.common.annotation.Excel;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
import java.util.Date;
/**
* TTS合成任务VO对象(包含关联信息)
*
* @author cmvr
*/
@Data
@ApiModel("TTS合成任务视图对象")
public class TtsSynthesizeTaskVo {
/**
* 主键ID
*/
@ApiModelProperty("主键ID")
private String id;
/**
* 任务编码
*/
@Excel(name = "任务编码")
@ApiModelProperty("任务编码")
private String taskCode;
/**
* 合成文本
*/
@Excel(name = "合成文本")
@ApiModelProperty("合成文本")
private String text;
/**
* 语种
*/
@ApiModelProperty("语种")
private String language;
/**
* 语种名称
*/
@Excel(name = "语种")
@ApiModelProperty("语种名称")
private String languageName;
/**
* 方言
*/
@ApiModelProperty("方言")
private String dialect;
/**
* 音色
*/
@ApiModelProperty("音色")
private String voice;
/**
* 音色名称
*/
@Excel(name = "音色")
@ApiModelProperty("音色名称")
private String voiceName;
/**
* 情绪
*/
@ApiModelProperty("情绪")
private String emotion;
/**
* 情绪名称
*/
@Excel(name = "情绪")
@ApiModelProperty("情绪名称")
private String emotionName;
/**
* 语速
*/
@ApiModelProperty("语速")
private BigDecimal speed;
/**
* 音量
*/
@ApiModelProperty("音量")
private BigDecimal volume;
/**
* 音高
*/
@ApiModelProperty("音高")
private BigDecimal pitch;
/**
* 采样率
*/
@ApiModelProperty("采样率")
private String sampleRate;
/**
* 输出格式
*/
@ApiModelProperty("输出格式")
private String outputFormat;
/**
* 说话风格模板名称
*/
@ApiModelProperty("说话风格模板名称")
private String styleTemplate;
/**
* 任务状态(0待处理 1合成中 2成功已完成 3失败)
*/
@ApiModelProperty("任务状态(0待处理 1合成中 2成功已完成 3失败)")
private String status;
/**
* 任务状态名称
*/
@Excel(name = "任务状态")
@ApiModelProperty("任务状态名称")
private String statusName;
/**
* 音频文件存储路径
*/
@ApiModelProperty("音频文件存储路径")
private String audioFilePath;
/**
* 音频时长()
*/
@Excel(name = "音频时长")
@ApiModelProperty("音频时长(秒)")
private BigDecimal audioDuration;
/**
* 文件大小(字节)
*/
@Excel(name = "文件大小")
@ApiModelProperty("文件大小(字节)")
private Long fileSize;
/**
* 失败原因
*/
@ApiModelProperty("失败原因")
private String errorMsg;
/**
* 创建者
*/
@ApiModelProperty("创建者")
private String createBy;
/**
* 创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@ApiModelProperty("创建时间")
private Date createTime;
/**
* 更新者
*/
@ApiModelProperty("更新者")
private String updateBy;
/**
* 更新时间
*/
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@ApiModelProperty("更新时间")
private Date updateTime;
/**
* 备注
*/
@ApiModelProperty("备注")
private String remark;
}

View File

@ -0,0 +1,45 @@
package com.cmvr.tts.mapper;
import com.cmvr.tts.domain.CorpusCategory;
import java.util.List;
/**
* 语料分类Mapper接口
*
* @author cmvr
*/
public interface CorpusCategoryMapper {
/**
* 查询语料分类列表
*
* @param category 语料分类
* @return 语料分类集合
*/
List<CorpusCategory> selectList(CorpusCategory category);
/**
* 新增语料分类
*
* @param category 语料分类
* @return 结果
*/
int insert(CorpusCategory category);
/**
* 修改语料分类
*
* @param category 语料分类
* @return 结果
*/
int updateById(CorpusCategory category);
/**
* 删除语料分类
*
* @param id 语料分类主键
* @return 结果
*/
int deleteById(String id);
}

View File

@ -0,0 +1,113 @@
package com.cmvr.tts.mapper;
import com.cmvr.tts.domain.CorpusInfo;
import com.cmvr.tts.domain.vo.CorpusInfoVo;
import java.util.List;
import java.util.Map;
/**
* 车载语料数据Mapper接口
*
* @author cmvr
*/
public interface CorpusInfoMapper {
/**
* 查询车载语料数据
*
* @param id 车载语料数据主键
* @return 车载语料数据
*/
CorpusInfo selectCorpusById(String id);
/**
* 查询车载语料数据列表
*
* @param corpus 车载语料数据
* @return 车载语料数据集合
*/
List<CorpusInfo> selectCorpusList(CorpusInfo corpus);
/**
* 查询车载语料数据视图列表包含关联信息
*
* @param corpus 车载语料数据
* @return 车载语料数据视图集合
*/
List<CorpusInfoVo> selectCorpusVoList(CorpusInfo corpus);
/**
* 新增车载语料数据
*
* @param corpus 车载语料数据
* @return 结果
*/
int insertCorpus(CorpusInfo corpus);
/**
* 修改车载语料数据
*
* @param corpus 车载语料数据
* @return 结果
*/
int updateCorpus(CorpusInfo corpus);
/**
* 删除车载语料数据
*
* @param id 车载语料数据主键
* @return 结果
*/
int deleteCorpusById(String id);
/**
* 批量删除车载语料数据
*
* @param ids 需要删除的数据主键集合
* @return 结果
*/
int deleteCorpusByIds(String[] ids);
/**
* 统计看板指标数据
*
* @return 统计数据
*/
Map<String, Object> selectDashboardStats();
/**
* 统计昨日数据用于环比计算
*
* @return 昨日统计数据
*/
Map<String, Object> selectYesterdayStats();
/**
* 按语种分组统计语料数量
*
* @return 语种统计数据
*/
List<Map<String, Object>> selectStatsByLanguage();
/**
* 按方言分组统计语料数量
*
* @return 方言统计数据
*/
List<Map<String, Object>> selectStatsByDialect();
/**
* 按情绪分组统计语料数量
*
* @return 情绪统计数据
*/
List<Map<String, Object>> selectStatsByEmotion();
/**
* 按功能分类统计语料数量
*
* @return 分类统计数据
*/
List<Map<String, Object>> selectStatsByCategory();
}

View File

@ -0,0 +1,113 @@
package com.cmvr.tts.mapper;
import com.cmvr.tts.domain.TtsSynthesizeTask;
import com.cmvr.tts.domain.vo.TtsSynthesizeTaskVo;
import java.util.List;
import java.util.Map;
/**
* TTS合成任务Mapper接口
*
* @author cmvr
*/
public interface TtsSynthesizeTaskMapper {
/**
* 查询TTS合成任务
*
* @param id TTS合成任务主键
* @return TTS合成任务
*/
TtsSynthesizeTask selectTtsTaskById(String id);
/**
* 查询TTS合成任务列表
*
* @param task TTS合成任务
* @return TTS合成任务集合
*/
List<TtsSynthesizeTask> selectTtsTaskList(TtsSynthesizeTask task);
/**
* 查询TTS合成任务视图列表包含关联信息
*
* @param task TTS合成任务
* @return TTS合成任务视图集合
*/
List<TtsSynthesizeTaskVo> selectTtsTaskVoList(TtsSynthesizeTask task);
/**
* 新增TTS合成任务
*
* @param task TTS合成任务
* @return 结果
*/
int insertTtsTask(TtsSynthesizeTask task);
/**
* 修改TTS合成任务
*
* @param task TTS合成任务
* @return 结果
*/
int updateTtsTask(TtsSynthesizeTask task);
/**
* 删除TTS合成任务
*
* @param id TTS合成任务主键
* @return 结果
*/
int deleteTtsTaskById(String id);
/**
* 批量删除TTS合成任务
*
* @param ids 需要删除的数据主键集合
* @return 结果
*/
int deleteTtsTaskByIds(String[] ids);
/**
* 统计看板指标数据
*
* @return 统计数据
*/
Map<String, Object> selectDashboardStats();
/**
* 统计昨日数据用于环比计算
*
* @return 昨日统计数据
*/
Map<String, Object> selectYesterdayStats();
/**
* 按语种分组统计任务总量
*
* @return 语种统计数据
*/
List<Map<String, Object>> selectStatsByLanguage();
/**
* 按音色分组统计任务总量
*
* @return 音色统计数据
*/
List<Map<String, Object>> selectStatsByVoice();
/**
* 按情绪分组统计任务总量
*
* @return 情绪统计数据
*/
List<Map<String, Object>> selectStatsByEmotion();
/**
* 近7日每日合成任务数量趋势
*
* @return 每日任务数量
*/
List<Map<String, Object>> selectLast7DaysTrend();
}

View File

@ -0,0 +1,109 @@
package com.cmvr.tts.service;
import com.cmvr.common.core.page.TableDataInfo;
import com.cmvr.tts.domain.CorpusInfo;
import com.cmvr.tts.domain.vo.CorpusCategoryTreeVo;
import com.cmvr.tts.domain.vo.CorpusInfoVo;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Map;
/**
* 车载语料数据Service接口
*
* @author cmvr
*/
public interface ICorpusInfoService {
/**
* 查询语料列表
*/
TableDataInfo selectCorpusList(CorpusInfo corpus);
/**
* 新增语料
*/
int insertCorpus(CorpusInfo corpus);
/**
* 修改语料
*/
int updateCorpus(CorpusInfo corpus);
/**
* 删除语料
*/
int deleteCorpusByIds(String[] ids);
/**
* 查询语料详情
*/
CorpusInfoVo selectCorpusById(String id);
/**
* 获取音频流
*/
void getAudioStream(String corpusId, HttpServletResponse response);
/**
* 上传语料音频
*/
String uploadCorpusAudio(String corpusId, MultipartFile file);
/**
* 批量导入语料
*/
Map<String, Object> importCorpus(MultipartFile file);
/**
* 导出语料
*/
byte[] exportCorpusList(CorpusInfo corpus);
/**
* 查询看板统计
*/
Map<String, Object> getDashboardStats();
/**
* 按语种统计
*/
List<Map<String, Object>> getStatsByLanguage();
/**
* 按方言统计
*/
List<Map<String, Object>> getStatsByDialect();
/**
* 按情绪统计
*/
List<Map<String, Object>> getStatsByEmotion();
/**
* 按分类统计
*/
List<Map<String, Object>> getStatsByCategory();
/**
* 查询分类树
*/
List<CorpusCategoryTreeVo> getCategoryTree();
/**
* 新增分类
*/
int insertCategory(com.cmvr.tts.domain.CorpusCategory category);
/**
* 修改分类
*/
int updateCategory(com.cmvr.tts.domain.CorpusCategory category);
/**
* 删除分类
*/
int deleteCategoryById(String id);
}

View File

@ -0,0 +1,133 @@
package com.cmvr.tts.service;
import com.cmvr.common.core.page.TableDataInfo;
import com.cmvr.tts.domain.TtsSynthesizeTask;
import com.cmvr.tts.domain.vo.TtsSynthesizeTaskVo;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
import java.util.Map;
/**
* TTS合成任务Service接口
*
* @author cmvr
*/
public interface ITtsSynthesizeTaskService {
/**
* 查询TTS合成任务列表
*
* @param task TTS合成任务
* @return TTS合成任务集合
*/
TableDataInfo selectTtsTaskList(TtsSynthesizeTask task);
/**
* 查询TTS合成任务
*
* @param id TTS合成任务主键
* @return TTS合成任务
*/
TtsSynthesizeTaskVo selectTtsTaskById(String id);
/**
* 新增TTS合成任务
*
* @param task TTS合成任务
* @return 结果
*/
TtsSynthesizeTask insertTtsTask(TtsSynthesizeTask task);
/**
* 批量新增TTS合成任务
*
* @param tasks TTS合成任务列表
* @return 结果
*/
int batchInsertTtsTask(List<TtsSynthesizeTask> tasks);
/**
* 修改TTS合成任务
*
* @param task TTS合成任务
* @return 结果
*/
int updateTtsTask(TtsSynthesizeTask task);
/**
* 删除TTS合成任务
*
* @param ids 需要删除的TTS合成任务主键集合
* @return 结果
*/
int deleteTtsTaskByIds(String[] ids);
/**
* 重生成TTS任务
*
* @param taskId 任务ID
* @return 结果
*/
int regenerateTask(String taskId);
/**
* 获取音频文件流
*
* @param taskId 任务ID
* @param response 响应
*/
void getAudioStream(String taskId, HttpServletResponse response);
/**
* 导出TTS合成任务列表
*
* @param task TTS合成任务
* @return 结果
*/
byte[] exportTtsTaskList(TtsSynthesizeTask task);
/**
* 查询看板统计数据
*
* @return 统计数据
*/
Map<String, Object> getDashboardStats();
/**
* 按语种统计
*
* @return 统计数据
*/
List<Map<String, Object>> getStatsByLanguage();
/**
* 按音色统计
*
* @return 统计数据
*/
List<Map<String, Object>> getStatsByVoice();
/**
* 按情绪统计
*
* @return 统计数据
*/
List<Map<String, Object>> getStatsByEmotion();
/**
* 近7日趋势
*
* @return 趋势数据
*/
List<Map<String, Object>> getLast7DaysTrend();
/**
* 保存任务到语料库
*
* @param taskId 任务ID
* @param categoryId 分类ID
* @return 结果
*/
int saveToCorpus(String taskId, String categoryId);
}

View File

@ -0,0 +1,335 @@
package com.cmvr.tts.service.impl;
import cn.hutool.core.util.IdUtil;
import com.cmvr.common.core.minio.MinioService;
import com.cmvr.common.core.page.TableDataInfo;
import com.cmvr.common.utils.DateUtils;
import com.cmvr.common.utils.SecurityUtils;
import com.cmvr.common.utils.StringUtils;
import com.cmvr.tts.domain.CorpusCategory;
import com.cmvr.tts.domain.CorpusInfo;
import com.cmvr.tts.domain.vo.CorpusCategoryTreeVo;
import com.cmvr.tts.domain.vo.CorpusInfoVo;
import com.cmvr.tts.mapper.CorpusCategoryMapper;
import com.cmvr.tts.mapper.CorpusInfoMapper;
import com.cmvr.tts.service.ICorpusInfoService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.util.*;
import java.util.stream.Collectors;
/**
* 车载语料数据Service业务层处理
*
* @author cmvr
*/
@Slf4j
@Service
public class CorpusInfoServiceImpl implements ICorpusInfoService {
@Autowired
private CorpusInfoMapper corpusInfoMapper;
@Autowired
private CorpusCategoryMapper corpusCategoryMapper;
@Autowired
private MinioService minioService;
/**
* 查询语料列表
*/
@Override
public TableDataInfo selectCorpusList(CorpusInfo corpus) {
List<CorpusInfoVo> list = corpusInfoMapper.selectCorpusVoList(corpus);
return new TableDataInfo(list, list.size());
}
/**
* 新增语料
*/
@Override
public int insertCorpus(CorpusInfo corpus) {
// 生成UUID作为主键
if (corpus.getId() == null || corpus.getId().isEmpty()) {
corpus.setId(IdUtil.fastSimpleUUID());
}
corpus.setCreateTime(DateUtils.getNowDate());
corpus.setCreateBy(SecurityUtils.getUsername());
return corpusInfoMapper.insertCorpus(corpus);
}
/**
* 修改语料
*/
@Override
public int updateCorpus(CorpusInfo corpus) {
corpus.setUpdateTime(DateUtils.getNowDate());
corpus.setUpdateBy(SecurityUtils.getUsername());
return corpusInfoMapper.updateCorpus(corpus);
}
/**
* 删除语料
*/
@Override
@Transactional(rollbackFor = Exception.class)
public int deleteCorpusByIds(String[] ids) {
for (String id : ids) {
CorpusInfo corpus = corpusInfoMapper.selectCorpusById(id);
if (corpus != null && StringUtils.isNotEmpty(corpus.getAudioFilePath())) {
try {
minioService.deleteFile("cmvr-iot", corpus.getAudioFilePath());
} catch (Exception e) {
log.error("删除音频文件失败: {}", corpus.getAudioFilePath(), e);
}
}
corpusInfoMapper.deleteCorpusById(id);
}
return ids.length;
}
/**
* 查询语料详情
*/
@Override
public CorpusInfoVo selectCorpusById(String id) {
CorpusInfo corpus = corpusInfoMapper.selectCorpusById(id);
if (corpus == null) {
return null;
}
CorpusInfoVo vo = new CorpusInfoVo();
BeanUtils.copyProperties(corpus, vo);
// TODO: 补充关联查询
return vo;
}
/**
* 获取音频流
*/
@Override
public void getAudioStream(String corpusId, HttpServletResponse response) {
CorpusInfo corpus = corpusInfoMapper.selectCorpusById(corpusId);
if (corpus == null || StringUtils.isEmpty(corpus.getAudioFilePath())) {
throw new RuntimeException("音频文件不存在");
}
try {
InputStream inputStream = minioService.getObject("cmvr-iot", corpus.getAudioFilePath());
response.setContentType("audio/wav");
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode("corpus_" + corpusId + ".wav", "UTF-8"));
OutputStream outputStream = response.getOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, len);
}
outputStream.flush();
inputStream.close();
} catch (Exception e) {
log.error("获取音频流失败", e);
throw new RuntimeException("获取音频流失败");
}
}
/**
* 上传语料音频
*/
@Override
@Transactional(rollbackFor = Exception.class)
public String uploadCorpusAudio(String corpusId, MultipartFile file) {
CorpusInfo corpus = corpusInfoMapper.selectCorpusById(corpusId);
if (corpus == null) {
throw new RuntimeException("语料不存在");
}
try {
String objectName = "corpus/" + DateUtils.datePath() + "/" + corpusId + "_" + System.currentTimeMillis() + ".wav";
minioService.upload("cmvr-iot", "corpus/" + DateUtils.datePath(), file);
corpus.setAudioFilePath(objectName);
corpus.setFileSize(file.getSize());
corpusInfoMapper.updateCorpus(corpus);
return objectName;
} catch (Exception e) {
log.error("上传音频失败", e);
throw new RuntimeException("上传音频失败");
}
}
/**
* 批量导入语料
*/
@Override
public Map<String, Object> importCorpus(MultipartFile file) {
Map<String, Object> result = new HashMap<>();
result.put("successCount", 0);
result.put("failCount", 0);
result.put("failDetails", new ArrayList<>());
// TODO: 实现Excel解析和批量导入逻辑
return result;
}
/**
* 导出语料
*/
@Override
public byte[] exportCorpusList(CorpusInfo corpus) {
List<CorpusInfoVo> list = corpusInfoMapper.selectCorpusVoList(corpus);
// TODO: 使用Excel工具类导出
return new byte[0];
}
/**
* 查询看板统计
*/
@Override
public Map<String, Object> getDashboardStats() {
Map<String, Object> todayStats = corpusInfoMapper.selectDashboardStats();
Map<String, Object> yesterdayStats = corpusInfoMapper.selectYesterdayStats();
// 计算环比
if (yesterdayStats != null && !yesterdayStats.isEmpty()) {
Long todayTotal = ((Number) todayStats.get("totalCorpus")).longValue();
Long yesterdayTotal = ((Number) yesterdayStats.get("totalCorpus")).longValue();
todayStats.put("totalCorpusGrowth", calculatePercent(todayTotal, yesterdayTotal));
} else {
todayStats.put("totalCorpusGrowth", "0%");
}
return todayStats;
}
/**
* 按语种统计
*/
@Override
public List<Map<String, Object>> getStatsByLanguage() {
return corpusInfoMapper.selectStatsByLanguage();
}
/**
* 按方言统计
*/
@Override
public List<Map<String, Object>> getStatsByDialect() {
return corpusInfoMapper.selectStatsByDialect();
}
/**
* 按情绪统计
*/
@Override
public List<Map<String, Object>> getStatsByEmotion() {
return corpusInfoMapper.selectStatsByEmotion();
}
/**
* 按分类统计
*/
@Override
public List<Map<String, Object>> getStatsByCategory() {
return corpusInfoMapper.selectStatsByCategory();
}
/**
* 查询分类树
*/
@Override
public List<CorpusCategoryTreeVo> getCategoryTree() {
CorpusCategory queryParam = new CorpusCategory();
List<CorpusCategory> allCategories = corpusCategoryMapper.selectList(queryParam);
return buildCategoryTree(allCategories, "0");
}
/**
* 新增分类
*/
@Override
public int insertCategory(CorpusCategory category) {
category.setId(IdUtil.fastSimpleUUID());
category.setCreateTime(DateUtils.getNowDate());
return corpusCategoryMapper.insert(category);
}
/**
* 修改分类
*/
@Override
public int updateCategory(CorpusCategory category) {
category.setUpdateTime(DateUtils.getNowDate());
return corpusCategoryMapper.updateById(category);
}
/**
* 删除分类
*/
@Override
@Transactional(rollbackFor = Exception.class)
public int deleteCategoryById(String id) {
// 检查是否有子分类
CorpusCategory queryParam = new CorpusCategory();
queryParam.setParentId(id);
List<CorpusCategory> children = corpusCategoryMapper.selectList(queryParam);
if (children != null && !children.isEmpty()) {
throw new RuntimeException("存在子分类,无法删除");
}
// 检查是否有关联语料
CorpusInfo corpusParam = new CorpusInfo();
corpusParam.setCategoryId(id);
List<CorpusInfo> corpusList = corpusInfoMapper.selectCorpusList(corpusParam);
if (corpusList != null && !corpusList.isEmpty()) {
throw new RuntimeException("分类下存在语料,无法删除");
}
return corpusCategoryMapper.deleteById(id);
}
/**
* 构建分类树
*/
private List<CorpusCategoryTreeVo> buildCategoryTree(List<CorpusCategory> allCategories, String parentId) {
return allCategories.stream()
.filter(category -> parentId.equals(category.getParentId()))
.map(category -> {
CorpusCategoryTreeVo treeVo = new CorpusCategoryTreeVo();
treeVo.setId(category.getId());
treeVo.setParentId(category.getParentId());
treeVo.setCategoryName(category.getCategoryName());
treeVo.setSortOrder(category.getSortOrder());
List<CorpusCategoryTreeVo> children = buildCategoryTree(allCategories, category.getId());
if (!children.isEmpty()) {
treeVo.setChildren(children);
}
return treeVo;
})
.sorted(Comparator.comparingInt(CorpusCategoryTreeVo::getSortOrder))
.collect(Collectors.toList());
}
/**
* 计算环比增长率
*/
private String calculatePercent(Long current, Long previous) {
if (previous == 0) {
return current > 0 ? "+100%" : "0%";
}
double growth = (current - previous) * 100.0 / previous;
return String.format("%+.2f%%", growth);
}
}

View File

@ -0,0 +1,392 @@
package com.cmvr.tts.service.impl;
import cn.hutool.core.util.IdUtil;
import com.cmvr.common.core.minio.MinioService;
import com.cmvr.common.core.page.TableDataInfo;
import com.cmvr.common.utils.DateUtils;
import com.cmvr.common.utils.SecurityUtils;
import com.cmvr.common.utils.StringUtils;
import com.cmvr.common.utils.http.HttpUtils;
import com.cmvr.tts.domain.CorpusInfo;
import com.cmvr.tts.domain.TtsSynthesizeTask;
import com.cmvr.tts.domain.vo.TtsSynthesizeTaskVo;
import com.cmvr.tts.mapper.CorpusInfoMapper;
import com.cmvr.tts.mapper.TtsSynthesizeTaskMapper;
import com.cmvr.tts.service.ITtsSynthesizeTaskService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.util.*;
/**
* TTS合成任务Service业务层处理
*
* @author cmvr
*/
@Slf4j
@Service
public class TtsSynthesizeTaskServiceImpl implements ITtsSynthesizeTaskService {
@Autowired
private TtsSynthesizeTaskMapper ttsTaskMapper;
@Autowired
private CorpusInfoMapper corpusInfoMapper;
@Autowired
private MinioService minioService;
@org.springframework.beans.factory.annotation.Value("${tts.external-api.url:http://192.168.1.101:8080/tts/synthesize}")
private String externalTtsUrl;
@org.springframework.beans.factory.annotation.Value("${minio.bucketName:cmvr-iot}")
private String bucketName;
/**
* 查询TTS合成任务列表
*/
@Override
public TableDataInfo selectTtsTaskList(TtsSynthesizeTask task) {
List<TtsSynthesizeTaskVo> list = ttsTaskMapper.selectTtsTaskVoList(task);
return new TableDataInfo(list, list.size());
}
/**
* 查询TTS合成任务详情
*/
@Override
public TtsSynthesizeTaskVo selectTtsTaskById(String id) {
TtsSynthesizeTask task = ttsTaskMapper.selectTtsTaskById(id);
if (task == null) {
return null;
}
// 转换为VO包含关联信息
List<TtsSynthesizeTaskVo> voList = ttsTaskMapper.selectTtsTaskVoList(task);
return voList != null && !voList.isEmpty() ? voList.get(0) : null;
}
/**
* 新增TTS合成任务
*/
@Override
@Transactional(rollbackFor = Exception.class)
public TtsSynthesizeTask insertTtsTask(TtsSynthesizeTask task) {
// 生成UUID作为主键
if (task.getId() == null || task.getId().isEmpty()) {
task.setId(IdUtil.fastSimpleUUID());
}
task.setTaskCode(IdUtil.fastSimpleUUID());
task.setStatus("0");
task.setCreateTime(DateUtils.getNowDate());
task.setCreateBy(SecurityUtils.getUsername());
int rows = ttsTaskMapper.insertTtsTask(task);
// 异步调用外部TTS接口
if (rows > 0) {
callExternalTtsApi(task);
}
return task;
}
/**
* 批量新增TTS合成任务
*/
@Override
@Transactional(rollbackFor = Exception.class)
public int batchInsertTtsTask(List<TtsSynthesizeTask> tasks) {
int count = 0;
for (TtsSynthesizeTask task : tasks) {
// 生成UUID作为主键
if (task.getId() == null || task.getId().isEmpty()) {
task.setId(IdUtil.fastSimpleUUID());
}
task.setTaskCode(IdUtil.fastSimpleUUID());
task.setStatus("0");
task.setCreateTime(DateUtils.getNowDate());
task.setCreateBy(SecurityUtils.getUsername());
ttsTaskMapper.insertTtsTask(task);
// 异步调用外部TTS接口
callExternalTtsApi(task);
count++;
}
return count;
}
/**
* 修改TTS合成任务
*/
@Override
public int updateTtsTask(TtsSynthesizeTask task) {
task.setUpdateTime(DateUtils.getNowDate());
task.setUpdateBy(SecurityUtils.getUsername());
return ttsTaskMapper.updateTtsTask(task);
}
/**
* 删除TTS合成任务
*/
@Override
@Transactional(rollbackFor = Exception.class)
public int deleteTtsTaskByIds(String[] ids) {
for (String id : ids) {
TtsSynthesizeTask task = ttsTaskMapper.selectTtsTaskById(id);
if (task != null && StringUtils.isNotEmpty(task.getAudioFilePath())) {
// 删除音频文件
try {
minioService.deleteFile(bucketName, task.getAudioFilePath());
} catch (Exception e) {
log.error("删除音频文件失败: {}", task.getAudioFilePath(), e);
}
}
ttsTaskMapper.deleteTtsTaskById(id);
}
return ids.length;
}
/**
* 重生成TTS任务
*/
@Override
@Transactional(rollbackFor = Exception.class)
public int regenerateTask(String taskId) {
TtsSynthesizeTask task = ttsTaskMapper.selectTtsTaskById(taskId);
if (task == null) {
throw new RuntimeException("任务不存在");
}
// 更新状态为待处理
task.setStatus("0");
task.setErrorMsg(null);
task.setUpdateTime(DateUtils.getNowDate());
task.setUpdateBy(SecurityUtils.getUsername());
ttsTaskMapper.updateTtsTask(task);
// 重新调用外部TTS接口
callExternalTtsApi(task);
return 1;
}
/**
* 获取音频文件流
*/
@Override
public void getAudioStream(String taskId, HttpServletResponse response) {
TtsSynthesizeTask task = ttsTaskMapper.selectTtsTaskById(taskId);
if (task == null || StringUtils.isEmpty(task.getAudioFilePath())) {
throw new RuntimeException("音频文件不存在");
}
try {
InputStream inputStream = minioService.getObject(bucketName, task.getAudioFilePath());
response.setContentType("audio/wav");
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode("audio_" + taskId + ".wav", "UTF-8"));
OutputStream outputStream = response.getOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, len);
}
outputStream.flush();
inputStream.close();
} catch (Exception e) {
log.error("获取音频流失败", e);
throw new RuntimeException("获取音频流失败");
}
}
/**
* 导出TTS合成任务列表
*/
@Override
public byte[] exportTtsTaskList(TtsSynthesizeTask task) {
List<TtsSynthesizeTaskVo> list = ttsTaskMapper.selectTtsTaskVoList(task);
// TODO: 使用Excel工具类导出
return new byte[0];
}
/**
* 查询看板统计数据
*/
@Override
public Map<String, Object> getDashboardStats() {
Map<String, Object> todayStats = ttsTaskMapper.selectDashboardStats();
Map<String, Object> yesterdayStats = ttsTaskMapper.selectYesterdayStats();
// 计算环比
calculateGrowthRate(todayStats, yesterdayStats);
return todayStats;
}
/**
* 按语种统计
*/
@Override
public List<Map<String, Object>> getStatsByLanguage() {
return ttsTaskMapper.selectStatsByLanguage();
}
/**
* 按音色统计
*/
@Override
public List<Map<String, Object>> getStatsByVoice() {
return ttsTaskMapper.selectStatsByVoice();
}
/**
* 按情绪统计
*/
@Override
public List<Map<String, Object>> getStatsByEmotion() {
return ttsTaskMapper.selectStatsByEmotion();
}
/**
* 近7日趋势
*/
@Override
public List<Map<String, Object>> getLast7DaysTrend() {
return ttsTaskMapper.selectLast7DaysTrend();
}
/**
* 调用外部TTS接口
*/
private void callExternalTtsApi(TtsSynthesizeTask task) {
try {
// 更新状态为合成中
task.setStatus("1");
ttsTaskMapper.updateTtsTask(task);
// 构建请求参数 - 只支持text, voice, speed, volume, pitch, sampleRate, format
Map<String, Object> params = new HashMap<>();
params.put("text", task.getText());
params.put("voice", task.getVoice());
params.put("speed", task.getSpeed());
params.put("volume", task.getVolume());
params.put("pitch", task.getPitch());
params.put("sampleRate", task.getSampleRate());
params.put("format", task.getOutputFormat());
// 调用外部TTS接口返回音频文件(字节数组)
byte[] audioBytes = HttpUtils.sendPostReturnBytes(externalTtsUrl, params);
if (audioBytes != null && audioBytes.length > 0) {
// 上传音频到Minio
String objectName = "tts/" + DateUtils.datePath() + "/" + task.getTaskCode() + ".wav";
ByteArrayInputStream inputStream = new ByteArrayInputStream(audioBytes);
// 使用MinioService上传
minioService.uploadStream(bucketName, objectName, inputStream, audioBytes.length, "audio/wav");
// 更新任务状态
task.setStatus("2");
task.setAudioFilePath(objectName);
task.setFileSize((long) audioBytes.length);
// 估算音频时长假设WAV格式16bit采样
Integer sampleRateInt = Integer.parseInt(task.getSampleRate());
double duration = audioBytes.length / (sampleRateInt * 2.0);
task.setAudioDuration(BigDecimal.valueOf(duration).setScale(2, BigDecimal.ROUND_HALF_UP));
ttsTaskMapper.updateTtsTask(task);
} else {
throw new RuntimeException("外部TTS接口返回空数据");
}
} catch (Exception e) {
log.error("调用外部TTS接口失败", e);
task.setStatus("3");
task.setErrorMsg(e.getMessage());
ttsTaskMapper.updateTtsTask(task);
}
}
/**
* 保存任务到语料库
*/
@Override
@Transactional(rollbackFor = Exception.class)
public int saveToCorpus(String taskId, String categoryId) {
// 查询任务
TtsSynthesizeTask task = ttsTaskMapper.selectTtsTaskById(taskId);
if (task == null) {
throw new RuntimeException("任务不存在");
}
// 检查是否有音频文件
if (StringUtils.isEmpty(task.getAudioFilePath())) {
throw new RuntimeException("任务尚未合成音频,无法保存到语料库");
}
// 创建语料记录
CorpusInfo corpus = new CorpusInfo();
corpus.setId(IdUtil.fastSimpleUUID());
corpus.setCategoryId(categoryId);
corpus.setText(task.getText());
corpus.setLanguage(task.getLanguage());
corpus.setDialect(task.getDialect());
corpus.setEmotion(task.getEmotion());
corpus.setVoice(task.getVoice());
corpus.setAudioFilePath(task.getAudioFilePath());
corpus.setAudioDuration(task.getAudioDuration());
corpus.setFileSize(task.getFileSize());
corpus.setCreateBy(SecurityUtils.getUsername());
corpus.setCreateTime(DateUtils.getNowDate());
return corpusInfoMapper.insertCorpus(corpus);
}
/**
* 计算环比增长率
*/
private void calculateGrowthRate(Map<String, Object> today, Map<String, Object> yesterday) {
if (yesterday == null || yesterday.isEmpty()) {
today.put("totalTasksGrowth", "0%");
today.put("todayTasksGrowth", "0%");
today.put("avgDurationGrowth", "0%");
return;
}
// 总任务数环比
Long todayTotal = ((Number) today.get("totalTasks")).longValue();
Long yesterdayTotal = ((Number) yesterday.get("totalTasks")).longValue();
today.put("totalTasksGrowth", calculatePercent(todayTotal, yesterdayTotal));
// 今日任务数环比
Long todayNew = ((Number) today.get("todayTasks")).longValue();
Long yesterdayNew = ((Number) yesterday.get("todayTasks")).longValue();
today.put("todayTasksGrowth", calculatePercent(todayNew, yesterdayNew));
// 平均时长环比
Double todayAvg = ((Number) today.get("avgDuration")).doubleValue();
Double yesterdayAvg = ((Number) yesterday.get("avgDuration")).doubleValue();
today.put("avgDurationGrowth", calculatePercentDouble(todayAvg, yesterdayAvg));
}
private String calculatePercent(Long current, Long previous) {
if (previous == 0) {
return current > 0 ? "+100%" : "0%";
}
double growth = (current - previous) * 100.0 / previous;
return String.format("%+.2f%%", growth);
}
private String calculatePercentDouble(Double current, Double previous) {
if (previous == 0 || previous.isNaN()) {
return "0%";
}
double growth = (current - previous) * 100.0 / previous;
return String.format("%+.2f%%", growth);
}
}

View File

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cmvr.tts.mapper.CorpusCategoryMapper">
<resultMap type="com.cmvr.tts.domain.CorpusCategory" id="CorpusCategoryResult">
<result property="id" column="id"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/>
<result property="updateTime" column="update_time"/>
<result property="remark" column="remark"/>
<result property="parentId" column="parent_id"/>
<result property="categoryName" column="category_name"/>
<result property="sortOrder" column="sort_order"/>
</resultMap>
<select id="selectList" parameterType="com.cmvr.tts.domain.CorpusCategory" resultMap="CorpusCategoryResult">
select id, parent_id, category_name, sort_order, create_by, create_time, update_by, update_time, remark
from tts_corpus_category
<where>
<if test="parentId != null and parentId != ''"> and parent_id = #{parentId}</if>
<if test="categoryName != null and categoryName != ''"> and category_name like concat('%', #{categoryName}, '%')</if>
</where>
order by sort_order asc
</select>
<insert id="insert" parameterType="com.cmvr.tts.domain.CorpusCategory">
insert into tts_corpus_category
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="id != null">id,</if>
<if test="createBy != null">create_by,</if>
<if test="createTime != null">create_time,</if>
<if test="updateBy != null">update_by,</if>
<if test="updateTime != null">update_time,</if>
<if test="remark != null">remark,</if>
<if test="parentId != null">parent_id,</if>
<if test="categoryName != null">category_name,</if>
<if test="sortOrder != null">sort_order,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">#{id},</if>
<if test="createBy != null">#{createBy},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateBy != null">#{updateBy},</if>
<if test="updateTime != null">#{updateTime},</if>
<if test="remark != null">#{remark},</if>
<if test="parentId != null">#{parentId},</if>
<if test="categoryName != null">#{categoryName},</if>
<if test="sortOrder != null">#{sortOrder},</if>
</trim>
</insert>
<update id="updateById" parameterType="com.cmvr.tts.domain.CorpusCategory">
update tts_corpus_category
<trim prefix="SET" suffixOverrides=",">
<if test="createBy != null">create_by = #{createBy},</if>
<if test="createTime != null">create_time = #{createTime},</if>
<if test="updateBy != null">update_by = #{updateBy},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
<if test="remark != null">remark = #{remark},</if>
<if test="parentId != null">parent_id = #{parentId},</if>
<if test="categoryName != null">category_name = #{categoryName},</if>
<if test="sortOrder != null">sort_order = #{sortOrder},</if>
</trim>
where id = #{id}
</update>
<delete id="deleteById" parameterType="String">
delete from tts_corpus_category where id = #{id}
</delete>
</mapper>

View File

@ -0,0 +1,248 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cmvr.tts.mapper.CorpusInfoMapper">
<resultMap type="com.cmvr.tts.domain.CorpusInfo" id="CorpusResult">
<result property="id" column="id"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/>
<result property="updateTime" column="update_time"/>
<result property="remark" column="remark"/>
<result property="text" column="text"/>
<result property="pinyin" column="pinyin"/>
<result property="categoryId" column="category_id"/>
<result property="language" column="language"/>
<result property="dialect" column="dialect"/>
<result property="emotion" column="emotion"/>
<result property="voice" column="voice"/>
<result property="speaker" column="speaker"/>
<result property="scene" column="scene"/>
<result property="audioFilePath" column="audio_file_path"/>
<result property="audioDuration" column="audio_duration"/>
<result property="fileSize" column="file_size"/>
</resultMap>
<resultMap type="com.cmvr.tts.domain.vo.CorpusInfoVo" id="CorpusVoResult">
<result property="id" column="id"/>
<result property="text" column="text"/>
<result property="pinyin" column="pinyin"/>
<result property="categoryId" column="category_id"/>
<result property="categoryName" column="category_name"/>
<result property="language" column="language"/>
<result property="languageName" column="language_name"/>
<result property="dialect" column="dialect"/>
<result property="emotion" column="emotion"/>
<result property="emotionName" column="emotion_name"/>
<result property="voice" column="voice"/>
<result property="voiceName" column="voice_name"/>
<result property="speaker" column="speaker"/>
<result property="scene" column="scene"/>
<result property="audioFilePath" column="audio_file_path"/>
<result property="audioDuration" column="audio_duration"/>
<result property="fileSize" column="file_size"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/>
<result property="updateTime" column="update_time"/>
<result property="remark" column="remark"/>
</resultMap>
<sql id="selectCorpusVo">
select c.id, c.text, c.pinyin, c.category_id, c.language, c.dialect, c.emotion,
c.voice, c.speaker, c.scene, c.audio_file_path, c.audio_duration, c.file_size,
c.create_by, c.create_time, c.update_by, c.update_time, c.remark,
cc.category_name as category_name,
dict_lang.dict_label as language_name,
dict_emotion.dict_label as emotion_name,
dict_voice.dict_label as voice_name
from tts_corpus_info c
left join tts_corpus_category cc on c.category_id = cc.id
left join sys_dict_data dict_lang on c.language = dict_lang.dict_value and dict_lang.dict_type = 'tts_language'
left join sys_dict_data dict_emotion on c.emotion = dict_emotion.dict_value and dict_emotion.dict_type = 'tts_emotion'
left join sys_dict_data dict_voice on c.voice = dict_voice.dict_value and dict_voice.dict_type = 'tts_voice'
</sql>
<select id="selectCorpusList" parameterType="com.cmvr.tts.domain.CorpusInfo" resultMap="CorpusResult">
select id, text, pinyin, category_id, language, dialect, emotion, voice, speaker, scene,
audio_file_path, audio_duration, file_size,
create_by, create_time, update_by, update_time, remark
from tts_corpus_info
<where>
<if test="ids != null and ids.length > 0">
and id in
<foreach item="id" collection="ids" open="(" separator="," close=")">
#{id}
</foreach>
</if>
<if test="text != null and text != ''"> and text like concat('%', #{text}, '%')</if>
<if test="categoryId != null and categoryId != ''"> and category_id = #{categoryId}</if>
<if test="language != null and language != ''"> and language = #{language}</if>
<if test="dialect != null and dialect != ''"> and dialect = #{dialect}</if>
<if test="emotion != null and emotion != ''"> and emotion = #{emotion}</if>
<if test="voice != null and voice != ''"> and voice = #{voice}</if>
</where>
order by create_time desc
</select>
<select id="selectCorpusById" parameterType="String" resultMap="CorpusResult">
select id, text, pinyin, category_id, language, dialect, emotion, voice, speaker, scene,
audio_file_path, audio_duration, file_size,
create_by, create_time, update_by, update_time, remark
from tts_corpus_info
where id = #{id}
</select>
<select id="selectCorpusVoList" parameterType="com.cmvr.tts.domain.CorpusInfo" resultMap="CorpusVoResult">
<include refid="selectCorpusVo"/>
<where>
<if test="ids != null and ids.length > 0">
and c.id in
<foreach item="id" collection="ids" open="(" separator="," close=")">
#{id}
</foreach>
</if>
<if test="text != null and text != ''"> and c.text like concat('%', #{text}, '%')</if>
<if test="categoryId != null and categoryId != ''"> and c.category_id = #{categoryId}</if>
<if test="language != null and language != ''"> and c.language = #{language}</if>
<if test="dialect != null and dialect != ''"> and c.dialect = #{dialect}</if>
<if test="emotion != null and emotion != ''"> and c.emotion = #{emotion}</if>
<if test="voice != null and voice != ''"> and c.voice = #{voice}</if>
</where>
order by c.create_time desc
</select>
<insert id="insertCorpus" parameterType="com.cmvr.tts.domain.CorpusInfo">
insert into tts_corpus_info
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="id != null">id,</if>
<if test="createBy != null">create_by,</if>
<if test="createTime != null">create_time,</if>
<if test="updateBy != null">update_by,</if>
<if test="updateTime != null">update_time,</if>
<if test="remark != null">remark,</if>
<if test="text != null">text,</if>
<if test="pinyin != null">pinyin,</if>
<if test="categoryId != null">category_id,</if>
<if test="language != null">language,</if>
<if test="dialect != null">dialect,</if>
<if test="emotion != null">emotion,</if>
<if test="voice != null">voice,</if>
<if test="speaker != null">speaker,</if>
<if test="scene != null">scene,</if>
<if test="audioFilePath != null">audio_file_path,</if>
<if test="audioDuration != null">audio_duration,</if>
<if test="fileSize != null">file_size,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">#{id},</if>
<if test="createBy != null">#{createBy},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateBy != null">#{updateBy},</if>
<if test="updateTime != null">#{updateTime},</if>
<if test="remark != null">#{remark},</if>
<if test="text != null">#{text},</if>
<if test="pinyin != null">#{pinyin},</if>
<if test="categoryId != null">#{categoryId},</if>
<if test="language != null">#{language},</if>
<if test="dialect != null">#{dialect},</if>
<if test="emotion != null">#{emotion},</if>
<if test="voice != null">#{voice},</if>
<if test="speaker != null">#{speaker},</if>
<if test="scene != null">#{scene},</if>
<if test="audioFilePath != null">#{audioFilePath},</if>
<if test="audioDuration != null">#{audioDuration},</if>
<if test="fileSize != null">#{fileSize},</if>
</trim>
</insert>
<update id="updateCorpus" parameterType="com.cmvr.tts.domain.CorpusInfo">
update tts_corpus_info
<trim prefix="SET" suffixOverrides=",">
<if test="createBy != null">create_by = #{createBy},</if>
<if test="createTime != null">create_time = #{createTime},</if>
<if test="updateBy != null">update_by = #{updateBy},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
<if test="remark != null">remark = #{remark},</if>
<if test="text != null">text = #{text},</if>
<if test="pinyin != null">pinyin = #{pinyin},</if>
<if test="categoryId != null">category_id = #{categoryId},</if>
<if test="language != null">language = #{language},</if>
<if test="dialect != null">dialect = #{dialect},</if>
<if test="emotion != null">emotion = #{emotion},</if>
<if test="voice != null">voice = #{voice},</if>
<if test="speaker != null">speaker = #{speaker},</if>
<if test="scene != null">scene = #{scene},</if>
<if test="audioFilePath != null">audio_file_path = #{audioFilePath},</if>
<if test="audioDuration != null">audio_duration = #{audioDuration},</if>
<if test="fileSize != null">file_size = #{fileSize},</if>
</trim>
where id = #{id}
</update>
<delete id="deleteCorpusById" parameterType="String">
delete from tts_corpus_info where id = #{id}
</delete>
<delete id="deleteCorpusByIds" parameterType="String">
delete from tts_corpus_info where id in
<foreach item="id" collection="array" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
<select id="selectDashboardStats" resultType="java.util.Map">
select
count(*) as totalCorpus,
sum(case when date_format(create_time,'%Y-%m-%d') = curdate() then 1 else 0 end) as todayCorpus,
count(distinct language) as totalLanguages,
count(distinct voice) as totalVoices,
avg(audio_duration) as avgDuration
from tts_corpus_info
</select>
<select id="selectYesterdayStats" resultType="java.util.Map">
select
count(*) as totalCorpus,
sum(case when date_format(create_time,'%Y-%m-%d') = date_sub(curdate(), interval 1 day) then 1 else 0 end) as todayCorpus,
count(distinct language) as totalLanguages,
count(distinct voice) as totalVoices,
avg(audio_duration) as avgDuration
from tts_corpus_info
where date_format(create_time,'%Y-%m-%d') &lt;= date_sub(curdate(), interval 1 day)
</select>
<select id="selectStatsByLanguage" resultType="java.util.Map">
select language as name, count(*) as value
from tts_corpus_info
group by language
order by value desc
</select>
<select id="selectStatsByDialect" resultType="java.util.Map">
select dialect as name, count(*) as value
from tts_corpus_info
where dialect is not null
group by dialect
order by value desc
</select>
<select id="selectStatsByEmotion" resultType="java.util.Map">
select emotion as name, count(*) as value
from tts_corpus_info
where emotion is not null
group by emotion
order by value desc
</select>
<select id="selectStatsByCategory" resultType="java.util.Map">
select cc.category_name as name, count(c.id) as value
from tts_corpus_category cc
left join tts_corpus_info c on cc.id = c.category_id
group by cc.id, cc.category_name
order by value desc
</select>
</mapper>

View File

@ -0,0 +1,260 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.cmvr.tts.mapper.TtsSynthesizeTaskMapper">
<resultMap type="com.cmvr.tts.domain.TtsSynthesizeTask" id="TtsTaskResult">
<result property="id" column="id"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/>
<result property="updateTime" column="update_time"/>
<result property="remark" column="remark"/>
<result property="taskCode" column="task_code"/>
<result property="text" column="text"/>
<result property="language" column="language"/>
<result property="dialect" column="dialect"/>
<result property="voice" column="voice"/>
<result property="emotion" column="emotion"/>
<result property="speed" column="speed"/>
<result property="volume" column="volume"/>
<result property="pitch" column="pitch"/>
<result property="sampleRate" column="sample_rate"/>
<result property="outputFormat" column="output_format"/>
<result property="styleTemplate" column="style_template"/>
<result property="status" column="status"/>
<result property="audioFilePath" column="audio_file_path"/>
<result property="audioDuration" column="audio_duration"/>
<result property="fileSize" column="file_size"/>
<result property="errorMsg" column="error_msg"/>
</resultMap>
<resultMap type="com.cmvr.tts.domain.vo.TtsSynthesizeTaskVo" id="TtsTaskVoResult">
<result property="id" column="id"/>
<result property="taskCode" column="task_code"/>
<result property="text" column="text"/>
<result property="language" column="language"/>
<result property="languageName" column="language_name"/>
<result property="dialect" column="dialect"/>
<result property="voice" column="voice"/>
<result property="voiceName" column="voice_name"/>
<result property="emotion" column="emotion"/>
<result property="emotionName" column="emotion_name"/>
<result property="speed" column="speed"/>
<result property="volume" column="volume"/>
<result property="pitch" column="pitch"/>
<result property="sampleRate" column="sample_rate"/>
<result property="outputFormat" column="output_format"/>
<result property="styleTemplate" column="style_template"/>
<result property="status" column="status"/>
<result property="statusName" column="status_name"/>
<result property="audioFilePath" column="audio_file_path"/>
<result property="audioDuration" column="audio_duration"/>
<result property="fileSize" column="file_size"/>
<result property="errorMsg" column="error_msg"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/>
<result property="updateTime" column="update_time"/>
<result property="remark" column="remark"/>
</resultMap>
<sql id="selectTtsTaskVo">
select t.id, t.task_code, t.text, t.language, t.dialect, t.voice, t.emotion,
t.speed, t.volume, t.pitch, t.sample_rate, t.output_format, t.style_template,
t.status, t.audio_file_path, t.audio_duration, t.file_size, t.error_msg,
t.create_by, t.create_time, t.update_by, t.update_time, t.remark,
dict_lang.dict_label as language_name,
dict_voice.dict_label as voice_name,
dict_emotion.dict_label as emotion_name,
case t.status
when '0' then '待处理'
when '1' then '合成中'
when '2' then '成功已完成'
when '3' then '失败'
else '未知'
end as status_name
from tts_synthesize_task t
left join sys_dict_data dict_lang on t.language = dict_lang.dict_value and dict_lang.dict_type = 'tts_language'
left join sys_dict_data dict_voice on t.voice = dict_voice.dict_value and dict_voice.dict_type = 'tts_voice'
left join sys_dict_data dict_emotion on t.emotion = dict_emotion.dict_value and dict_emotion.dict_type = 'tts_emotion'
</sql>
<select id="selectTtsTaskList" parameterType="com.cmvr.tts.domain.TtsSynthesizeTask" resultMap="TtsTaskResult">
select id, task_code, text, language, dialect, voice, emotion, speed, volume, pitch,
sample_rate, output_format, style_template, status, audio_file_path, audio_duration,
file_size, error_msg, create_by, create_time, update_by, update_time, remark
from tts_synthesize_task
<where>
<if test="taskCode != null and taskCode != ''"> and task_code = #{taskCode}</if>
<if test="text != null and text != ''"> and text like concat('%', #{text}, '%')</if>
<if test="status != null and status != ''"> and status = #{status}</if>
<if test="language != null and language != ''"> and language = #{language}</if>
<if test="voice != null and voice != ''"> and voice = #{voice}</if>
</where>
order by create_time desc
</select>
<select id="selectTtsTaskById" parameterType="String" resultMap="TtsTaskResult">
select id, task_code, text, language, dialect, voice, emotion, speed, volume, pitch,
sample_rate, output_format, style_template, status, audio_file_path, audio_duration,
file_size, error_msg, create_by, create_time, update_by, update_time, remark
from tts_synthesize_task
where id = #{id}
</select>
<select id="selectTtsTaskVoList" parameterType="com.cmvr.tts.domain.TtsSynthesizeTask" resultMap="TtsTaskVoResult">
<include refid="selectTtsTaskVo"/>
<where>
<if test="taskCode != null and taskCode != ''"> and t.task_code = #{taskCode}</if>
<if test="text != null and text != ''"> and t.text like concat('%', #{text}, '%')</if>
<if test="status != null and status != ''"> and t.status = #{status}</if>
<if test="params.beginTime != null and params.beginTime != ''">
and date_format(t.create_time,'%Y%m%d') &gt;= date_format(#{params.beginTime},'%Y%m%d')
</if>
<if test="params.endTime != null and params.endTime != ''">
and date_format(t.create_time,'%Y%m%d') &lt;= date_format(#{params.endTime},'%Y%m%d')
</if>
</where>
order by t.create_time desc
</select>
<insert id="insertTtsTask" parameterType="com.cmvr.tts.domain.TtsSynthesizeTask">
insert into tts_synthesize_task
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="id != null">id,</if>
<if test="createBy != null">create_by,</if>
<if test="createTime != null">create_time,</if>
<if test="updateBy != null">update_by,</if>
<if test="updateTime != null">update_time,</if>
<if test="remark != null">remark,</if>
<if test="taskCode != null">task_code,</if>
<if test="text != null">text,</if>
<if test="language != null">language,</if>
<if test="dialect != null">dialect,</if>
<if test="voice != null">voice,</if>
<if test="emotion != null">emotion,</if>
<if test="speed != null">speed,</if>
<if test="volume != null">volume,</if>
<if test="pitch != null">pitch,</if>
<if test="sampleRate != null">sample_rate,</if>
<if test="outputFormat != null">output_format,</if>
<if test="styleTemplate != null">style_template,</if>
<if test="status != null">status,</if>
<if test="audioFilePath != null">audio_file_path,</if>
<if test="audioDuration != null">audio_duration,</if>
<if test="fileSize != null">file_size,</if>
<if test="errorMsg != null">error_msg,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">#{id},</if>
<if test="createBy != null">#{createBy},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateBy != null">#{updateBy},</if>
<if test="updateTime != null">#{updateTime},</if>
<if test="remark != null">#{remark},</if>
<if test="taskCode != null">#{taskCode},</if>
<if test="text != null">#{text},</if>
<if test="language != null">#{language},</if>
<if test="dialect != null">#{dialect},</if>
<if test="voice != null">#{voice},</if>
<if test="emotion != null">#{emotion},</if>
<if test="speed != null">#{speed},</if>
<if test="volume != null">#{volume},</if>
<if test="pitch != null">#{pitch},</if>
<if test="sampleRate != null">#{sampleRate},</if>
<if test="outputFormat != null">#{outputFormat},</if>
<if test="styleTemplate != null">#{styleTemplate},</if>
<if test="status != null">#{status},</if>
<if test="audioFilePath != null">#{audioFilePath},</if>
<if test="audioDuration != null">#{audioDuration},</if>
<if test="fileSize != null">#{fileSize},</if>
<if test="errorMsg != null">#{errorMsg},</if>
</trim>
</insert>
<update id="updateTtsTask" parameterType="com.cmvr.tts.domain.TtsSynthesizeTask">
update tts_synthesize_task
<trim prefix="SET" suffixOverrides=",">
<if test="createBy != null">create_by = #{createBy},</if>
<if test="createTime != null">create_time = #{createTime},</if>
<if test="updateBy != null">update_by = #{updateBy},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
<if test="remark != null">remark = #{remark},</if>
<if test="taskCode != null">task_code = #{taskCode},</if>
<if test="text != null">text = #{text},</if>
<if test="language != null">language = #{language},</if>
<if test="dialect != null">dialect = #{dialect},</if>
<if test="voice != null">voice = #{voice},</if>
<if test="emotion != null">emotion = #{emotion},</if>
<if test="speed != null">speed = #{speed},</if>
<if test="volume != null">volume = #{volume},</if>
<if test="pitch != null">pitch = #{pitch},</if>
<if test="sampleRate != null">sample_rate = #{sampleRate},</if>
<if test="outputFormat != null">output_format = #{outputFormat},</if>
<if test="styleTemplate != null">style_template = #{styleTemplate},</if>
<if test="status != null">status = #{status},</if>
<if test="audioFilePath != null">audio_file_path = #{audioFilePath},</if>
<if test="audioDuration != null">audio_duration = #{audioDuration},</if>
<if test="fileSize != null">file_size = #{fileSize},</if>
<if test="errorMsg != null">error_msg = #{errorMsg},</if>
</trim>
where id = #{id}
</update>
<delete id="deleteTtsTaskById" parameterType="String">
delete from tts_synthesize_task where id = #{id}
</delete>
<select id="selectDashboardStats" resultType="java.util.Map">
select
count(*) as totalTasks,
sum(case when date_format(create_time,'%Y-%m-%d') = curdate() then 1 else 0 end) as todayTasks,
count(distinct language) as totalLanguages,
count(distinct voice) as totalVoices,
avg(audio_duration) as avgDuration
from tts_synthesize_task
</select>
<select id="selectYesterdayStats" resultType="java.util.Map">
select
count(*) as totalTasks,
sum(case when date_format(create_time,'%Y-%m-%d') = date_sub(curdate(), interval 1 day) then 1 else 0 end) as todayTasks,
count(distinct language) as totalLanguages,
count(distinct voice) as totalVoices,
avg(audio_duration) as avgDuration
from tts_synthesize_task
where date_format(create_time,'%Y-%m-%d') &lt;= date_sub(curdate(), interval 1 day)
</select>
<select id="selectStatsByLanguage" resultType="java.util.Map">
select language as name, count(*) as value
from tts_synthesize_task
group by language
order by value desc
</select>
<select id="selectStatsByVoice" resultType="java.util.Map">
select voice as name, count(*) as value
from tts_synthesize_task
group by voice
order by value desc
</select>
<select id="selectStatsByEmotion" resultType="java.util.Map">
select emotion as name, count(*) as value
from tts_synthesize_task
group by emotion
order by value desc
</select>
<select id="selectLast7DaysTrend" resultType="java.util.Map">
select date_format(create_time,'%Y-%m-%d') as date, count(*) as value
from tts_synthesize_task
where create_time &gt;= date_sub(curdate(), interval 6 day)
group by date_format(create_time,'%Y-%m-%d')
order by date
</select>
</mapper>

View File

@ -316,6 +316,13 @@
<version>${cmvr-iot.version}</version>
</dependency>
<!-- 智能座舱语音语料采集-->
<dependency>
<groupId>com.cmvr</groupId>
<artifactId>cmvr-iot-tts</artifactId>
<version>${cmvr-iot.version}</version>
</dependency>
<!-- huTool-->
<dependency>
<groupId>cn.hutool</groupId>
@ -354,6 +361,7 @@
<module>cmvr-iot-evaluation</module>
<module>cmvr-iot-inspection</module>
<module>cmvr-iot-aima</module>
<module>cmvr-iot-tts</module>
</modules>
<packaging>pom</packaging>

77
sql/tts_corpus_module.sql Normal file
View File

@ -0,0 +1,77 @@
-- ----------------------------
-- 智能座舱语音语料采集模块数据库表结构
-- ----------------------------
-- 1. TTS合成任务表
DROP TABLE IF EXISTS `tts_synthesize_task`;
CREATE TABLE `tts_synthesize_task` (
`id` varchar(64) NOT NULL COMMENT '主键ID',
`create_by` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
`task_code` varchar(64) DEFAULT NULL COMMENT '任务编码',
`text` text NOT NULL COMMENT '合成文本',
`language` varchar(20) DEFAULT 'zh-CN' COMMENT '语种',
`dialect` varchar(20) DEFAULT NULL COMMENT '方言',
`voice` varchar(50) DEFAULT NULL COMMENT '音色',
`emotion` varchar(20) DEFAULT NULL COMMENT '情绪',
`speed` decimal(5,2) DEFAULT '1.00' COMMENT '语速',
`volume` decimal(5,2) DEFAULT '1.00' COMMENT '音量',
`pitch` decimal(5,2) DEFAULT '1.00' COMMENT '音高',
`sample_rate` int(11) DEFAULT null COMMENT '采样率',
`output_format` varchar(20) DEFAULT 'wav' COMMENT '输出格式',
`style_template` varchar(50) DEFAULT NULL COMMENT '说话风格模板名称',
`status` char(1) DEFAULT '0' COMMENT '任务状态(0待处理 1合成中 2成功已完成 3失败)',
`audio_file_path` varchar(500) DEFAULT NULL COMMENT '音频文件存储路径',
`audio_duration` decimal(10,2) DEFAULT NULL COMMENT '音频时长(秒)',
`file_size` bigint(20) DEFAULT NULL COMMENT '文件大小(字节)',
`error_msg` text COMMENT '失败原因',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_task_code` (`task_code`),
KEY `idx_status` (`status`),
KEY `idx_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='TTS合成任务表';
-- 2. 语料分类树形表
DROP TABLE IF EXISTS `tts_corpus_category`;
CREATE TABLE `tts_corpus_category` (
`id` varchar(64) NOT NULL COMMENT '主键ID',
`create_by` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
`parent_id` varchar(64) DEFAULT '0' COMMENT '父分类ID',
`category_name` varchar(100) NOT NULL COMMENT '分类名称',
`sort_order` int(11) DEFAULT '0' COMMENT '排序',
PRIMARY KEY (`id`),
KEY `idx_parent_id` (`parent_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='语料分类树形表';
-- 3. 车载语料数据表
DROP TABLE IF EXISTS `tts_corpus_info`;
CREATE TABLE `tts_corpus_info` (
`id` varchar(64) NOT NULL COMMENT '主键ID',
`create_by` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
`text` text NOT NULL COMMENT '文本内容',
`pinyin` varchar(500) DEFAULT NULL COMMENT '拼音',
`category_id` varchar(64) DEFAULT NULL COMMENT '所属分类ID',
`language` varchar(20) DEFAULT 'zh-CN' COMMENT '语种',
`dialect` varchar(20) DEFAULT NULL COMMENT '方言',
`emotion` varchar(20) DEFAULT NULL COMMENT '情绪',
`voice` varchar(50) DEFAULT NULL COMMENT '音色',
`speaker` varchar(50) DEFAULT NULL COMMENT '说话人',
`scene` varchar(100) DEFAULT NULL COMMENT '适用场景',
`audio_file_path` varchar(500) DEFAULT NULL COMMENT '音频文件路径',
`audio_duration` decimal(10,2) DEFAULT NULL COMMENT '音频时长(秒)',
`file_size` bigint(20) DEFAULT NULL COMMENT '文件大小(字节)',
PRIMARY KEY (`id`),
KEY `idx_category_id` (`category_id`),
KEY `idx_language` (`language`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='车载语料数据表';