ard-admin/src/main/java/com/ard/web/controller/work/ArdFlightTaskController.java
New file @@ -0,0 +1,78 @@ package com.ard.web.controller.work; import com.ard.common.core.controller.BaseController; import com.ard.common.core.page.TableDataInfo; import com.ard.work.domain.dto.*; import com.ard.work.domain.entity.ArdFlightTask; import com.ard.work.domain.entity.ArdFlightTaskPart; import com.ard.work.domain.entity.ArdMediaPic; import com.ard.work.domain.entity.ArdMediaVideo; import com.ard.work.service.IArdFlightTaskPartService; import com.ard.work.service.IArdFlightTaskService; import com.ard.work.service.IArdMediaService; import com.ard.work.service.ITyService; import com.github.xiaoymin.knife4j.annotations.ApiOperationSupport; import com.github.xiaoymin.knife4j.annotations.ApiSort; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import java.util.List; /** * 飞行架次管理接口 * ArdFlightController * * @author liusuyi * @date 2024年10月21日13:52 **/ @Api(tags = "飞行架次管理接口") @ApiSort(value = 2) @RequestMapping("/uav/base") @RestController public class ArdFlightTaskController extends BaseController { @Resource private IArdFlightTaskService ardFlightTaskService; @Resource private IArdFlightTaskPartService ardFlightTaskPartService; @Resource private IArdMediaService ardMediaService; @ApiOperation("获取飞行架次") @ApiOperationSupport(order = 1) @GetMapping("/getFlightTask") public TableDataInfo getFlightTask(ArdFlightTask ardFlightTask) { startPage(); List<ArdFlightTask> list = ardFlightTaskService.selectArdFlightTask(ardFlightTask); return getDataTable(list); } @ApiOperation("获取架次飞行轨迹") @ApiOperationSupport(order = 2) @GetMapping("/getCompleteFlightData") public TableDataInfo getCompleteFlightData(ArdFlightTaskPart ardFlightTaskPart) { List<UavFlightStateDTO> completeFlightData = ardFlightTaskPartService.getCompleteFlightData(ardFlightTaskPart); return getDataTable(completeFlightData); } @ApiOperation("获取架次图片数据") @ApiOperationSupport(order = 3) @GetMapping("/getMediaPicList") public TableDataInfo getMediaPicList(ArdMediaPic ardMedia) { startPage(); List<ArdMediaPic> list = ardMediaService.selectArdMediaPicList(ardMedia); return getDataTable(list); } @ApiOperation("获取架次录像数据") @ApiOperationSupport(order = 4) @GetMapping("/getMediaVideoList") public TableDataInfo getMediaVideoList(ArdMediaVideo ardMedia) { startPage(); List<ArdMediaVideo> list = ardMediaService.selectArdMediaVideoList(ardMedia); return getDataTable(list); } } ard-admin/src/main/java/com/ard/web/controller/work/UavBaseController.java
@@ -5,9 +5,7 @@ import com.ard.common.core.page.TableDataInfo; import com.ard.work.domain.dto.*; import com.ard.work.domain.dto.telemetry.TelemetryData; import com.ard.work.domain.entity.ArdFlightTask; import com.ard.work.domain.entity.ArdFlightTaskPart; import com.ard.work.domain.entity.ArdMedia; import com.ard.work.domain.entity.*; import com.ard.work.domain.receiver.*; import com.ard.work.service.IArdFlightTaskPartService; import com.ard.work.service.IArdFlightTaskService; @@ -70,32 +68,6 @@ public AjaxResult getBoxHistory(@RequestBody BoxHistoryDTO boxHistoryDTO) { List<BoxHistoryReceiver> boxHistory = tyService.getBoxHistory(boxHistoryDTO); return AjaxResult.success(boxHistory); } @ApiOperation("获取飞行架次") @ApiOperationSupport(order = 4) @GetMapping("/getFlightTask") public TableDataInfo getFlightTask(ArdFlightTask ardFlightTask) { startPage(); List<ArdFlightTask> list = ardFlightTaskService.selectArdFlightTask(ardFlightTask); return getDataTable(list); } @ApiOperation("获取架次飞行轨迹") @ApiOperationSupport(order = 4) @GetMapping("/getCompleteFlightData") public TableDataInfo getCompleteFlightData(ArdFlightTaskPart ardFlightTaskPart) { List<UavFlightStateDTO> completeFlightData = ardFlightTaskPartService.getCompleteFlightData(ardFlightTaskPart); return getDataTable(completeFlightData); } @ApiOperation("获取媒体数据") @ApiOperationSupport(order = 4) @GetMapping("/getMediaList") public TableDataInfo getMediaList(ArdMedia ardMedia) { startPage(); List<ArdMedia> list = ardMediaService.selectArdMediaList(ardMedia); return getDataTable(list); } @ApiOperation("获取任务照片") ard-work/src/main/java/com/ard/work/domain/entity/ArdMedia.java
File was deleted ard-work/src/main/java/com/ard/work/domain/entity/ArdMediaPic.java
New file @@ -0,0 +1,26 @@ package com.ard.work.domain.entity; import com.ard.common.core.domain.BaseEntity; import com.fasterxml.jackson.annotation.JsonFormat; import lombok.*; import java.util.Date; @EqualsAndHashCode(callSuper = true) @Data @Builder @AllArgsConstructor @NoArgsConstructor public class ArdMediaPic extends BaseEntity { private Long id; private Integer taskId; private Long fileSize; private String fileMd5; private String mediaUrl; private String sourceUrl; private Double longitude; private Double latitude; private Double height; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date captureTime; } ard-work/src/main/java/com/ard/work/domain/entity/ArdMediaVideo.java
New file @@ -0,0 +1,28 @@ package com.ard.work.domain.entity; import com.ard.common.core.domain.BaseEntity; import com.fasterxml.jackson.annotation.JsonFormat; import lombok.*; import java.util.Date; @EqualsAndHashCode(callSuper = true) @Data @Builder @AllArgsConstructor @NoArgsConstructor public class ArdMediaVideo extends BaseEntity { private Long id; private String name; private Long fileSize; private String mediaUrl; private String sourceUrl; private String thumbnailUrl; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date startTime; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date endTime; private Integer spanSeconds; private Integer taskId; } ard-work/src/main/java/com/ard/work/mapper/ArdMediaMapper.java
File was deleted ard-work/src/main/java/com/ard/work/mapper/ArdMediaPicMapper.java
New file @@ -0,0 +1,41 @@ package com.ard.work.mapper; import com.ard.work.domain.entity.ArdMediaPic; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import java.util.List; @Mapper public interface ArdMediaPicMapper { /** * 查询图片列表(条件查询) */ List<ArdMediaPic> selectPicList(ArdMediaPic query); /** * 根据任务ID查询图片 */ List<ArdMediaPic> selectPicByTaskId(@Param("taskId") Integer taskId); /** * 新增图片 */ int insertPic(ArdMediaPic pic); /** * 批量新增图片 */ int insertPicList(@Param("list") List<ArdMediaPic> list); /** * 根据ID删除图片 */ int deletePicById(@Param("id") String id); /** * 根据任务ID删除图片 */ int deletePicByTaskId(@Param("taskId") Integer taskId); } ard-work/src/main/java/com/ard/work/mapper/ArdMediaVideoMapper.java
New file @@ -0,0 +1,36 @@ package com.ard.work.mapper; import com.ard.work.domain.entity.ArdMediaVideo; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import java.util.List; @Mapper public interface ArdMediaVideoMapper { /** * 查询视频列表(条件查询) */ List<ArdMediaVideo> selectArdMediaVideoList(ArdMediaVideo query); /** * 根据任务ID查询视频 */ List<ArdMediaVideo> selectArdMediaVideoByTaskId(@Param("taskId") Integer taskId); /** * 新增视频 */ int insertArdMediaVideo(ArdMediaVideo video); /** * 根据ID删除视频 */ int deleteArdMediaVideoById(@Param("id") String id); /** * 根据任务ID删除视频 */ int deleteArdMediaVideoByTaskId(@Param("taskId") Integer taskId); } ard-work/src/main/java/com/ard/work/service/IArdMediaService.java
@@ -1,7 +1,8 @@ package com.ard.work.service; import com.ard.work.domain.entity.ArdMedia; import com.ard.work.domain.entity.ArdFlightTask; import com.ard.work.domain.entity.ArdMediaPic; import com.ard.work.domain.entity.ArdMediaVideo; import java.util.List; /** @@ -10,11 +11,12 @@ * Date: 2025年11月18日13:15 **/ public interface IArdMediaService { /** * 查询媒体数据列表 * * @param ardMedia 媒体数据 * @return 媒体数据集合 */ public List<ArdMedia> selectArdMediaList(ArdMedia ardMedia); List<ArdMediaPic> selectArdMediaPicList(ArdMediaPic ardMediaPic); List<ArdMediaVideo> selectArdMediaVideoList(ArdMediaVideo ardMediaVideo); void insertPhotoToDatabase(ArdFlightTask flightTask, String serverId, String boxSn); void insertVideoToDatabase(ArdFlightTask flightTask, String serverId, String boxSn); void updatePhotoToDatabase(ArdFlightTask flightTask, String serverId, String boxSn); void updateVideoToDatabase(ArdFlightTask flightTask, String serverId, String boxSn); } ard-work/src/main/java/com/ard/work/service/impl/ArdFlightTaskServiceImpl.java
@@ -4,49 +4,23 @@ import com.ard.common.core.domain.entity.SysUser; import com.ard.common.core.redis.RedisCache; import com.ard.common.utils.DateUtils; import com.ard.common.utils.file.MimeTypeUtils; import com.ard.common.utils.uuid.IdUtils; import com.ard.oss.service.impl.OssServiceContext; import com.ard.work.component.forest.TyClient; import com.ard.work.domain.dto.BoxHistoryDTO; import com.ard.work.domain.dto.BoxPhotosDTO; import com.ard.work.domain.dto.BoxVideosDTO; import com.ard.work.domain.entity.ArdFlightTask; import com.ard.work.domain.entity.ArdMedia; import com.ard.work.domain.receiver.BoxHistoryReceiver; import com.ard.work.domain.receiver.BoxPhotosReceiver; import com.ard.work.domain.receiver.BoxVideosReceiver; import com.ard.work.domain.receiver.TyHttpCommonReceiver; import com.ard.work.mapper.ArdFlightTaskMapper; import com.ard.work.mapper.ArdMediaMapper; import com.ard.work.service.IArdFlightTaskService; import com.ard.work.service.IArdMediaService; import com.ard.work.service.IArdServerInfoService; import com.ard.work.service.control.UavControlService; import com.ard.work.util.osSelect; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Service; import javax.annotation.Resource; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLConnection; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; @Slf4j @Service @@ -55,19 +29,16 @@ @Resource private ArdFlightTaskMapper ardFlightTaskMapper; @Resource private ArdMediaMapper ardMediaMapper; @Resource private IArdServerInfoService ardServerInfoService; @Resource private TyClient tyClient; @Resource private OssServiceContext ossServiceContext; @Resource private ThreadPoolTaskExecutor threadPoolTaskExecutor; // 使用线程池执行异步任务 @Resource private UavControlService uavControlService; @Autowired @Resource private IArdMediaService ardMediaService; @Resource private RedisCache redisCache; @Resource private TyClient tyClient; /** * 查询飞行架次列表 * @@ -97,6 +68,12 @@ private Date getLastSyncTimeForServer(String serverId) { Date maxStartTime = ardFlightTaskMapper.findMaxStartTimeByServerId(serverId); return maxStartTime != null ? maxStartTime : new Date(0); // 如果没有数据,返回 Unix 纪元时间作为起点 } // 调用接口获取飞行历史数据 private List<BoxHistoryReceiver> getBoxHistory(String serverId, BoxHistoryDTO boxHistoryDTO) { TyHttpCommonReceiver<List<BoxHistoryReceiver>> boxHistory = tyClient.getBoxHistory(serverId, boxHistoryDTO); return boxHistory.getContent(); } // 根据上次同步时间同步飞行历史 @@ -158,24 +135,23 @@ // 插入新记录 int rows = ardFlightTaskMapper.insertArdFlightTask(flightTask); if (rows > 0) { // 删除旧的媒体记录,防止重复插入 ardMediaMapper.deleteArdMediaByTaskId(flightTask.getTaskId()); // 插入视频 insertVideoToDatabase(flightTask, serverId, boxHistoryReceiver.getBoxSn()); ardMediaService.insertVideoToDatabase(flightTask, serverId, boxHistoryReceiver.getBoxSn()); // 插入图片 insertPhotoToDatabase(flightTask, serverId, boxHistoryReceiver.getBoxSn()); ardMediaService.insertPhotoToDatabase(flightTask, serverId, boxHistoryReceiver.getBoxSn()); } } else { // 如果存在,则更新记录 log.debug("Updating task {}", boxHistoryReceiver.getTaskId()); updateFlightTask(ardFlightTask, boxHistoryReceiver); // 增量更新视频 updateVideoToDatabase(ardFlightTask, serverId, boxHistoryReceiver.getBoxSn()); ardMediaService.updateVideoToDatabase(ardFlightTask, serverId, boxHistoryReceiver.getBoxSn()); // 增量更新图片 updatePhotoToDatabase(ardFlightTask, serverId, boxHistoryReceiver.getBoxSn()); ardMediaService.updatePhotoToDatabase(ardFlightTask, serverId, boxHistoryReceiver.getBoxSn()); } } private void updateFlightTask(ArdFlightTask local, BoxHistoryReceiver remote) { boolean needUpdate = false; @@ -208,342 +184,5 @@ } } // 调用接口获取飞行历史数据 private List<BoxHistoryReceiver> getBoxHistory(String serverId, BoxHistoryDTO boxHistoryDTO) { TyHttpCommonReceiver<List<BoxHistoryReceiver>> boxHistory = tyClient.getBoxHistory(serverId, boxHistoryDTO); return boxHistory.getContent(); } // 调用接口获取架次图片 private List<BoxPhotosReceiver> getBoxPhotos(String serverId, BoxPhotosDTO boxPhotosDTO) { TyHttpCommonReceiver<List<BoxPhotosReceiver>> boxPhotos = tyClient.getBoxPhotos(serverId, boxPhotosDTO); return boxPhotos.getContent(); } // 调用接口获取架次视频 private List<BoxVideosReceiver> getBoxVideos(String serverId, BoxVideosDTO boxVideosDTO) { TyHttpCommonReceiver<List<BoxVideosReceiver>> boxVideos = tyClient.getBoxVideos(serverId, boxVideosDTO); return boxVideos.getContent(); } private void insertPhotoToDatabase(ArdFlightTask flightTask, String serverId, String boxSn) { // 查询图片 BoxPhotosDTO boxPhotosDTO = new BoxPhotosDTO(); boxPhotosDTO.setTaskId(flightTask.getTaskId()); boxPhotosDTO.setBoxSn(boxSn); boxPhotosDTO.setPageIndex(1); boxPhotosDTO.setPageSize(100); List<BoxPhotosReceiver> photoList = getBoxPhotos(serverId, boxPhotosDTO); if (photoList != null && !photoList.isEmpty()) { addPhotos(flightTask, photoList); } } private void insertVideoToDatabase(ArdFlightTask flightTask, String serverId, String boxSn) { BoxVideosDTO boxVideosDTO = new BoxVideosDTO(); boxVideosDTO.setTaskId(flightTask.getTaskId()); boxVideosDTO.setBoxSn(boxSn); boxVideosDTO.setPageIndex(1); boxVideosDTO.setPageSize(100); List<BoxVideosReceiver> videoList = getBoxVideos(serverId, boxVideosDTO); if (videoList != null && !videoList.isEmpty()) { addVideos(flightTask, videoList); } } private void updatePhotoToDatabase(ArdFlightTask flightTask, String serverId, String boxSn) { // 获取本地数据库中已存在的图片记录(类型为 0 表示图片) List<ArdMedia> existingPhotos = ardMediaMapper.selectArdMediaByTaskIdAndType(flightTask.getTaskId(), 0); int count = existingPhotos.size(); //获取本地库图片数量 // 查询图片 BoxPhotosDTO boxPhotosDTO = BoxPhotosDTO.builder() .taskId(flightTask.getTaskId()) .boxSn(boxSn) .pageIndex(1) .pageSize(500) .build(); TyHttpCommonReceiver<List<BoxPhotosReceiver>> boxPhotos = tyClient.getBoxPhotos(serverId, boxPhotosDTO); List<BoxPhotosReceiver> photoList = boxPhotos.getContent(); int size = photoList.size(); if (size > count) { log.info("任务【{}】图片数量不一致,需要更新数据库", flightTask.getTaskId()); // 需要插入新的视频 // 提取现有的图片路径(根据 imgUrl 来判断是否重复) List<String> existingPhotoUrls = existingPhotos.stream() .map(ArdMedia::getSourceUrl) // 提取 ArdMedia 中的图片 URL .collect(Collectors.toList()); // 筛选出 photoList 中没有出现在 existingPhotos 中的新图片 List<BoxPhotosReceiver> newPhotos = photoList.stream() .filter(photo -> !existingPhotoUrls.contains(photo.getImgUrl())) // 过滤出新图片 .collect(Collectors.toList()); if (!newPhotos.isEmpty()) { // 插入新的视频记录 addPhotos(flightTask, newPhotos); // 更新任务的视频数量 flightTask.setPhotoCount(flightTask.getPhotoCount() + newPhotos.size()); ardFlightTaskMapper.updateArdFlightTask(flightTask); } } } private void updateVideoToDatabase(ArdFlightTask flightTask, String serverId, String boxSn) { // 获取本地数据库中已存在的图片记录(类型为 0 表示图片) List<ArdMedia> existingVideos = ardMediaMapper.selectArdMediaByTaskIdAndType(flightTask.getTaskId(), 1); int count = existingVideos.size(); //获取本地库视频数量 // 查询图片 BoxVideosDTO boxVideosDTO = BoxVideosDTO.builder() .taskId(flightTask.getTaskId()) .boxSn(boxSn) .pageIndex(1) .pageSize(500) .build(); TyHttpCommonReceiver<List<BoxVideosReceiver>> boxVideos = tyClient.getBoxVideos(serverId, boxVideosDTO); List<BoxVideosReceiver> videoList = boxVideos.getContent(); int size = videoList.size(); if (size > count) { log.info("任务【{}】视频数量不一致,需要更新数据库", flightTask.getTaskId()); // 需要插入新的视频 // 提取现有的图片路径(根据 imgUrl 来判断是否重复) List<String> existingVideoUrls = existingVideos.stream() .map(ArdMedia::getSourceUrl) // 提取 ArdMedia 中的图片 URL .collect(Collectors.toList()); // 筛选出 photoList 中没有出现在 existingPhotos 中的新图片 List<BoxVideosReceiver> newVideos = videoList.stream() .filter(photo -> !existingVideoUrls.contains(photo.getImgUrl())) // 过滤出新视频 .collect(Collectors.toList()); if (!newVideos.isEmpty()) { // 插入新的视频记录 addVideos(flightTask, newVideos); // 更新任务的视频数量 flightTask.setVideoCount(flightTask.getPhotoCount() + newVideos.size()); ardFlightTaskMapper.updateArdFlightTask(flightTask); } } } private void addPhotos(ArdFlightTask flightTask, List<BoxPhotosReceiver> photoList) { String boxSn = flightTask.getBoxSn(); for (BoxPhotosReceiver photo : photoList) { ArdMedia ardMedia = ArdMedia.builder() .id(IdUtils.simpleUUID()) .taskId(flightTask.getTaskId()) .mediaType(0) // 0表示图片 .fileSize(photo.getFileSize()) .fileMd5(photo.getFileMd5()) .sourceUrl(photo.getImgUrl()) .photoId(photo.getPhotoId()) .longitude(photo.getLng()) .latitude(photo.getLat()) .height(photo.getHeight()) .captureTime(DateUtils.dateTime(DateUtils.YYYY_MM_DD_HH_MM_SS, photo.getTimeStamp())) .build(); // 异步上传文件到OSS,并在上传完成后插入数据库 CompletableFuture.supplyAsync(() -> { try (InputStream inputStream = openUrlInputStream(photo.getImgUrl(), 5000, 10000)) { if (inputStream != null) { return ossServiceContext.upload( "pic", boxSn + "/" + ardMedia.getId() + ".jpg", inputStream, MimeTypeUtils.IMAGE_JPG ); } } catch (Exception e) { e.printStackTrace(); } return null; }, threadPoolTaskExecutor) .thenAccept(url -> { ardMedia.setMediaUrl(url); ardMedia.setCreateTime(new Date()); ardMediaMapper.insertArdMedia(ardMedia); }); } } private void addVideos(ArdFlightTask flightTask, List<BoxVideosReceiver> videoList) { String boxSn = flightTask.getBoxSn(); for (BoxVideosReceiver video : videoList) { ArdMedia ardMedia = ArdMedia.builder() .id(IdUtils.simpleUUID()) .taskId(flightTask.getTaskId()) .mediaType(1) // 1表示视频 .fileSize(getFileSizeByUrl(video.getImgUrl())) .sourceUrl(video.getImgUrl()) .captureTime(DateUtils.parseDate(video.getStartTime())) .build(); // 异步上传文件到OSS,并在上传完成后插入数据库 // ---- 1. 视频上传(失败不抛异常,返回 null) ---- CompletableFuture<String> videoFuture = CompletableFuture.supplyAsync(() -> { try (InputStream inputStream = openUrlInputStream(ardMedia.getSourceUrl(), 5000, 10000)) { if (inputStream != null) { return ossServiceContext.upload( "record", boxSn + "/" + ardMedia.getId() + ".mp4", inputStream, MimeTypeUtils.VIDEO_MP4 ); } } catch (Exception e) { e.printStackTrace(); } return null; }, threadPoolTaskExecutor); // ---- 2. 缩略图上传(失败不抛异常,返回 null) ---- CompletableFuture<String> thumbFuture = CompletableFuture.supplyAsync(() -> { try { try (ByteArrayInputStream thumbStream = generateThumbnail(ardMedia.getSourceUrl(), 1, 200)) { if (thumbStream != null) { return ossServiceContext.upload( "record", boxSn + "/" + ardMedia.getId() + ".jpg", thumbStream, MimeTypeUtils.IMAGE_JPEG ); } } } catch (Exception e) { e.printStackTrace(); } return null; }, threadPoolTaskExecutor); // 两个完成后写数据库(任意失败不阻断流程) videoFuture.thenCombine(thumbFuture, (vUrl, tUrl) -> { ardMedia.setMediaUrl(vUrl); ardMedia.setThumbnailUrl(tUrl); ardMedia.setCreateTime(new Date()); return ardMedia; }).thenAcceptAsync(ardMediaMapper::insertArdMedia, threadPoolTaskExecutor); } } // 获取文件大小 private long getFileSizeByUrl(String fileUrl) { try { URL url = new URL(fileUrl); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("HEAD"); conn.connect(); return conn.getContentLengthLong(); // 返回字节大小 } catch (Exception e) { return -1; } } // 生成视频缩略图 public ByteArrayInputStream generateThumbnail(String videoFile, int skipSeconds, int targetWidth) { String ffmpegPath = ""; if (osSelect.isWindows()) { ffmpegPath = "ffmpeg"; } else if (osSelect.isLinux()) { ffmpegPath = "/usr/local/ffmpeg/bin/ffmpeg"; } try { List<String> command = new ArrayList<>(); command.add(ffmpegPath); command.add("-timeout"); command.add("3000000"); command.add("-rw_timeout"); command.add("3000000"); command.add("-ss"); command.add(String.format("00:00:%02d", skipSeconds)); command.add("-i"); command.add(videoFile); command.add("-vframes"); command.add("1"); // 等比缩放 command.add("-vf"); command.add("scale=" + targetWidth + ":-1"); // **确保输出 JPEG** command.add("-f"); command.add("mjpeg"); // 关键点:输出格式固定为 JPEG command.add("-vcodec"); command.add("mjpeg"); // 输出到管道 command.add("pipe:1"); // ⭐ 打印最终命令(可复制) log.info("ffmpeg command: {}", String.join(" ", command)); ProcessBuilder pb = new ProcessBuilder(command); pb.redirectErrorStream(false); Process process = pb.start(); // 读取 FFmpeg 输出(JPEG 数据)到内存 ByteArrayOutputStream baos = new ByteArrayOutputStream(); try (InputStream is = process.getInputStream()) { byte[] buffer = new byte[8192]; int len; while ((len = is.read(buffer)) != -1) { baos.write(buffer, 0, len); } } int exitCode = process.waitFor(); if (exitCode != 0) { throw new RuntimeException("FFmpeg 执行失败,退出码: " + exitCode); } return new ByteArrayInputStream(baos.toByteArray()); } catch (IOException | InterruptedException e) { throw new RuntimeException("FFmpeg 生成缩略图失败", e); } } /** * 将 URL 转成 InputStream * * @param fileUrl 文件 URL * @param connectTimeoutMillis 连接超时(毫秒) * @param readTimeoutMillis 读取超时(毫秒) * @return InputStream,如果失败返回 null */ public InputStream openUrlInputStream(String fileUrl, int connectTimeoutMillis, int readTimeoutMillis) { try { URL url = new URL(fileUrl); // 支持 HTTPS 信任所有证书(如果需要访问自签名证书) if ("https".equalsIgnoreCase(url.getProtocol())) { TrustManager[] trustAllCerts = new TrustManager[]{ new X509TrustManager() { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) { } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) { } @Override public X509Certificate[] getAcceptedIssuers() { return null; } } }; SSLContext sc = SSLContext.getInstance("SSL"); sc.init(null, trustAllCerts, new java.security.SecureRandom()); HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); HttpsURLConnection.setDefaultHostnameVerifier((hostname, session) -> true); } URLConnection connection = url.openConnection(); connection.setConnectTimeout(connectTimeoutMillis); connection.setReadTimeout(readTimeoutMillis); return connection.getInputStream(); } catch (Exception e) { e.printStackTrace(); return null; } } } ard-work/src/main/java/com/ard/work/service/impl/ArdMediaServiceImpl.java
@@ -1,31 +1,493 @@ package com.ard.work.service.impl; import com.ard.work.domain.entity.ArdMedia; import com.ard.work.mapper.ArdMediaMapper; import com.ard.common.utils.DateUtils; import com.ard.common.utils.file.MimeTypeUtils; import com.ard.oss.service.impl.OssServiceContext; import com.ard.work.component.forest.TyClient; import com.ard.work.domain.dto.BoxPhotosDTO; import com.ard.work.domain.dto.BoxVideosDTO; import com.ard.work.domain.entity.ArdFlightTask; import com.ard.work.domain.entity.ArdMediaPic; import com.ard.work.domain.entity.ArdMediaVideo; import com.ard.work.domain.receiver.BoxPhotosReceiver; import com.ard.work.domain.receiver.BoxVideosReceiver; import com.ard.work.domain.receiver.TyHttpCommonReceiver; import com.ard.work.mapper.ArdFlightTaskMapper; import com.ard.work.mapper.ArdMediaPicMapper; import com.ard.work.mapper.ArdMediaVideoMapper; import com.ard.work.service.IArdMediaService; import com.ard.work.util.osSelect; import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Service; import javax.annotation.Resource; import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLConnection; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; /** * Description: * Author: lsy * Date: 2025年11月18日13:17 **/ @Slf4j @Service public class ArdMediaServiceImpl implements IArdMediaService { @Resource private ArdMediaMapper ardMediaMapper; private TyClient tyClient; @Resource private ArdMediaPicMapper ardMediaPicMapper; @Resource private ArdMediaVideoMapper ardMediaVideoMapper; @Resource private OssServiceContext ossServiceContext; @Resource private ArdFlightTaskMapper ardFlightTaskMapper; @Resource private ThreadPoolTaskExecutor threadPoolTaskExecutor; // 调用接口获取架次图片 private List<BoxPhotosReceiver> getBoxPhotos(String serverId, BoxPhotosDTO boxPhotosDTO) { TyHttpCommonReceiver<List<BoxPhotosReceiver>> boxPhotos = tyClient.getBoxPhotos(serverId, boxPhotosDTO); return boxPhotos.getContent(); } // 调用接口获取架次视频 private List<BoxVideosReceiver> getBoxVideos(String serverId, BoxVideosDTO boxVideosDTO) { TyHttpCommonReceiver<List<BoxVideosReceiver>> boxVideos = tyClient.getBoxVideos(serverId, boxVideosDTO); return boxVideos.getContent(); } @Override public List<ArdMediaPic> selectArdMediaPicList(ArdMediaPic ardMediaPic) { return ardMediaPicMapper.selectPicList(ardMediaPic); } @Override public List<ArdMediaVideo> selectArdMediaVideoList(ArdMediaVideo ardMediaVideo) { return ardMediaVideoMapper.selectArdMediaVideoList(ardMediaVideo); } @Override public void insertPhotoToDatabase(ArdFlightTask flightTask, String serverId, String boxSn) { log.warn("开始获取图片信息"); // 查询图片 BoxPhotosDTO boxPhotosDTO = new BoxPhotosDTO(); boxPhotosDTO.setTaskId(flightTask.getTaskId()); boxPhotosDTO.setBoxSn(boxSn); boxPhotosDTO.setPageIndex(1); boxPhotosDTO.setPageSize(100); List<BoxPhotosReceiver> photoList = getBoxPhotos(serverId, boxPhotosDTO); if (photoList != null && !photoList.isEmpty()) { addPhotos(flightTask, photoList); } } @Override public void insertVideoToDatabase(ArdFlightTask flightTask, String serverId, String boxSn) { log.warn("开始获取视频信息"); BoxVideosDTO boxVideosDTO = new BoxVideosDTO(); boxVideosDTO.setTaskId(flightTask.getTaskId()); boxVideosDTO.setBoxSn(boxSn); boxVideosDTO.setPageIndex(1); boxVideosDTO.setPageSize(100); List<BoxVideosReceiver> videoList = getBoxVideos(serverId, boxVideosDTO); if (videoList != null && !videoList.isEmpty()) { addVideos(flightTask, videoList); } } @Override public void updatePhotoToDatabase(ArdFlightTask flightTask, String serverId, String boxSn) { // 获取本地数据库中已存在的图片记录(类型为 0 表示图片) List<ArdMediaPic> existingPhotos = ardMediaPicMapper.selectPicByTaskId(flightTask.getTaskId()); int count = existingPhotos.size(); //获取本地库图片数量 // 查询图片 BoxPhotosDTO boxPhotosDTO = BoxPhotosDTO.builder() .taskId(flightTask.getTaskId()) .boxSn(boxSn) .pageIndex(1) .pageSize(500) .build(); TyHttpCommonReceiver<List<BoxPhotosReceiver>> boxPhotos = tyClient.getBoxPhotos(serverId, boxPhotosDTO); List<BoxPhotosReceiver> photoList = boxPhotos.getContent(); int size = photoList.size(); if (size > count) { log.info("任务【{}】图片数量不一致,需要更新数据库", flightTask.getTaskId()); // 需要插入新的视频 // 提取现有的图片路径(根据 imgUrl 来判断是否重复) List<String> existingPhotoUrls = existingPhotos.stream() .map(ArdMediaPic::getSourceUrl) // 提取 ArdMediaPic 中的图片 URL .collect(Collectors.toList()); // 筛选出 photoList 中没有出现在 existingPhotos 中的新图片 List<BoxPhotosReceiver> newPhotos = photoList.stream() .filter(photo -> !existingPhotoUrls.contains(photo.getImgUrl())) // 过滤出新图片 .collect(Collectors.toList()); if (!newPhotos.isEmpty()) { // 插入新的视频记录 addPhotos(flightTask, newPhotos); // 更新任务的视频数量 flightTask.setPhotoCount(flightTask.getPhotoCount() + newPhotos.size()); ardFlightTaskMapper.updateArdFlightTask(flightTask); } } } @Override public void updateVideoToDatabase(ArdFlightTask flightTask, String serverId, String boxSn) { // 获取本地数据库中已存在的视频记录 List<ArdMediaVideo> existingVideos = ardMediaVideoMapper.selectArdMediaVideoByTaskId(flightTask.getTaskId()); int count = existingVideos.size(); //获取本地库视频数量 // 查询图片 BoxVideosDTO boxVideosDTO = BoxVideosDTO.builder() .taskId(flightTask.getTaskId()) .boxSn(boxSn) .pageIndex(1) .pageSize(500) .build(); TyHttpCommonReceiver<List<BoxVideosReceiver>> boxVideos = tyClient.getBoxVideos(serverId, boxVideosDTO); List<BoxVideosReceiver> videoList = boxVideos.getContent(); int size = videoList.size(); if (size > count) { log.info("任务【{}】视频数量不一致,需要更新数据库", flightTask.getTaskId()); // 需要插入新的视频 // 提取现有的图片路径(根据 imgUrl 来判断是否重复) List<String> existingVideoUrls = existingVideos.stream() .map(ArdMediaVideo::getSourceUrl) // 提取 ArdMedia 中的视频 URL .collect(Collectors.toList()); // 筛选出 photoList 中没有出现在 existingPhotos 中的新图片 List<BoxVideosReceiver> newVideos = videoList.stream() .filter(photo -> !existingVideoUrls.contains(photo.getImgUrl())) // 过滤出新视频 .collect(Collectors.toList()); if (!newVideos.isEmpty()) { // 插入新的视频记录 addVideos(flightTask, newVideos); // 更新任务的视频数量 flightTask.setVideoCount(flightTask.getVideoCount() + newVideos.size()); ardFlightTaskMapper.updateArdFlightTask(flightTask); } } } private void addPhotos1(ArdFlightTask flightTask, List<BoxPhotosReceiver> photoList) { log.warn("开始添加图片"); String boxSn = flightTask.getBoxSn(); for (BoxPhotosReceiver photo : photoList) { ArdMediaPic ardMedia = ArdMediaPic.builder() .id(photo.getPhotoId()) .taskId(flightTask.getTaskId()) .fileSize(photo.getFileSize()) .fileMd5(photo.getFileMd5()) .sourceUrl(photo.getImgUrl()) .longitude(photo.getLng()) .latitude(photo.getLat()) .height(photo.getHeight()) .captureTime(DateUtils.parseDate(photo.getTimeStamp())) .build(); String url = ""; try { InputStream inputStream = openUrlInputStream(photo.getImgUrl(), 5000, 10000); if (inputStream != null) { url = ossServiceContext.upload( "pic", boxSn + "/" + ardMedia.getId() + ".jpg", inputStream, MimeTypeUtils.IMAGE_JPG ); } } catch (Exception e) { e.printStackTrace(); } ardMedia.setMediaUrl(url); ardMedia.setCreateTime(new Date()); ardMediaPicMapper.insertPic(ardMedia); } } private void addPhotos(ArdFlightTask flightTask, List<BoxPhotosReceiver> photoList) { log.warn("开始添加图片"); String boxSn = flightTask.getBoxSn(); for (BoxPhotosReceiver photo : photoList) { CompletableFuture.runAsync(() -> { ArdMediaPic ardMedia = ArdMediaPic.builder() .id(photo.getPhotoId()) .taskId(flightTask.getTaskId()) .fileSize(photo.getFileSize()) .fileMd5(photo.getFileMd5()) .sourceUrl(photo.getImgUrl()) .longitude(photo.getLng()) .latitude(photo.getLat()) .height(photo.getHeight()) .captureTime(DateUtils.parseDate(photo.getTimeStamp())) .build(); String url = ""; try (InputStream inputStream = openUrlInputStream(photo.getImgUrl(), 5000, 10000)) { if (inputStream != null) { url = ossServiceContext.upload( "pic", boxSn + "/" + ardMedia.getId() + ".jpg", inputStream, MimeTypeUtils.IMAGE_JPG ); } } catch (Exception e) { log.error("图片上传失败 taskId={}, photoId={}", flightTask.getTaskId(), photo.getPhotoId(), e); } ardMedia.setMediaUrl(url); ardMedia.setCreateTime(new Date()); ardMediaPicMapper.insertPic(ardMedia); }, threadPoolTaskExecutor); // 使用自定义线程池 } } private void addVideos1(ArdFlightTask flightTask, List<BoxVideosReceiver> videoList) { log.warn("开始添加视频"); String boxSn = flightTask.getBoxSn(); for (BoxVideosReceiver video : videoList) { ArdMediaVideo ardMedia = ArdMediaVideo.builder() .id(video.getId()) .name(video.getName()) .taskId(flightTask.getTaskId()) .fileSize(getFileSizeByUrl(video.getImgUrl())) .sourceUrl(video.getImgUrl()) .startTime(DateUtils.parseDate(video.getStartTime())) .endTime(DateUtils.parseDate(video.getEndTime())) .spanSeconds(Integer.parseInt(video.getSpanSeconds())) .build(); // 视频上传 String videoUrl = null; try (InputStream videoStream = openUrlInputStream(ardMedia.getSourceUrl(), 5000, 10000)) { if (videoStream != null) { videoUrl = ossServiceContext.upload("record", boxSn + "/" + ardMedia.getId() + ".mp4", videoStream, MimeTypeUtils.VIDEO_MP4); } } catch (Exception e) { log.error("视频上传失败 taskId={}, videoId={}", flightTask.getTaskId(), video.getId(), e); } // 缩略图上传(失败不影响视频入库) String thumbUrl = null; try (ByteArrayInputStream thumbStream = generateThumbnail(ardMedia.getSourceUrl(), 1, 200)) { if (thumbStream != null) { thumbUrl = ossServiceContext.upload("record", boxSn + "/" + ardMedia.getId() + ".jpg", thumbStream, MimeTypeUtils.IMAGE_JPEG); } } catch (Exception e) { log.warn("缩略图生成或上传失败 taskId={}, videoId={}", flightTask.getTaskId(), video.getId(), e); } // 入库(即使缩略图为空也入库) ardMedia.setMediaUrl(videoUrl); ardMedia.setThumbnailUrl(thumbUrl); ardMedia.setCreateTime(new Date()); ardMediaVideoMapper.insertArdMediaVideo(ardMedia); } } private void addVideos(ArdFlightTask flightTask, List<BoxVideosReceiver> videoList) { String boxSn = flightTask.getBoxSn(); for (BoxVideosReceiver video : videoList) { CompletableFuture.runAsync(() -> { ArdMediaVideo ardMedia = ArdMediaVideo.builder() .id(video.getId()) .name(video.getName()) .taskId(flightTask.getTaskId()) .fileSize(getFileSizeByUrl(video.getImgUrl())) .sourceUrl(video.getImgUrl()) .startTime(DateUtils.parseDate(video.getStartTime())) .endTime(DateUtils.parseDate(video.getEndTime())) .spanSeconds(Integer.parseInt(video.getSpanSeconds())) .build(); // 视频上传 String videoUrl = null; try (InputStream videoStream = openUrlInputStream(ardMedia.getSourceUrl(), 5000, 10000)) { if (videoStream != null) { videoUrl = ossServiceContext.upload("record", boxSn + "/" + ardMedia.getId() + ".mp4", videoStream, MimeTypeUtils.VIDEO_MP4); } } catch (Exception e) { log.error("视频上传失败 taskId={}, videoId={}", flightTask.getTaskId(), video.getId(), e); } // 缩略图上传(失败不影响视频入库) String thumbUrl = null; try (ByteArrayInputStream thumbStream = generateThumbnail(ardMedia.getSourceUrl(), 1, 200)) { if (thumbStream != null) { thumbUrl = ossServiceContext.upload("record", boxSn + "/" + ardMedia.getId() + ".jpg", thumbStream, MimeTypeUtils.IMAGE_JPEG); } } catch (Exception e) { log.warn("缩略图生成或上传失败 taskId={}, videoId={}", flightTask.getTaskId(), video.getId(), e); } // 入库(即使缩略图为空也入库) ardMedia.setMediaUrl(videoUrl); ardMedia.setThumbnailUrl(thumbUrl); ardMedia.setCreateTime(new Date()); ardMediaVideoMapper.insertArdMediaVideo(ardMedia); }, threadPoolTaskExecutor); } } // 获取文件大小 private long getFileSizeByUrl(String fileUrl) { try { URL url = new URL(fileUrl); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("HEAD"); conn.connect(); return conn.getContentLengthLong(); // 返回字节大小 } catch (Exception e) { return -1; } } // 生成视频缩略图 public ByteArrayInputStream generateThumbnail(String videoFile, int skipSeconds, int targetWidth) { String ffmpegPath = ""; if (osSelect.isWindows()) { ffmpegPath = "ffmpeg"; } else if (osSelect.isLinux()) { ffmpegPath = "/usr/local/ffmpeg/bin/ffmpeg"; } try { List<String> command = new ArrayList<>(); command.add(ffmpegPath); command.add("-timeout"); command.add("3000000"); command.add("-rw_timeout"); command.add("3000000"); command.add("-ss"); command.add(String.format("00:00:%02d", skipSeconds)); command.add("-i"); command.add(videoFile); command.add("-vframes"); command.add("1"); // 等比缩放 command.add("-vf"); command.add("scale=" + targetWidth + ":-1"); // **确保输出 JPEG** command.add("-f"); command.add("mjpeg"); // 关键点:输出格式固定为 JPEG command.add("-vcodec"); command.add("mjpeg"); // 输出到管道 command.add("pipe:1"); // ⭐ 打印最终命令(可复制) log.info("ffmpeg command: {}", String.join(" ", command)); ProcessBuilder pb = new ProcessBuilder(command); pb.redirectErrorStream(false); Process process = pb.start(); // 读取 FFmpeg 输出(JPEG 数据)到内存 ByteArrayOutputStream baos = new ByteArrayOutputStream(); try (InputStream is = process.getInputStream()) { byte[] buffer = new byte[8192]; int len; while ((len = is.read(buffer)) != -1) { baos.write(buffer, 0, len); } } int exitCode = process.waitFor(); if (exitCode != 0) { throw new RuntimeException("FFmpeg 执行失败,退出码: " + exitCode); } return new ByteArrayInputStream(baos.toByteArray()); } catch (IOException | InterruptedException e) { throw new RuntimeException("FFmpeg 生成缩略图失败", e); } } /** * 查询媒体数据列表 * 将 URL 转成 InputStream * * @param ardMedia 媒体数据 * @return 媒体数据集合 * @param fileUrl 文件 URL * @param connectTimeoutMillis 连接超时(毫秒) * @param readTimeoutMillis 读取超时(毫秒) * @return InputStream,如果失败返回 null */ @Override public List<ArdMedia> selectArdMediaList(ArdMedia ardMedia) { return ardMediaMapper.selectArdMedia(ardMedia); public InputStream openUrlInputStream(String fileUrl, int connectTimeoutMillis, int readTimeoutMillis) { try { URL url = new URL(fileUrl); // 支持 HTTPS 信任所有证书(如果需要访问自签名证书) if ("https".equalsIgnoreCase(url.getProtocol())) { TrustManager[] trustAllCerts = new TrustManager[]{ new X509TrustManager() { @Override public void checkClientTrusted(X509Certificate[] chain, String authType) { } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) { } @Override public X509Certificate[] getAcceptedIssuers() { return null; } } }; SSLContext sc = SSLContext.getInstance("SSL"); sc.init(null, trustAllCerts, new java.security.SecureRandom()); HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); HttpsURLConnection.setDefaultHostnameVerifier((hostname, session) -> true); } URLConnection connection = url.openConnection(); connection.setConnectTimeout(connectTimeoutMillis); connection.setReadTimeout(readTimeoutMillis); return connection.getInputStream(); } catch (Exception e) { e.printStackTrace(); return null; } } } ard-work/src/main/resources/mapper/ArdMediaMapper.xml
File was deleted ard-work/src/main/resources/mapper/ArdMediaPicMapper.xml
New file @@ -0,0 +1,84 @@ <?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.ard.work.mapper.ArdMediaPicMapper"> <resultMap id="ArdMediaPicResult" type="ArdMediaPic"> <result property="id" column="id"/> <result property="fileSize" column="file_size"/> <result property="fileMd5" column="file_md5"/> <result property="mediaUrl" column="media_url"/> <result property="sourceUrl" column="source_url"/> <result property="longitude" column="longitude"/> <result property="latitude" column="latitude"/> <result property="height" column="height"/> <result property="taskId" column="task_id"/> <result property="captureTime" column="capture_time"/> <result property="createTime" column="create_time"/> <result property="updateTime" column="update_time"/> </resultMap> <!-- 查询图片列表 --> <select id="selectPicList" resultMap="ArdMediaPicResult"> SELECT id, task_id, file_size, file_md5, media_url, source_url, longitude, latitude, height, capture_time, create_time, update_time FROM ard_media_pic <where> <if test="taskId != null">AND task_id = #{taskId}</if> <if test="fileMd5 != null">AND file_md5 = #{fileMd5}</if> </where> ORDER BY capture_time DESC </select> <!-- 根据任务ID查询图片 --> <select id="selectPicByTaskId" resultMap="ArdMediaPicResult"> SELECT * FROM ard_media_pic WHERE task_id = #{taskId} ORDER BY capture_time DESC </select> <!-- 插入图片 --> <insert id="insertPic" parameterType="com.ard.work.domain.entity.ArdMediaPic"> INSERT INTO ard_media_pic ( id, task_id, file_size, file_md5, media_url, source_url, longitude, latitude, height, capture_time, create_time ) VALUES ( #{id}, #{taskId}, #{fileSize}, #{fileMd5}, #{mediaUrl}, #{sourceUrl}, #{longitude}, #{latitude}, #{height}, #{captureTime}, #{createTime} ) </insert> <!-- 批量插入图片 --> <insert id="insertPicList"> INSERT INTO ard_media_pic ( id, task_id, file_size, file_md5, media_url, source_url, longitude, latitude, height, capture_time, create_time ) VALUES <foreach collection="list" item="item" separator=","> ( #{item.id}, #{item.taskId}, #{item.fileSize}, #{item.fileMd5}, #{item.mediaUrl}, #{item.sourceUrl}, #{item.longitude}, #{item.latitude}, #{item.height}, #{item.captureTime}, #{item.createTime} ) </foreach> </insert> <delete id="deletePicById"> DELETE FROM ard_media_pic WHERE id = #{id} </delete> <delete id="deletePicByTaskId"> DELETE FROM ard_media_pic WHERE task_id = #{taskId} </delete> </mapper> ard-work/src/main/resources/mapper/ArdMediaVideoMapper.xml
New file @@ -0,0 +1,118 @@ <?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.ard.work.mapper.ArdMediaVideoMapper"> <!-- 结果映射 --> <resultMap id="ArdMediaVideoResult" type="com.ard.work.domain.entity.ArdMediaVideo"> <id property="id" column="id"/> <result property="name" column="name"/> <result property="fileSize" column="file_size"/> <result property="mediaUrl" column="media_url"/> <result property="sourceUrl" column="source_url"/> <result property="thumbnailUrl" column="thumbnail_url"/> <result property="startTime" column="start_time"/> <result property="endTime" column="end_time"/> <result property="spanSeconds" column="span_seconds"/> <result property="taskId" column="task_id"/> <result property="createTime" column="create_time"/> <result property="updateTime" column="update_time"/> </resultMap> <!-- 查询视频列表 --> <select id="selectArdMediaVideoList" resultMap="ArdMediaVideoResult"> SELECT id, name, file_size, media_url, source_url, thumbnail_url, start_time, end_time, span_seconds, task_id, create_time, update_time FROM ard_media_video <where> <if test="taskId != null">AND task_id = #{taskId}</if> <if test="name != null and name != ''">AND name LIKE CONCAT('%', #{name}, '%')</if> <if test="startTime != null">AND start_time >= #{startTime}</if> <if test="endTime != null">AND end_time <= #{endTime}</if> </where> ORDER BY start_time DESC </select> <!-- 根据ID查询 --> <select id="selectArdMediaVideoById" resultMap="ArdMediaVideoResult"> SELECT id, name, file_size, media_url, source_url, thumbnail_url, start_time, end_time, span_seconds, task_id, create_time, update_time FROM ard_media_video WHERE id = #{id} </select> <!-- 根据任务ID查询 --> <select id="selectArdMediaVideoByTaskId" resultMap="ArdMediaVideoResult"> SELECT id, name, file_size, media_url, source_url, thumbnail_url, start_time, end_time, span_seconds, task_id, create_time, update_time FROM ard_media_video WHERE task_id = #{taskId} ORDER BY start_time DESC </select> <!-- 新增视频 --> <insert id="insertArdMediaVideo" parameterType="com.ard.work.domain.entity.ArdMediaVideo"> INSERT INTO ard_media_video ( id, name, file_size, media_url, source_url, thumbnail_url, start_time, end_time, span_seconds, task_id, create_time, update_time ) VALUES ( #{id}, #{name}, #{fileSize}, #{mediaUrl}, #{sourceUrl}, #{thumbnailUrl}, #{startTime}, #{endTime}, #{spanSeconds}, #{taskId}, #{createTime}, #{updateTime} ) </insert> <!-- 批量新增 --> <insert id="insertArdMediaVideoList"> INSERT INTO ard_media_video ( id, name, file_size, media_url, source_url, thumbnail_url, start_time, end_time, span_seconds, task_id, create_time, update_time ) VALUES <foreach collection="list" item="item" separator=","> ( #{item.id}, #{item.name}, #{item.fileSize}, #{item.mediaUrl}, #{item.sourceUrl}, #{item.thumbnailUrl}, #{item.startTime}, #{item.endTime}, #{item.spanSeconds}, #{item.taskId}, #{item.createTime}, #{item.updateTime} ) </foreach> </insert> <!-- 更新视频 --> <update id="updateArdMediaVideo"> UPDATE ard_media_video <set> <if test="name != null">name = #{name},</if> <if test="fileSize != null">file_size = #{fileSize},</if> <if test="mediaUrl != null">media_url = #{mediaUrl},</if> <if test="sourceUrl != null">source_url = #{sourceUrl},</if> <if test="thumbnailUrl != null">thumbnail_url = #{thumbnailUrl},</if> <if test="startTime != null">start_time = #{startTime},</if> <if test="endTime != null">end_time = #{endTime},</if> <if test="spanSeconds != null">span_seconds = #{spanSeconds},</if> <if test="updateTime != null">update_time = #{updateTime},</if> </set> WHERE id = #{id} </update> <!-- 删除 --> <delete id="deleteArdMediaVideoById"> DELETE FROM ard_media_video WHERE id = #{id} </delete> <delete id="deleteArdMediaVideoByTaskId"> DELETE FROM ard_media_video WHERE task_id = #{taskId} </delete> </mapper>