feat(agv): 添加AGV协议定义和重构命令接口
- 新增agv.proto文件定义AGV相关消息结构包括位姿、速度、电池状态等 - 新增统一2D/3D地图格式定义支持栅格、点云、体素等多种地图类型 - 重构agv_command.proto将原有命令接口替换为新的运行状态查询接口 - 添加导航控制、速度设置、地图管理等标准化命令接口 - 整合站点管理、路径规划、任务状态等AGV核心功能协议 - 引入运动约束选项和适配器参数扩展支持不同厂商设备特性
This commit is contained in:
parent
46fb4fecde
commit
736c510270
@ -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>
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
@ -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参数)
|
||||
* 直接导航到指定的X、Y坐标和角度,不依赖站点
|
||||
* 注意:此命令会取消当前正在执行的任务,仅支持双轮差速底盘
|
||||
* 停止建图
|
||||
*
|
||||
* @param edgeCommonVO 边缘通用参数
|
||||
* @param freeGoPoint 自由导航目标点(包含x、y、theta)
|
||||
* @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_status,false=全量信息)
|
||||
* @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);
|
||||
}
|
||||
@ -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));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -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
File diff suppressed because it is too large
Load Diff
@ -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);
|
||||
// 开始建图/扫图会话。请求只选择 2D、3D 或二者都要,
|
||||
// 控制器特有地图格式由 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);
|
||||
}
|
||||
@ -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;
|
||||
// 最大线加速度,单位:米/秒^2;0 表示使用 AGV 默认值。
|
||||
double max_acceleration = 3;
|
||||
// 最大角加速度,单位:弧度/秒^2;0 表示使用 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;
|
||||
}
|
||||
|
||||
// 地图维度请求。这里只描述上位机想要 2D、3D 还是二者都要,
|
||||
// 不用于指定厂商文件格式;厂商格式必须在 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;
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 机器人位置信息(包含X、Y坐标、角度、当前站点等)
|
||||
* @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);
|
||||
|
||||
}
|
||||
@ -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 机器人位置信息(包含X、Y坐标、角度、当前站点等)
|
||||
* @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);
|
||||
}
|
||||
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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
33
cmvr-iot-tts/pom.xml
Normal 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>
|
||||
@ -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;
|
||||
}
|
||||
109
cmvr-iot-tts/src/main/java/com/cmvr/tts/domain/CorpusInfo.java
Normal file
109
cmvr-iot-tts/src/main/java/com/cmvr/tts/domain/CorpusInfo.java
Normal 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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
248
cmvr-iot-tts/src/main/resources/mapper/tts/CorpusInfoMapper.xml
Normal file
248
cmvr-iot-tts/src/main/resources/mapper/tts/CorpusInfoMapper.xml
Normal 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') <= 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>
|
||||
@ -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') >= 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') <= 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') <= 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 >= date_sub(curdate(), interval 6 day)
|
||||
group by date_format(create_time,'%Y-%m-%d')
|
||||
order by date
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
8
pom.xml
8
pom.xml
@ -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
77
sql/tts_corpus_module.sql
Normal 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='车载语料数据表';
|
||||
Loading…
Reference in New Issue
Block a user