From e15a249caf5575839b29359d09b584cc44b6fbb4 Mon Sep 17 00:00:00 2001 From: sdy Date: Wed, 20 May 2026 18:37:39 +0800 Subject: [PATCH] =?UTF-8?q?1.=E5=A2=9E=E5=8A=A0=E5=96=8A=E8=AF=9D=E5=99=A8?= =?UTF-8?q?=E6=A8=A1=E6=9D=BF=E9=85=8D=E7=BD=AE=202.=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E6=A8=A1=E6=9D=BF=E5=96=8A=E8=AF=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/constant/BusinessConstant.java | 3 +- .../business/controller/MinioController.java | 11 +++ .../business/controller/PsdkController.java | 13 ++- .../controller/SpeakerController.java | 56 ++++++++++- .../modules/business/dto/SpeakerDTO.java | 67 +++++++++---- .../business/service/MinioService.java | 3 + .../modules/business/service/PsdkService.java | 3 + .../business/service/SpeakerService.java | 16 ++++ .../service/impl/MinioServiceImpl.java | 14 +++ .../service/impl/PsdkServiceImpl.java | 21 ++++- .../service/impl/SpeakerServiceImpl.java | 93 ++++++++++++++++++- .../multictrl/common/exception/ErrorCode.java | 2 + .../main/resources/i18n/messages.properties | 4 +- 13 files changed, 279 insertions(+), 27 deletions(-) diff --git a/admin/src/main/java/com/multictrl/common/constant/BusinessConstant.java b/admin/src/main/java/com/multictrl/common/constant/BusinessConstant.java index bd7d17b..760803f 100644 --- a/admin/src/main/java/com/multictrl/common/constant/BusinessConstant.java +++ b/admin/src/main/java/com/multictrl/common/constant/BusinessConstant.java @@ -13,7 +13,8 @@ public interface BusinessConstant { String ROUTE_KMZ_BUCKET = "route-kmz";//航线桶 String DOCK_MEDIA_BUCKET = "dock-media";//机库回传媒体桶 String REMOTE_LOG_BUCKET = "remote-log";//机库回传媒体桶 - String DEVICE_FIRMWARE_BUCKET = "device-firmware"; + String DEVICE_FIRMWARE_BUCKET = "device-firmware";//设备固件桶 + String SPEAKER_AUDIO_BUCKET = "speaker-audio";//喊话器音频桶 //********************************* route action *********************************// String DEFAULT_ACTION_TRIGGER_TYPE = "reachPoint";//默认动作触发器类型 到达航点执行 diff --git a/admin/src/main/java/com/multictrl/modules/business/controller/MinioController.java b/admin/src/main/java/com/multictrl/modules/business/controller/MinioController.java index 4610378..a1ebea8 100644 --- a/admin/src/main/java/com/multictrl/modules/business/controller/MinioController.java +++ b/admin/src/main/java/com/multictrl/modules/business/controller/MinioController.java @@ -33,4 +33,15 @@ public class MinioController { return new Result().ok(minioService.uploadRouteImg(dockSn, file)); } + + @Operation(summary = "上传喊话器音频文件") + @PostMapping("/uploadSpeakerAudio/{dockSn}") + public Result uploadSpeakerAudio(@PathVariable String dockSn, + @RequestParam("file") MultipartFile file) { + if (file.isEmpty()) { + return new Result().error(ErrorCode.UPLOAD_FILE_EMPTY); + } + + return new Result().ok(minioService.uploadSpeakerAudio(dockSn, file)); + } } diff --git a/admin/src/main/java/com/multictrl/modules/business/controller/PsdkController.java b/admin/src/main/java/com/multictrl/modules/business/controller/PsdkController.java index afd2724..3c719fa 100644 --- a/admin/src/main/java/com/multictrl/modules/business/controller/PsdkController.java +++ b/admin/src/main/java/com/multictrl/modules/business/controller/PsdkController.java @@ -71,12 +71,23 @@ public class PsdkController { @PostMapping("/startSpeakerTts/{dockSn}") @Operation(summary = "播放TTS文本") @RequiresPermissions("bus:speaker:startSpeakerTts") - public Result drcSetTts(@PathVariable String dockSn, @RequestBody TtsText ttsText) { + public Result startSpeakerTts(@PathVariable String dockSn, @RequestBody TtsText ttsText) { ValidatorUtils.validateEntity(ttsText); return new Result<>().ok(psdkService.startSpeakerTts(dockSn, ttsText)); } + @LogOperation("播放模板文本") + @PostMapping("/startSpeakerTemplate/{dockSn}") + @Operation(summary = "播放模板文本") + @RequiresPermissions("bus:speaker:startSpeakerTemplate") + public Result startSpeakerTemplate(@PathVariable String dockSn, + @Parameter(name = "id", description = "模版编号") + @RequestParam Long id) { + + return new Result<>().ok(psdkService.startSpeakerTemplate(dockSn, id)); + } + @LogOperation("播放音频文件") @PostMapping("/playAudio/{dockSn}") @Operation(summary = "播放音频文件") diff --git a/admin/src/main/java/com/multictrl/modules/business/controller/SpeakerController.java b/admin/src/main/java/com/multictrl/modules/business/controller/SpeakerController.java index bdaae7b..21971f5 100644 --- a/admin/src/main/java/com/multictrl/modules/business/controller/SpeakerController.java +++ b/admin/src/main/java/com/multictrl/modules/business/controller/SpeakerController.java @@ -2,18 +2,24 @@ package com.multictrl.modules.business.controller; import com.multictrl.common.annotation.ApiOrder; import com.multictrl.common.annotation.LogOperation; +import com.multictrl.common.constant.Constant; +import com.multictrl.common.page.PageData; import com.multictrl.common.utils.Result; import com.multictrl.common.validator.ValidatorUtils; +import com.multictrl.modules.business.dto.SpeakerDTO; import com.multictrl.modules.business.dto.speaker.SpeakerSet; import com.multictrl.modules.business.dto.speaker.TtsText; import com.multictrl.modules.business.service.SpeakerService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.Parameters; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.springframework.web.bind.annotation.*; +import java.util.Map; + /** * 喊话器控制 大疆官方AS1喊话器 * @@ -28,6 +34,43 @@ import org.springframework.web.bind.annotation.*; public class SpeakerController { private final SpeakerService speakerService; + @GetMapping("page") + @Operation(summary = "分页") + @Parameters({ + @Parameter(name = Constant.PAGE, description = "当前页码,从1开始"), + @Parameter(name = Constant.LIMIT, description = "每页显示记录数"), + @Parameter(name = "type", description = "类型 1:文本 2:音频"), + @Parameter(name = "dockSn", description = "机库SN码") + }) + @RequiresPermissions("bus:speaker:page") + public Result> page(@Parameter(hidden = true) @RequestParam Map params) { + PageData page = speakerService.page(params); + + return new Result>().ok(page); + } + + @LogOperation("添加喊话模版") + @PostMapping("/addTemplate") + @Operation(summary = "添加喊话模版") + @RequiresPermissions("bus:speaker:addTemplate") + public Result addTemplate(@RequestBody SpeakerDTO speaker) { + ValidatorUtils.validateEntity(speaker); + speakerService.addTemplate(speaker); + + return new Result<>(); + } + + @LogOperation("删除模版") + @DeleteMapping("/deleteTemplate") + @Operation(summary = "删除模版") + @RequiresPermissions("bus:speaker:deleteTemplate") + public Result deleteTemplate(@Parameter(name = "id", description = "模版编号") + @RequestParam Long id) { + speakerService.deleteTemplate(id); + + return new Result<>(); + } + @LogOperation("设置音量") @PostMapping("/drcSetVolume/{dockSn}") @Operation(summary = "设置音量") @@ -82,9 +125,20 @@ public class SpeakerController { @PostMapping("/drcStartSpeakerTts/{dockSn}") @Operation(summary = "播放TTS文本") @RequiresPermissions("bus:speaker:drcStartSpeakerTts") - public Result drcSetTts(@PathVariable String dockSn, @RequestBody TtsText ttsText) { + public Result drcStartSpeakerTts(@PathVariable String dockSn, @RequestBody TtsText ttsText) { ValidatorUtils.validateEntity(ttsText); return new Result<>().ok(speakerService.drcStartSpeakerTts(dockSn, ttsText)); } + + @LogOperation("播放模板文本") + @PostMapping("/drcStartSpeakerTemplate/{dockSn}") + @Operation(summary = "播放模板文本") + @RequiresPermissions("bus:speaker:drcStartSpeakerTemplate") + public Result drcStartSpeakerTemplate(@PathVariable String dockSn, + @Parameter(name = "id", description = "模版编号") + @RequestParam Long id) { + + return new Result<>().ok(speakerService.drcStartSpeakerTemplate(dockSn, id)); + } } diff --git a/admin/src/main/java/com/multictrl/modules/business/dto/SpeakerDTO.java b/admin/src/main/java/com/multictrl/modules/business/dto/SpeakerDTO.java index e9adc62..6038c37 100644 --- a/admin/src/main/java/com/multictrl/modules/business/dto/SpeakerDTO.java +++ b/admin/src/main/java/com/multictrl/modules/business/dto/SpeakerDTO.java @@ -1,7 +1,11 @@ package com.multictrl.modules.business.dto; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.*; import lombok.Data; +import org.apache.commons.lang3.StringUtils; import java.io.Serial; import java.io.Serializable; @@ -10,39 +14,62 @@ import java.util.Date; /** * 喊话器内容 * - * @author Sdy + * @author Sdy * @since 1.0.0 2026-05-08 */ @Data @Schema(name = "喊话器内容") public class SpeakerDTO implements Serializable { @Serial - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 1L; - @Schema(name = "主键") - private Long id; + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + @Schema(name = "标识") + private Long id; - @Schema(name = "类型 1 文本 2 音频") - private Integer type; + @NotNull(message = "类型不能为空") + @Min(value = 1, message = "类型错误") + @Max(value = 2, message = "类型错误") + @Schema(name = "类型 1:文本 2:音频") + private Integer type; - @Schema(name = "机场编号") - private String dockSn; + @NotBlank(message = "机场编号不能为空") + @Schema(name = "机场编号") + private String dockSn; - @Schema(name = "模版名称") - private String name; + @NotBlank(message = "模版名称不能为空") + @Schema(name = "模版名称") + private String name; - @Schema(name = "文本内容") - private String textContent; + @Schema(name = "文本内容") + private String textContent; - @Schema(name = "pcm音频地址") - private String pcmPath; + @Schema(name = "源音频文件地址") + private String mediaPath; - @Schema(name = "源音频文件地址") - private String mediaPath; + @JsonProperty(access = JsonProperty.Access.READ_ONLY) + @Schema(name = "创建时间") + private Date createDate; - @Schema(name = "文件md5") - private String md5; + @JsonIgnore + @Schema(hidden = true) + @AssertTrue(message = "源音频文件不能为空") + public boolean isMediaPathValid() { + if (type == 2) { + return StringUtils.isNotBlank(mediaPath); + } - @Schema(name = "创建时间") - private Date createDate; + return true; + } + + @JsonIgnore + @Schema(hidden = true) + @AssertTrue(message = "文本内容不能为空") + public boolean isTextContentValid() { + if (type == 1) { + return StringUtils.isNotBlank(textContent); + } + + return true; + } } diff --git a/admin/src/main/java/com/multictrl/modules/business/service/MinioService.java b/admin/src/main/java/com/multictrl/modules/business/service/MinioService.java index 650c9b7..892000b 100644 --- a/admin/src/main/java/com/multictrl/modules/business/service/MinioService.java +++ b/admin/src/main/java/com/multictrl/modules/business/service/MinioService.java @@ -36,4 +36,7 @@ public interface MinioService { //上传固件 String uploadFirmware(InputStream inputStream, String deviceName, String originalFilename); + + //上传喊话器音频文件 + String uploadSpeakerAudio(String dockSn, MultipartFile file); } diff --git a/admin/src/main/java/com/multictrl/modules/business/service/PsdkService.java b/admin/src/main/java/com/multictrl/modules/business/service/PsdkService.java index 9c26e66..0f66b59 100644 --- a/admin/src/main/java/com/multictrl/modules/business/service/PsdkService.java +++ b/admin/src/main/java/com/multictrl/modules/business/service/PsdkService.java @@ -25,6 +25,9 @@ public interface PsdkService { //播放TTS文本 String startSpeakerTts(String dockSn, TtsText ttsText); + //播放模板文本 + String startSpeakerTemplate(String dockSn, Long id); + //播放音频文件 String playAudio(String dockSn, Long id); } diff --git a/admin/src/main/java/com/multictrl/modules/business/service/SpeakerService.java b/admin/src/main/java/com/multictrl/modules/business/service/SpeakerService.java index b38da96..75dfe46 100644 --- a/admin/src/main/java/com/multictrl/modules/business/service/SpeakerService.java +++ b/admin/src/main/java/com/multictrl/modules/business/service/SpeakerService.java @@ -1,11 +1,15 @@ package com.multictrl.modules.business.service; +import com.multictrl.common.page.PageData; import com.multictrl.common.service.CrudService; +import com.multictrl.modules.business.dao.SpeakerDao; import com.multictrl.modules.business.dto.SpeakerDTO; import com.multictrl.modules.business.dto.speaker.SpeakerSet; import com.multictrl.modules.business.dto.speaker.TtsText; import com.multictrl.modules.business.entity.SpeakerEntity; +import java.util.Map; + /** * 喊话器控制 * @@ -14,6 +18,12 @@ import com.multictrl.modules.business.entity.SpeakerEntity; */ public interface SpeakerService extends CrudService { + //添加喊话模版 + void addTemplate(SpeakerDTO speaker); + + //删除模板 + void deleteTemplate(Long id); + // 设置音量 String drcSetVolume(String dockSn, Integer volume); @@ -31,4 +41,10 @@ public interface SpeakerService extends CrudService { //播放TTS文本 String drcStartSpeakerTts(String dockSn, TtsText ttsText); + + //播放模板文本 + String drcStartSpeakerTemplate(String dockSn, Long id); + + //获取dao + SpeakerDao getDao(); } diff --git a/admin/src/main/java/com/multictrl/modules/business/service/impl/MinioServiceImpl.java b/admin/src/main/java/com/multictrl/modules/business/service/impl/MinioServiceImpl.java index 32321da..251617d 100644 --- a/admin/src/main/java/com/multictrl/modules/business/service/impl/MinioServiceImpl.java +++ b/admin/src/main/java/com/multictrl/modules/business/service/impl/MinioServiceImpl.java @@ -169,6 +169,20 @@ public class MinioServiceImpl implements MinioService { return BusinessConstant.DEVICE_FIRMWARE_BUCKET + "/" + path; } + @Override + public String uploadSpeakerAudio(String dockSn, MultipartFile file) { + //文件路径 + String path = dockSn + "/" + DateUtils.format(new Date(), DateUtils.DATE_TIME_PATTERN_COMPACT) + "." + FileNameUtil.extName(file.getOriginalFilename()); + try { + uploadFile(file.getInputStream(), BusinessConstant.SPEAKER_AUDIO_BUCKET, path); + } catch (Exception e) { + log.error(ExceptionUtils.getErrorStackTrace(e)); + throw new RenException(ErrorCode.OSS_UPLOAD_FILE_ERROR, file.getOriginalFilename()); + } + + return BusinessConstant.SPEAKER_AUDIO_BUCKET + "/" + path; + } + private String getMimeType(String fileName) { if (fileName == null) return "application/octet-stream"; diff --git a/admin/src/main/java/com/multictrl/modules/business/service/impl/PsdkServiceImpl.java b/admin/src/main/java/com/multictrl/modules/business/service/impl/PsdkServiceImpl.java index 774944c..bdcd884 100644 --- a/admin/src/main/java/com/multictrl/modules/business/service/impl/PsdkServiceImpl.java +++ b/admin/src/main/java/com/multictrl/modules/business/service/impl/PsdkServiceImpl.java @@ -10,6 +10,7 @@ import com.multictrl.common.utils.CacheUtils; import com.multictrl.common.utils.Utils; import com.multictrl.modules.business.dto.SpeakerDTO; import com.multictrl.modules.business.dto.speaker.TtsText; +import com.multictrl.modules.business.entity.SpeakerEntity; import com.multictrl.modules.business.service.DJIBaseService; import com.multictrl.modules.business.service.PsdkService; import com.multictrl.modules.business.service.SpeakerService; @@ -72,9 +73,21 @@ public class PsdkServiceImpl implements PsdkService { return djiBaseService.executeAndReturnResult(dockSn, "speaker_tts_play_start", data); } + @Override + public String startSpeakerTemplate(String dockSn, Long id) { + SpeakerEntity speakerEntity = speakerService.selectById(id); + if (speakerEntity == null || speakerEntity.getType() != 1) { + throw new RenException(ErrorCode.PARAMS_ERROR); + } + TtsText ttsText = new TtsText(); + ttsText.setName(speakerEntity.getName()); + ttsText.setText(speakerEntity.getTextContent()); + return startSpeakerTts(dockSn, ttsText); + } + @Override public String playAudio(String dockSn, Long id) { - SpeakerDTO speaker = speakerService.get(id); + SpeakerEntity speaker = speakerService.getDao().selectById(id); if (speaker == null || speaker.getType() != 2) { throw new RenException(ErrorCode.PARAMS_ERROR); } @@ -83,7 +96,11 @@ public class PsdkServiceImpl implements PsdkService { file.set("format", "pcm"); file.set("name", speaker.getName()); file.set("md5", speaker.getMd5()); - file.set("url", sysConfig.getIp() + "/" + BusinessConstant.FILE_PATH + speaker.getPcmPath()); + String protocol = BusinessConstant.HTTP_PROTOCOL; + if (sysConfig.getIsSsl()) { + protocol = BusinessConstant.HTTPS_PROTOCOL; + } + file.set("url", protocol + sysConfig.getIp() + ":" + sysConfig.getPort() + "/" + BusinessConstant.FILE_PATH + speaker.getPcmPath()); data.set("file", file); return djiBaseService.executeAndReturnResult(dockSn, "speaker_audio_play_start", data); } diff --git a/admin/src/main/java/com/multictrl/modules/business/service/impl/SpeakerServiceImpl.java b/admin/src/main/java/com/multictrl/modules/business/service/impl/SpeakerServiceImpl.java index fb189d3..b751d5a 100644 --- a/admin/src/main/java/com/multictrl/modules/business/service/impl/SpeakerServiceImpl.java +++ b/admin/src/main/java/com/multictrl/modules/business/service/impl/SpeakerServiceImpl.java @@ -1,12 +1,18 @@ package com.multictrl.modules.business.service.impl; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.multictrl.common.config.MinioConfig; import com.multictrl.common.constant.BusinessConstant; import com.multictrl.common.exception.ErrorCode; import com.multictrl.common.exception.RenException; +import com.multictrl.common.page.PageData; import com.multictrl.common.service.impl.CrudServiceImpl; import com.multictrl.common.utils.CacheUtils; +import com.multictrl.common.utils.ConvertUtils; +import com.multictrl.common.utils.FfmpegUtils; import com.multictrl.common.utils.Utils; import com.multictrl.modules.business.dao.SpeakerDao; import com.multictrl.modules.business.dto.SpeakerDTO; @@ -16,6 +22,7 @@ import com.multictrl.modules.business.entity.SpeakerEntity; import com.multictrl.modules.business.service.DJIBaseService; import com.multictrl.modules.business.service.SpeakerService; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import java.util.Map; @@ -26,14 +33,81 @@ import java.util.Map; * @author Sdy * @since 1.0.0 2026/5/6 */ +@Slf4j @Service @RequiredArgsConstructor public class SpeakerServiceImpl extends CrudServiceImpl implements SpeakerService { private final DJIBaseService djiBaseService; + private final MinioConfig minioConfig; @Override public QueryWrapper getWrapper(Map params) { - return null; + String id = (String) params.get("id"); + String type = (String) params.get("type"); + String dockSn = (String) params.get("dockSn"); + + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq(StrUtil.isNotBlank(id), "id", id); + if (StrUtil.isNotBlank(type)) { + wrapper.eq("type", Integer.parseInt(type)); + } + wrapper.eq(StrUtil.isNotBlank(dockSn), "dock_sn", dockSn); + wrapper.orderByDesc("create_date"); + + return wrapper; + } + + @Override + public void addTemplate(SpeakerDTO speaker) { + Long count = baseDao.selectCount(new QueryWrapper().eq("dock_sn", speaker.getDockSn()) + .eq("name", speaker.getName())); + if (count > 0) { + throw new RenException(ErrorCode.SPEAKER_NAME_EXIST); + } + SpeakerEntity speakerEntity = ConvertUtils.sourceToTarget(speaker, SpeakerEntity.class); + Integer type = speaker.getType(); + if (type == 1) { + speakerEntity.setMd5(Utils.md5Txt(speaker.getTextContent())); + } else { + String mediaPath = speaker.getMediaPath(); + String path = minioConfig.getOther().getDataPath() + "/" + mediaPath; + if (!FileUtil.exist(path)) { + throw new RenException(ErrorCode.FILE_NOT_EXIST, "音频"); + } + //调用ffmpeg将mp3或wav 转成pcm + String pcmPath = path.split("\\.")[0] + ".pcm"; + String command = "ffmpeg -i " + path + + " -ss 00 -t 179 -f s16le -ar 16000 -ac 1 -acodec pcm_s16le " + pcmPath; + FfmpegUtils.runCommand(command); + //轮询文件是否生成,因为ffmpeg和当前java进程不同步 + int retry = 20; + while (!FileUtil.exist(pcmPath) && retry-- > 0) { + Utils.sleep(1); + log.info("ffmpeg mp3 or wav 2 pcm, await {} s", 20 - retry); + } + if (!FileUtil.exist(pcmPath)) { + throw new RenException(ErrorCode.FILE_NOT_EXIST, "pcm音频"); + } + speakerEntity.setMd5(Utils.md5File(pcmPath)); + speakerEntity.setPcmPath(pcmPath.replaceAll(minioConfig.getOther().getDataPath() + "/", "")); + } + baseDao.insert(speakerEntity); + } + + @Override + public void deleteTemplate(Long id) { + SpeakerEntity speakerEntity = selectById(id); + if (speakerEntity == null) { + return; + } + Integer type = speakerEntity.getType(); + if (type == 2) { + String mediaPath = speakerEntity.getMediaPath(); + String pcmPath = speakerEntity.getPcmPath(); + FileUtil.del(minioConfig.getOther().getDataPath() + "/" + mediaPath); + FileUtil.del(minioConfig.getOther().getDataPath() + "/" + pcmPath); + } + deleteById(id); } @Override @@ -89,6 +163,23 @@ public class SpeakerServiceImpl extends CrudServiceImpl