diff --git a/cmvr-iot-admin/src/main/java/com/cmvr/web/controller/vi/ViCorpusController.java b/cmvr-iot-admin/src/main/java/com/cmvr/web/controller/vi/ViCorpusController.java index bd90e29..93c374e 100644 --- a/cmvr-iot-admin/src/main/java/com/cmvr/web/controller/vi/ViCorpusController.java +++ b/cmvr-iot-admin/src/main/java/com/cmvr/web/controller/vi/ViCorpusController.java @@ -4,11 +4,14 @@ import com.cmvr.common.core.controller.BaseController; import com.cmvr.common.core.domain.AjaxResult; import com.cmvr.common.core.page.TableDataInfo; import com.cmvr.vi.model.domain.ViCorpus; +import com.cmvr.vi.model.vo.ViContinuousCorpusVO; +import com.cmvr.vi.model.vo.ViCorpusVO; import com.cmvr.vi.service.IViCorpusService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.RequiredArgsConstructor; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -20,11 +23,6 @@ import org.springframework.web.bind.annotation.RestController; import java.util.List; -/** - * 语料库Controller - * - * @author cmvr-iot - */ @Api(tags = "语音交互--语料库") @RestController @RequestMapping("/vi/corpus") @@ -33,64 +31,53 @@ public class ViCorpusController extends BaseController { private final IViCorpusService viCorpusService; - /** - * 查询语料库列表 - */ - @ApiOperation("查询语料库列表") - @PreAuthorize("@ss.hasPermi('vi:corpus:list')") - @GetMapping("/list") - public TableDataInfo list(ViCorpus viCorpus) { - startPage(); - List list = viCorpusService.selectViCorpusList(viCorpus); - return getDataTable(list); - } - -// /** -// * 导出语料库列表 -// */ -// @ApiOperation("查询语料库列表") -// @PreAuthorize("@ss.hasPermi('vi:corpus:export')") -// @PostMapping("/export") -// public void export(HttpServletResponse response, ViCorpus viCorpus) { -// List list = viCorpusService.selectViCorpusList(viCorpus); -// ExcelUtil util = new ExcelUtil(ViCorpus.class); -// util.exportExcel(response, list, "语料库数据"); -// } - - /** - * 获取语料库详细信息 - */ - @ApiOperation("根据id获取语料库详细信息") + @ApiOperation("根据id获取语料详细信息") @PreAuthorize("@ss.hasPermi('vi:corpus:query')") @GetMapping(value = "/{corpusId}") public AjaxResult getInfo(@PathVariable("corpusId") Long corpusId) { return success(viCorpusService.selectViCorpusByCorpusId(corpusId)); } - /** - * 新增语料库 - */ - @ApiOperation("新增语料库") + @ApiOperation("查询单次对话语料/唤醒语料列表") + @PreAuthorize("@ss.hasPermi('vi:corpus:list')") + @GetMapping("/single/list") + public TableDataInfo list(@Validated ViCorpusVO viCorpusVO) { + startPage(); + List list = viCorpusService.selectViCorpusList(viCorpusVO); + return getDataTable(list); + } + + @ApiOperation("查询连续对话列表") + @PreAuthorize("@ss.hasPermi('vi:corpus:list')") + @GetMapping("/continuous/list") + public TableDataInfo continuousList(ViCorpus viCorpus) { + startPage(); + List list = viCorpusService.selectContinuousList(viCorpus); + return getDataTable(list); + } + + @ApiOperation("新增语料") @PreAuthorize("@ss.hasPermi('vi:corpus:add')") @PostMapping public AjaxResult add(@RequestBody ViCorpus viCorpus) { return toAjax(viCorpusService.insertViCorpus(viCorpus)); } - /** - * 修改语料库 - */ - @ApiOperation("修改语料库") + @ApiOperation("批量新增/修改") + @PreAuthorize("@ss.hasPermi('vi:corpus:add')") + @PostMapping("/insertOrUpdateBatch") + public AjaxResult insertOrUpdateBatch(@RequestBody List data) { + return toAjax(viCorpusService.insertOrUpdateBatch(data)); + } + + @ApiOperation("修改语料") @PreAuthorize("@ss.hasPermi('vi:corpus:edit')") @PutMapping public AjaxResult edit(@RequestBody ViCorpus viCorpus) { return toAjax(viCorpusService.updateViCorpus(viCorpus)); } - /** - * 删除语料库 - */ - @ApiOperation("删除语料库") + @ApiOperation("删除语料") @PreAuthorize("@ss.hasPermi('vi:corpus:remove')") @DeleteMapping("/{corpusIds}") public AjaxResult remove(@PathVariable Long[] corpusIds) { diff --git a/cmvr-iot-api/cmvr-iot-edge/cmvr-iot-grpc-client/src/main/java/com/cmvr/edge/client/service/impl/EdgeCameraServiceImpl.java b/cmvr-iot-api/cmvr-iot-edge/cmvr-iot-grpc-client/src/main/java/com/cmvr/edge/client/service/impl/EdgeCameraServiceImpl.java index 4f191cd..6cabbf4 100644 --- a/cmvr-iot-api/cmvr-iot-edge/cmvr-iot-grpc-client/src/main/java/com/cmvr/edge/client/service/impl/EdgeCameraServiceImpl.java +++ b/cmvr-iot-api/cmvr-iot-edge/cmvr-iot-grpc-client/src/main/java/com/cmvr/edge/client/service/impl/EdgeCameraServiceImpl.java @@ -24,7 +24,11 @@ import org.springframework.http.MediaType; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; +import javax.imageio.IIOImage; import javax.imageio.ImageIO; +import javax.imageio.ImageWriteParam; +import javax.imageio.ImageWriter; +import javax.imageio.stream.FileImageOutputStream; import java.awt.*; import java.awt.color.ColorSpace; import java.awt.image.BufferedImage; @@ -42,6 +46,7 @@ import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Base64; +import java.util.Iterator; import java.util.List; @@ -166,6 +171,7 @@ public class EdgeCameraServiceImpl implements EdgeCameraService, EdgeStreamServi throw new GlobalException("响应图片数据无效"); } String filePath = saveImage(frame.getData(), frame.getWidth(), frame.getHeight(), false); + log.info("彩色图片地址:{}", filePath); // String deepFilePath = saveImage(depthFrame.getData(), depthFrame.getWidth(), depthFrame.getHeight(), true); // List result = new ArrayList<>(); try { @@ -178,7 +184,7 @@ public class EdgeCameraServiceImpl implements EdgeCameraService, EdgeStreamServi } catch (IOException e) { throw new GlobalException("图片上传失败: " + e.getMessage()); } finally { - deleteTempFile(filePath); +// deleteTempFile(filePath); // deleteTempFile(deepFilePath); // todo 暂时不关闭摄像头 // stop(terminalId, deviceId); @@ -368,50 +374,50 @@ public class EdgeCameraServiceImpl implements EdgeCameraService, EdgeStreamServi String fileName = System.currentTimeMillis() + (isDepth ? "_depth" : "") + ".jpg"; String filePath = Paths.get(tempDir.getAbsolutePath(), fileName).toString(); + try { byte[] pixels = pixelData.toByteArray(); BufferedImage image; if (isDepth) { - // 深度图:单通道(灰度) + // 灰度深度图 DataBuffer buffer = new DataBufferByte(pixels, pixels.length); - WritableRaster raster = Raster.createInterleavedRaster( - buffer, width, height, width, 1, - new int[]{0}, null - ); - - ColorModel cm = new ComponentColorModel( - ColorSpace.getInstance(ColorSpace.CS_GRAY), - false, false, - Transparency.OPAQUE, - DataBuffer.TYPE_BYTE - ); + WritableRaster raster = Raster.createInterleavedRaster(buffer, width, height, width, 1, new int[]{0}, null); + ColorModel cm = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_GRAY), false, false, + Transparency.OPAQUE, DataBuffer.TYPE_BYTE); image = new BufferedImage(cm, raster, false, null); } else { - // 彩色图:RGB 三通道 + // 彩色RGB DataBuffer buffer = new DataBufferByte(pixels, pixels.length); - WritableRaster raster = Raster.createInterleavedRaster( - buffer, width, height, 3 * width, 3, - new int[]{2, 1, 0}, null - ); - - ColorModel cm = new ComponentColorModel( - ColorSpace.getInstance(ColorSpace.CS_sRGB), - false, false, - Transparency.OPAQUE, - DataBuffer.TYPE_BYTE - ); + WritableRaster raster = Raster.createInterleavedRaster(buffer, width, height, 3 * width, 3, + new int[]{1, 2, 0}, null); + ColorModel cm = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), false, false, + Transparency.OPAQUE, DataBuffer.TYPE_BYTE); image = new BufferedImage(cm, raster, false, null); } - File output = new File(filePath); - ImageIO.write(image, "jpg", output); + // 设置高质量JPEG压缩 + Iterator writers = ImageIO.getImageWritersByFormatName("jpg"); + ImageWriter writer = writers.next(); + + try (FileImageOutputStream output = new FileImageOutputStream(new File(filePath))) { + writer.setOutput(output); + ImageWriteParam param = writer.getDefaultWriteParam(); + if (param.canWriteCompressed()) { + param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); + param.setCompressionQuality(0.95f); // 压缩质量/压缩比 95% + } + writer.write(null, new IIOImage(image, null, null), param); + } + writer.dispose(); + } catch (IOException e) { throw new GlobalException(e.getMessage()); } return filePath; } + /** * 将本地文件转换为 MultipartFile */ diff --git a/cmvr-iot-vi/src/main/java/com/cmvr/vi/enums/ViCorpusEnum.java b/cmvr-iot-vi/src/main/java/com/cmvr/vi/enums/ViCorpusEnum.java new file mode 100644 index 0000000..1142300 --- /dev/null +++ b/cmvr-iot-vi/src/main/java/com/cmvr/vi/enums/ViCorpusEnum.java @@ -0,0 +1,6 @@ +package com.cmvr.vi.enums; + +public enum ViCorpusEnum { + WAKEUP, //唤醒 + TEST //测试 +} diff --git a/cmvr-iot-vi/src/main/java/com/cmvr/vi/model/domain/ViCorpus.java b/cmvr-iot-vi/src/main/java/com/cmvr/vi/model/domain/ViCorpus.java index 40d5355..77e6361 100644 --- a/cmvr-iot-vi/src/main/java/com/cmvr/vi/model/domain/ViCorpus.java +++ b/cmvr-iot-vi/src/main/java/com/cmvr/vi/model/domain/ViCorpus.java @@ -8,83 +8,52 @@ import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; -/** - * 语料库对象 vi_corpus - * - * @author cmvr-iot - */ @Data -@ApiModel("语料库对象") +@ApiModel("语音交互--语料库") public class ViCorpus extends BaseEntity { private static final long serialVersionUID = 1L; - /** - * 语料ID - */ @ApiModelProperty("语料ID") @TableId(type = IdType.AUTO) private Long corpusId; - /** - * 语料名称 - */ @Excel(name = "语料名称") @ApiModelProperty("语料名称") private String corpusName; - /** - * 语料类型(WAKEUP唤醒 / TEST测试) - */ @Excel(name = "语料类型", readConverterExp = "WAKEUP唤醒,TEST测试") @ApiModelProperty("语料类型") private String type; - /** - * 语音对应的文本 - */ @Excel(name = "语音对应的文本") @ApiModelProperty("语音对应的文本") private String textContent; - /** - * 语音文件存放路径 - */ @Excel(name = "语音文件存放路径") @ApiModelProperty("语音文件存放路径") private String audioPath; - /** - * 音色 - */ @Excel(name = "音色") @ApiModelProperty("音色") private String voiceType; - /** - * 方言 - */ @Excel(name = "方言") @ApiModelProperty("方言") private String dialect; - /** - * 预期结果(预留) - */ // @Excel(name = "预期结果(预留)") @ApiModelProperty("预期结果(预留)") private String expectedResult; - /** - * 同一父语料下的顺序 - */ @Excel(name = "同一父语料下的顺序") @ApiModelProperty("同一父语料下的顺序") private Integer sortOrder; - /** - * 状态(0正常 1停用) - */ + @Excel(name = "是否连续(0连续 1单次)") + @ApiModelProperty("是否连续(0连续 1单次),测试语料时使用") + private Integer continuous = 1; + @Excel(name = "状态", readConverterExp = "0=正常,1=停用") @ApiModelProperty(value = "状态", notes = "0=正常,1=停用") private String status; diff --git a/cmvr-iot-vi/src/main/java/com/cmvr/vi/model/vo/ViContinuousCorpusVO.java b/cmvr-iot-vi/src/main/java/com/cmvr/vi/model/vo/ViContinuousCorpusVO.java new file mode 100644 index 0000000..2ca2640 --- /dev/null +++ b/cmvr-iot-vi/src/main/java/com/cmvr/vi/model/vo/ViContinuousCorpusVO.java @@ -0,0 +1,25 @@ +package com.cmvr.vi.model.vo; + +import com.cmvr.vi.model.domain.ViCorpus; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +@Data +@ApiModel("连续对话分组VO") +public class ViContinuousCorpusVO { + + @ApiModelProperty("语料名称") + private String corpusName; + + @ApiModelProperty("音色") + private String voiceType; + + @ApiModelProperty("方言") + private String dialect; + + @ApiModelProperty("子语料列表") + private List children; +} diff --git a/cmvr-iot-vi/src/main/java/com/cmvr/vi/model/vo/ViCorpusVO.java b/cmvr-iot-vi/src/main/java/com/cmvr/vi/model/vo/ViCorpusVO.java new file mode 100644 index 0000000..6d72b8e --- /dev/null +++ b/cmvr-iot-vi/src/main/java/com/cmvr/vi/model/vo/ViCorpusVO.java @@ -0,0 +1,31 @@ +package com.cmvr.vi.model.vo; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import javax.validation.constraints.NotEmpty; + +@Data +@ApiModel("查询单次对话语料/唤醒语料VO") +public class ViCorpusVO { + + @ApiModelProperty("语料名称") + private String corpusName; + + @ApiModelProperty("音色") + private String voiceType; + + @ApiModelProperty("方言") + private String dialect; + + @ApiModelProperty("语音对应的文本") + private String textContent; + + @ApiModelProperty(value = "状态", notes = "0=正常,1=停用") + private String status; + + @NotEmpty(message = "语料类型不能为空") + @ApiModelProperty(value = "语料类型", notes = "WAKEUP 唤醒,TEST 单次对话测试") + private String type; +} diff --git a/cmvr-iot-vi/src/main/java/com/cmvr/vi/service/IViCorpusService.java b/cmvr-iot-vi/src/main/java/com/cmvr/vi/service/IViCorpusService.java index cf76bf9..98e6fb1 100644 --- a/cmvr-iot-vi/src/main/java/com/cmvr/vi/service/IViCorpusService.java +++ b/cmvr-iot-vi/src/main/java/com/cmvr/vi/service/IViCorpusService.java @@ -2,6 +2,8 @@ package com.cmvr.vi.service; import com.baomidou.mybatisplus.extension.service.IService; import com.cmvr.vi.model.domain.ViCorpus; +import com.cmvr.vi.model.vo.ViContinuousCorpusVO; +import com.cmvr.vi.model.vo.ViCorpusVO; import java.util.List; @@ -22,10 +24,18 @@ public interface IViCorpusService extends IService { /** * 查询语料库列表 * + * @param viCorpusVO 语料库 + * @return 语料库集合 + */ + public List selectViCorpusList(ViCorpusVO viCorpusVO); + + /** + * 查询连续对话列表 + * * @param viCorpus 语料库 * @return 语料库集合 */ - public List selectViCorpusList(ViCorpus viCorpus); + public List selectContinuousList(ViCorpus viCorpus); /** * 新增语料库 @@ -35,6 +45,14 @@ public interface IViCorpusService extends IService { */ public int insertViCorpus(ViCorpus viCorpus); + /** + * 批量新增语料库 + * + * @param viCorpus 语料库 + * @return 结果 + */ + public boolean insertOrUpdateBatch(List viCorpus); + /** * 修改语料库 * diff --git a/cmvr-iot-vi/src/main/java/com/cmvr/vi/service/impl/IViCorpusServiceImpl.java b/cmvr-iot-vi/src/main/java/com/cmvr/vi/service/impl/IViCorpusServiceImpl.java index 3726bdb..5600e6a 100644 --- a/cmvr-iot-vi/src/main/java/com/cmvr/vi/service/impl/IViCorpusServiceImpl.java +++ b/cmvr-iot-vi/src/main/java/com/cmvr/vi/service/impl/IViCorpusServiceImpl.java @@ -1,11 +1,16 @@ package com.cmvr.vi.service.impl; +import cn.hutool.core.util.ObjUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.cmvr.vi.enums.ViCorpusEnum; import com.cmvr.vi.mapper.ViCorpusMapper; import com.cmvr.vi.model.domain.ViCorpus; +import com.cmvr.vi.model.vo.ViContinuousCorpusVO; +import com.cmvr.vi.model.vo.ViCorpusVO; import com.cmvr.vi.service.IViCorpusService; +import com.github.yulichang.wrapper.MPJLambdaWrapper; import org.springframework.stereotype.Service; import java.util.Arrays; @@ -19,58 +24,85 @@ import java.util.List; @Service public class IViCorpusServiceImpl extends ServiceImpl implements IViCorpusService { - /** - * 查询语料库 - * - * @param corpusId 语料库主键 - * @return 语料库 - */ @Override public ViCorpus selectViCorpusByCorpusId(Long corpusId) { return this.getById(corpusId); } - /** - * 查询语料库列表 - * - * @param viCorpus 语料库 - * @return 语料库 - */ @Override - public List selectViCorpusList(ViCorpus viCorpus) { + public List selectViCorpusList(ViCorpusVO viCorpusVO) { LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); - wrapper.eq(ViCorpus::getType, viCorpus.getType()); + + // 如果是唤醒语料 + if (ViCorpusEnum.WAKEUP.name().equalsIgnoreCase(viCorpusVO.getType())) { + wrapper.eq(ViCorpus::getType, ViCorpusEnum.WAKEUP); + } + + // 如果是单次对话 + if (ViCorpusEnum.TEST.name().equalsIgnoreCase(viCorpusVO.getType())) { + wrapper.eq(ViCorpus::getType, ViCorpusEnum.TEST) + .eq(ViCorpus::getContinuous, 1); // 只查单次 + } + + // 公共条件 + wrapper.like(ObjUtil.isNotEmpty(viCorpusVO.getVoiceType()), ViCorpus::getVoiceType, viCorpusVO.getVoiceType()) + .like(ObjUtil.isNotEmpty(viCorpusVO.getDialect()), ViCorpus::getDialect, viCorpusVO.getDialect()) + .like(ObjUtil.isNotEmpty(viCorpusVO.getCorpusName()), ViCorpus::getCorpusName, viCorpusVO.getCorpusName()) + .like(ObjUtil.isNotEmpty(viCorpusVO.getTextContent()), ViCorpus::getTextContent, viCorpusVO.getTextContent()) + .eq(ObjUtil.isNotEmpty(viCorpusVO.getStatus()), ViCorpus::getStatus, viCorpusVO.getStatus()) + .orderByAsc(ViCorpus::getSortOrder) + .orderByDesc(ViCorpus::getUpdateTime); + return this.list(wrapper); } - /** - * 新增语料库 - * - * @param viCorpus 语料库 - * @return 结果 - */ + + @Override + public List selectContinuousList(ViCorpus viCorpus) { + // 先查父级 + MPJLambdaWrapper wrapper = new MPJLambdaWrapper<>(); + wrapper.select(ViCorpus::getCorpusName) + .select(ViCorpus::getVoiceType) + .select(ViCorpus::getDialect) + .eq(ViCorpus::getType, ViCorpusEnum.TEST) + .eq(ViCorpus::getContinuous, 0) + .like(ObjUtil.isNotEmpty(viCorpus.getVoiceType()), ViCorpus::getVoiceType, viCorpus.getVoiceType()) + .like(ObjUtil.isNotEmpty(viCorpus.getDialect()), ViCorpus::getDialect, viCorpus.getDialect()) + .like(ObjUtil.isNotEmpty(viCorpus.getCorpusName()), ViCorpus::getCorpusName, viCorpus.getCorpusName()) + .groupBy(ViCorpus::getCorpusName, ViCorpus::getVoiceType, ViCorpus::getDialect); + + List list = this.baseMapper.selectJoinList(ViContinuousCorpusVO.class, wrapper); + + // 再查 children + for (ViContinuousCorpusVO vo : list) { + List children = this.list(Wrappers.lambdaQuery(ViCorpus.class) + .eq(ViCorpus::getCorpusName, vo.getCorpusName()) + .eq(ViCorpus::getVoiceType, vo.getVoiceType()) + .eq(ViCorpus::getDialect, vo.getDialect()) + .eq(ViCorpus::getContinuous, 0) + .eq(ViCorpus::getType, ViCorpusEnum.TEST) + .orderByAsc(ViCorpus::getSortOrder)); + vo.setChildren(children); + } + + return list; + } + @Override public int insertViCorpus(ViCorpus viCorpus) { return this.baseMapper.insert(viCorpus); } - /** - * 修改语料库 - * - * @param viCorpus 语料库 - * @return 结果 - */ + @Override + public boolean insertOrUpdateBatch(List data) { + return this.saveOrUpdateBatch(data); + } + @Override public int updateViCorpus(ViCorpus viCorpus) { return this.baseMapper.updateById(viCorpus); } - /** - * 批量删除语料库 - * - * @param corpusIds 需要删除的语料库主键 - * @return 结果 - */ @Override public int deleteViCorpusByCorpusIds(Long[] corpusIds) { return this.baseMapper.deleteBatchIds(Arrays.asList(corpusIds));