From 5c2f353bfdaea493f043ea021c742f1b93bae46c Mon Sep 17 00:00:00 2001
From: liusuyi <1951119284@qq.com>
Date: Tue, 18 Nov 2025 14:40:28 +0800
Subject: [PATCH] 增加架次和媒体库同步

---
 ard-work/src/main/java/com/ard/work/mapper/ArdFlightTaskMapper.java                   |   26 +
 ard-work/src/main/java/com/ard/work/mapper/ArdMediaMapper.java                        |   32 ++
 ard-work/src/main/java/com/ard/work/domain/dto/BoxHistoryDTO.java                     |    3 
 ard-oss/src/main/java/com/ard/oss/service/impl/OssServiceContext.java                 |   16 +
 ard-work/src/main/java/com/ard/work/domain/entity/ArdFlightTask.java                  |   23 +
 ard-work/src/main/java/com/ard/work/domain/entity/ArdMedia.java                       |   16 +
 ard-admin/src/main/java/com/ard/web/controller/work/UavBaseController.java            |   26 +
 ard-work/src/main/java/com/ard/work/service/impl/IArdMediaServiceImpl.java            |   31 ++
 ard-work/src/main/java/com/ard/work/mapper/ArdServerInfoMapper.java                   |    6 
 ard-work/src/main/java/com/ard/work/service/impl/GimbalSpeedServiceImpl.java          |    2 
 ard-work/src/main/java/com/ard/work/service/impl/ArdFlightTaskServiceServiceImpl.java |  273 +++++++++++++++++++
 ard-common/src/main/java/com/ard/common/utils/file/MimeTypeUtils.java                 |   27 +
 ard-work/src/main/resources/mapper/ArdFlightTaskMapper.xml                            |  117 ++++++++
 ard-work/src/main/java/com/ard/work/domain/receiver/BoxHistoryReceiver.java           |    7 
 ard-work/src/main/resources/mapper/ArdServerInfoMapper.xml                            |    6 
 ard-work/src/main/java/com/ard/work/service/impl/ArdServerInfoServiceImpl.java        |    5 
 ard-work/src/main/java/com/ard/work/service/IArdMediaService.java                     |   20 +
 ard-work/src/main/java/com/ard/work/service/IArdServerInfoService.java                |    5 
 ard-work/src/main/java/com/ard/work/service/IArdFlightTaskService.java                |   19 +
 ard-work/src/main/resources/mapper/ArdMediaMapper.xml                                 |  140 ++++++++++
 20 files changed, 786 insertions(+), 14 deletions(-)

diff --git a/ard-admin/src/main/java/com/ard/web/controller/work/UavBaseController.java b/ard-admin/src/main/java/com/ard/web/controller/work/UavBaseController.java
index 243651c..2ca21ae 100644
--- a/ard-admin/src/main/java/com/ard/web/controller/work/UavBaseController.java
+++ b/ard-admin/src/main/java/com/ard/web/controller/work/UavBaseController.java
@@ -6,10 +6,15 @@
 import com.ard.work.domain.dto.BoxHistoryDTO;
 import com.ard.work.domain.dto.BoxPhotosDTO;
 import com.ard.work.domain.dto.BoxSettingsDTO;
+import com.ard.work.domain.entity.ArdDroneInfo;
+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.BoxInfoReceiver;
 import com.ard.work.domain.receiver.BoxStreamUrlReceiver;
+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;
@@ -35,6 +40,10 @@
 
     @Resource
     private ITyService tyService;
+    @Resource
+    private IArdFlightTaskService ardFlightTaskService;
+    @Resource
+    private IArdMediaService ardMediaService;
 
     @ApiOperation("获取边缘服务器token信息")
     @ApiOperationSupport(order = 1)
@@ -61,6 +70,23 @@
         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("/getMediaList")
+    public TableDataInfo getMediaList(ArdMedia ardMedia) {
+        startPage();
+        List<ArdMedia> list = ardMediaService.selectArdMediaList(ardMedia);
+        return getDataTable(list);
+    }
 
     @ApiOperation("获取任务照片")
     @ApiOperationSupport(order = 5)
diff --git a/ard-common/src/main/java/com/ard/common/utils/file/MimeTypeUtils.java b/ard-common/src/main/java/com/ard/common/utils/file/MimeTypeUtils.java
index 9d85813..11c9625 100644
--- a/ard-common/src/main/java/com/ard/common/utils/file/MimeTypeUtils.java
+++ b/ard-common/src/main/java/com/ard/common/utils/file/MimeTypeUtils.java
@@ -2,11 +2,12 @@
 
 /**
  * 媒体类型工具类
- * 
+ *
  * @author ard
  */
-public class MimeTypeUtils
-{
+public class MimeTypeUtils {
+    public static final String VIDEO_MP4 = "video/mp4";
+
     public static final String IMAGE_PNG = "image/png";
 
     public static final String IMAGE_JPG = "image/jpg";
@@ -16,15 +17,15 @@
     public static final String IMAGE_BMP = "image/bmp";
 
     public static final String IMAGE_GIF = "image/gif";
-    
-    public static final String[] IMAGE_EXTENSION = { "bmp", "gif", "jpg", "jpeg", "png" };
 
-    public static final String[] FLASH_EXTENSION = { "swf", "flv" };
+    public static final String[] IMAGE_EXTENSION = {"bmp", "gif", "jpg", "jpeg", "png"};
 
-    public static final String[] MEDIA_EXTENSION = { "swf", "flv", "mp3", "wav", "wma", "wmv", "mid", "avi", "mpg",
-            "asf", "rm", "rmvb" };
+    public static final String[] FLASH_EXTENSION = {"swf", "flv"};
 
-    public static final String[] VIDEO_EXTENSION = { "mp4", "avi", "rmvb" };
+    public static final String[] MEDIA_EXTENSION = {"swf", "flv", "mp3", "wav", "wma", "wmv", "mid", "avi", "mpg",
+            "asf", "rm", "rmvb"};
+
+    public static final String[] VIDEO_EXTENSION = {"mp4", "avi", "rmvb"};
 
     public static final String[] DEFAULT_ALLOWED_EXTENSION = {
             // 图片
@@ -43,10 +44,10 @@
             "glb"
     };
 
-    public static String getExtension(String prefix)
-    {
-        switch (prefix)
-        {
+    public static String getExtension(String prefix) {
+        switch (prefix) {
+            case VIDEO_MP4:
+                return "mp4";
             case IMAGE_PNG:
                 return "png";
             case IMAGE_JPG:
diff --git a/ard-oss/src/main/java/com/ard/oss/service/impl/OssServiceContext.java b/ard-oss/src/main/java/com/ard/oss/service/impl/OssServiceContext.java
index cc61355..e78de05 100644
--- a/ard-oss/src/main/java/com/ard/oss/service/impl/OssServiceContext.java
+++ b/ard-oss/src/main/java/com/ard/oss/service/impl/OssServiceContext.java
@@ -12,6 +12,7 @@
 import io.minio.PutObjectArgs;
 import lombok.Cleanup;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 import org.springframework.util.StringUtils;
 import org.springframework.web.multipart.MultipartFile;
@@ -167,7 +168,22 @@
             throw new RuntimeException("Upload failed: " + e.getMessage(), e);
         }
     }
+    @Async("threadPoolTaskExecutor")
+    public String upload(String bucket, String objectKey, InputStream input, String contentType) {
+        try {
+            // 上传文件时指定 Content-Type
+            this.ossService.putObject(bucket, objectKey, input, contentType);
 
+            // ✅ 构建永久访问 URL
+            String baseUrl = OssConfiguration.endpoint;
+            if (!baseUrl.endsWith("/")) {
+                baseUrl += "/";
+            }
+            return baseUrl + bucket + "/" + objectKey;
+        } catch (Exception e) {
+            throw new RuntimeException("Upload failed: " + e.getMessage(), e);
+        }
+    }
     // 获取文件 MIME 类型
     public String getMimeTypeByExtension(String fileName) {
         // 获取文件扩展名
diff --git a/ard-work/src/main/java/com/ard/work/domain/dto/BoxHistoryDTO.java b/ard-work/src/main/java/com/ard/work/domain/dto/BoxHistoryDTO.java
index ecbd858..d58e025 100644
--- a/ard-work/src/main/java/com/ard/work/domain/dto/BoxHistoryDTO.java
+++ b/ard-work/src/main/java/com/ard/work/domain/dto/BoxHistoryDTO.java
@@ -1,11 +1,14 @@
 package com.ard.work.domain.dto;
 
+import com.fasterxml.jackson.annotation.JsonFormat;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 
+import java.util.Date;
+
 @Data
 @Builder
 @AllArgsConstructor
diff --git a/ard-work/src/main/java/com/ard/work/domain/entity/ArdFlightTask.java b/ard-work/src/main/java/com/ard/work/domain/entity/ArdFlightTask.java
new file mode 100644
index 0000000..8e4602b
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/work/domain/entity/ArdFlightTask.java
@@ -0,0 +1,23 @@
+package com.ard.work.domain.entity;
+
+import com.ard.common.core.domain.BaseEntity;
+import lombok.Data;
+
+import java.util.Date;
+import java.util.List;
+
+@Data
+public class ArdFlightTask extends BaseEntity {
+
+    private Integer taskId;             // 任务ID
+    private String boxSn;               // 飞行器序列号
+    private String boxName;             // 飞行器名称
+    private String taskName;            // 任务名称
+    private Integer photoCount;         // 图片数量
+    private Integer videoCount;         // 录像数量
+    private String videoPath;           // 视频路径
+    private Date startTime;             // 任务开始时间
+    private Date endTime;               // 任务结束时间
+    private String serverId;            // 服务器ID
+    private List<ArdMedia> mediaList;   // 媒体文件列表
+}
diff --git a/ard-work/src/main/java/com/ard/work/domain/entity/ArdMedia.java b/ard-work/src/main/java/com/ard/work/domain/entity/ArdMedia.java
new file mode 100644
index 0000000..a29e680
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/work/domain/entity/ArdMedia.java
@@ -0,0 +1,16 @@
+package com.ard.work.domain.entity;
+
+import com.ard.common.core.domain.BaseEntity;
+import lombok.Data;
+
+@Data
+public class ArdMedia extends BaseEntity {
+
+    private String id;                 // 媒体ID
+    private Integer taskId;            // 任务ID(外键关联飞行任务)
+    private Integer mediaType;         // 媒体类型(0"photo" 或 1"video")
+    private Long fileSize;             // 文件大小
+    private String fileMd5;            // 文件MD5值
+    private String mediaUrl;           // 地址
+    private String sourceUrl;          // 源地址
+}
diff --git a/ard-work/src/main/java/com/ard/work/domain/receiver/BoxHistoryReceiver.java b/ard-work/src/main/java/com/ard/work/domain/receiver/BoxHistoryReceiver.java
index de84960..5e03b4e 100644
--- a/ard-work/src/main/java/com/ard/work/domain/receiver/BoxHistoryReceiver.java
+++ b/ard-work/src/main/java/com/ard/work/domain/receiver/BoxHistoryReceiver.java
@@ -37,6 +37,13 @@
     @JsonProperty("endTime")
     private String endTime;
 
+
+    private Integer flightDuration;
+    private Integer flightMileage;
+    private Float aveSpeed;
+    private Float maxSpeed;
+
+
     @JsonProperty("videoPathList")
     private List<String> videoPathList;
 
diff --git a/ard-work/src/main/java/com/ard/work/mapper/ArdFlightTaskMapper.java b/ard-work/src/main/java/com/ard/work/mapper/ArdFlightTaskMapper.java
new file mode 100644
index 0000000..9235bc4
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/work/mapper/ArdFlightTaskMapper.java
@@ -0,0 +1,26 @@
+package com.ard.work.mapper;
+
+import com.ard.work.domain.entity.ArdFlightTask;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.Date;
+import java.util.List;
+
+public interface ArdFlightTaskMapper {
+    // 插入飞行任务
+    int insertArdFlightTask(ArdFlightTask task);
+
+    // 根据任务ID获取飞行任务
+    ArdFlightTask getArdFlightTaskById(@Param("taskId") Integer taskId);
+
+    // 获取所有飞行任务
+    List<ArdFlightTask> selectArdFlightTask(ArdFlightTask ardFlightTask);
+
+    // 获取某个时间戳后的任务
+    List<ArdFlightTask> getTasksAfter(@Param("lastSyncTime") String lastSyncTime);
+
+
+    // 获取最大 startTime
+    Date findMaxStartTimeByServerId(String serverId);
+}
+
diff --git a/ard-work/src/main/java/com/ard/work/mapper/ArdMediaMapper.java b/ard-work/src/main/java/com/ard/work/mapper/ArdMediaMapper.java
new file mode 100644
index 0000000..ebd0e76
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/work/mapper/ArdMediaMapper.java
@@ -0,0 +1,32 @@
+package com.ard.work.mapper;
+
+import com.ard.work.domain.entity.ArdMedia;
+import org.apache.ibatis.annotations.Mapper;
+import java.util.List;
+
+
+public interface ArdMediaMapper {
+
+    /**
+     * 查询媒体数据列表
+     *
+     * @param ardMedia 媒体数据
+     * @return 媒体数据集合
+     */
+    List<ArdMedia> selectArdMedia(ArdMedia ardMedia);  // 动态查询
+    List<ArdMedia> selectArdMediaByTaskId(Integer taskId);  // 根据任务ID查询
+
+    /**
+     * 插入媒体数据记录
+     *
+     * @param ardMedia 媒体数据
+     */
+    void insertArdMedia(ArdMedia ardMedia);  // 插入单个记录
+
+    void insertArdMediaList(List<ArdMedia> ardMediaList);  // 插入批量记录
+
+    void updateArdMedia(ArdMedia ardMedia);  // 更新记录
+    void deleteArdMediaById(Integer id);  // 根据ID删除记录
+    void deleteArdMediaByIds(List<Integer> mediaIds);  // 批量删除记录
+    void deleteArdMediaByTaskId(Integer taskId);  // 批量删除记录
+}
diff --git a/ard-work/src/main/java/com/ard/work/mapper/ArdServerInfoMapper.java b/ard-work/src/main/java/com/ard/work/mapper/ArdServerInfoMapper.java
index f28c31b..b99214d 100644
--- a/ard-work/src/main/java/com/ard/work/mapper/ArdServerInfoMapper.java
+++ b/ard-work/src/main/java/com/ard/work/mapper/ArdServerInfoMapper.java
@@ -70,4 +70,10 @@
      */
     public int updateServerStatus(@Param("id") String id, @Param("status") String status);
 
+    /**
+     * 获取服务器ids
+     *
+     * @return 结果
+     */
+    List<String> getServerIdsList();
 }
diff --git a/ard-work/src/main/java/com/ard/work/service/IArdFlightTaskService.java b/ard-work/src/main/java/com/ard/work/service/IArdFlightTaskService.java
new file mode 100644
index 0000000..ca8d500
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/work/service/IArdFlightTaskService.java
@@ -0,0 +1,19 @@
+package com.ard.work.service;
+
+import com.ard.work.domain.entity.ArdFlightTask;
+import java.util.List;
+
+/**
+ * Description:
+ * Author: lsy
+ * Date: 2025年11月18日12:31
+ **/
+public interface IArdFlightTaskService {
+    /**
+     * 查询飞行架次列表
+     *
+     * @param ardFlightTask 飞行架次
+     * @return 无人机飞行架次合
+     */
+    public List<ArdFlightTask> selectArdFlightTask(ArdFlightTask ardFlightTask);
+}
diff --git a/ard-work/src/main/java/com/ard/work/service/IArdMediaService.java b/ard-work/src/main/java/com/ard/work/service/IArdMediaService.java
new file mode 100644
index 0000000..356ed7a
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/work/service/IArdMediaService.java
@@ -0,0 +1,20 @@
+package com.ard.work.service;
+
+import com.ard.work.domain.entity.ArdMedia;
+
+import java.util.List;
+
+/**
+ * Description:
+ * Author: lsy
+ * Date: 2025年11月18日13:15
+ **/
+public interface IArdMediaService {
+    /**
+     * 查询媒体数据列表
+     *
+     * @param ardMedia 媒体数据
+     * @return 媒体数据集合
+     */
+    public List<ArdMedia> selectArdMediaList(ArdMedia ardMedia);
+}
diff --git a/ard-work/src/main/java/com/ard/work/service/IArdServerInfoService.java b/ard-work/src/main/java/com/ard/work/service/IArdServerInfoService.java
index f6ab3d7..78b1125 100644
--- a/ard-work/src/main/java/com/ard/work/service/IArdServerInfoService.java
+++ b/ard-work/src/main/java/com/ard/work/service/IArdServerInfoService.java
@@ -70,4 +70,9 @@
      */
     public int updateServerStatus(String id,String status);
 
+    /**
+     * 获取服务器ID列表
+     */
+    public List<String> getServerIdsList();
+
 }
diff --git a/ard-work/src/main/java/com/ard/work/service/impl/ArdFlightTaskServiceServiceImpl.java b/ard-work/src/main/java/com/ard/work/service/impl/ArdFlightTaskServiceServiceImpl.java
new file mode 100644
index 0000000..87b1be2
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/work/service/impl/ArdFlightTaskServiceServiceImpl.java
@@ -0,0 +1,273 @@
+package com.ard.work.service.impl;
+
+import com.ard.common.utils.DateUtils;
+import com.ard.common.utils.StringUtils;
+import com.ard.common.utils.file.MimeTypeUtils;
+import com.ard.common.utils.uuid.IdUtils;
+import com.ard.oss.service.IOssService;
+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.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.TyHttpCommonReceiver;
+import com.ard.work.mapper.ArdFlightTaskMapper;
+import com.ard.work.mapper.ArdMediaMapper;
+import com.ard.work.service.IArdFlightTaskService;
+import com.ard.work.service.IArdServerInfoService;
+import org.apache.commons.io.FilenameUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.annotation.Resource;
+import javax.net.ssl.*;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Date;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
+
+@Service
+public class ArdFlightTaskServiceServiceImpl implements IArdFlightTaskService {
+
+    @Resource
+    private ArdFlightTaskMapper ardFlightTaskMapper;
+    @Resource
+    private ArdMediaMapper ardMediaMapper;
+    @Resource
+    private IArdServerInfoService ardServerInfoService;
+    @Resource
+    private TyClient tyClient;
+    @Resource
+    private OssServiceContext ossServiceContext;
+
+    @Resource
+    private ThreadPoolTaskExecutor threadPoolTaskExecutor;  // 使用线程池执行异步任务
+
+    @Override
+    public List<ArdFlightTask> selectArdFlightTask(ArdFlightTask ardFlightTask) {
+        return ardFlightTaskMapper.selectArdFlightTask(ardFlightTask);
+    }
+
+    // 同步所有服务的飞行历史
+    @Scheduled(fixedDelay = 10000)
+    public void syncAllFlightHistory() {
+        List<String> serverIds = ardServerInfoService.getServerIdsList();
+        for (String serverId : serverIds) {
+            // 获取上次同步的时间(即数据库中最大 startTime)
+            Date lastSyncTime = getLastSyncTimeForServer(serverId);
+            // 根据上次同步时间同步飞行历史
+            syncFlightHistory(serverId, lastSyncTime);
+        }
+    }
+
+    // 获取上次同步时间(获取最大 startTime)
+    private Date getLastSyncTimeForServer(String serverId) {
+        Date maxStartTime = ardFlightTaskMapper.findMaxStartTimeByServerId(serverId);
+        return maxStartTime != null ? maxStartTime : new Date(0);  // 如果没有数据,返回 Unix 纪元时间作为起点
+    }
+
+    // 调用接口获取飞行历史数据
+    // 根据上次同步时间同步飞行历史
+    public void syncFlightHistory(String serverId, Date lastSyncTime) {
+        // 创建查询条件 BoxHistoryDTO,并设置 startTime 作为过滤条件
+        BoxHistoryDTO boxHistoryDTO = new BoxHistoryDTO();
+        boxHistoryDTO.setStartTime(DateUtils.parseDateToStr(DateUtils.YYYY_MM_DD, lastSyncTime));  // 传递 lastSyncTime 参数
+
+        // 获取飞行历史
+        List<BoxHistoryReceiver> historyList = getBoxHistory(serverId, boxHistoryDTO);
+
+        // 如果获取到数据,处理并同步到本地数据库
+        if (historyList != null && !historyList.isEmpty()) {
+            for (BoxHistoryReceiver boxHistory : historyList) {
+                syncToLocalDatabase(serverId, boxHistory);
+            }
+        }
+    }
+
+    // 调用接口获取飞行历史数据
+    private List<BoxHistoryReceiver> getBoxHistory(String serverId, BoxHistoryDTO boxHistoryDTO) {
+        TyHttpCommonReceiver<List<BoxHistoryReceiver>> boxHistory = tyClient.getBoxHistory(serverId, boxHistoryDTO);
+        return boxHistory.getContent();
+    }
+
+    // 将数据同步到本地数据库
+    private void syncToLocalDatabase(String serverId, BoxHistoryReceiver boxHistoryReceiver) {
+        // 创建FlightTask实体对象并填充数据
+        ArdFlightTask flightTask = new ArdFlightTask();
+        flightTask.setTaskId(boxHistoryReceiver.getTaskId());
+        flightTask.setBoxSn(boxHistoryReceiver.getBoxSn());
+        flightTask.setBoxName(boxHistoryReceiver.getBoxName());
+        flightTask.setTaskName(boxHistoryReceiver.getTaskName());
+
+        Integer photoCount = Integer.parseInt(boxHistoryReceiver.getPhotoCount());
+        flightTask.setPhotoCount(photoCount);
+
+        Integer videoCount = (boxHistoryReceiver.getVideoPathList() != null) ?
+                boxHistoryReceiver.getVideoPathList().size() : 0;
+        flightTask.setVideoCount(videoCount);
+
+        flightTask.setVideoPath(boxHistoryReceiver.getVideoPath());
+        flightTask.setServerId(serverId);
+
+        Date startTime = DateUtils.dateTime(DateUtils.YYYY_MM_DD_HH_MM_SS, boxHistoryReceiver.getStartTime());
+        flightTask.setStartTime(startTime);
+
+        Date endTime = DateUtils.dateTime(DateUtils.YYYY_MM_DD_HH_MM_SS, boxHistoryReceiver.getEndTime());
+        flightTask.setEndTime(endTime);
+
+        flightTask.setCreateTime(DateUtils.getNowDate());
+
+        // 检查是否已经存在相同任务ID的记录
+        ArdFlightTask ardFlightTaskById = ardFlightTaskMapper.getArdFlightTaskById(flightTask.getTaskId());
+        if (ardFlightTaskById != null) {
+            return;
+        }
+
+        System.out.println("Syncing task " + boxHistoryReceiver.getTaskId());
+
+        // 插入新记录
+        int rows = ardFlightTaskMapper.insertArdFlightTask(flightTask);
+        if (rows > 0) {
+            // 删除旧的媒体记录,防止重复插入
+            ardMediaMapper.deleteArdMediaByTaskId(flightTask.getTaskId());
+
+            // 插入视频
+            syncVideoToDatabase(flightTask, boxHistoryReceiver.getVideoPathList());
+
+            // 插入图片
+            syncPhotoToDatabase(flightTask, serverId, boxHistoryReceiver.getBoxSn());
+        }
+    }
+
+    // 插入视频记录
+    private void syncVideoToDatabase(ArdFlightTask flightTask, List<String> videoPathList) {
+        if (videoPathList != null && !videoPathList.isEmpty()) {
+            for (String videoPath : videoPathList) {
+
+                ArdMedia ardMedia = new ArdMedia();
+                ardMedia.setId(IdUtils.simpleUUID());
+                ardMedia.setTaskId(flightTask.getTaskId());
+                ardMedia.setMediaType(1); // 1表示视频
+                ardMedia.setSourceUrl(videoPath);
+                // 异步上传文件到OSS,并在上传完成后插入数据库
+                CompletableFuture.supplyAsync(() -> uploadFileToOss(flightTask.getBoxSn(), videoPath, "record",
+                                MimeTypeUtils.VIDEO_MP4), threadPoolTaskExecutor)
+                        .thenAccept(url -> {
+                            ardMedia.setMediaUrl(url);  // 获取上传后的文件 URL
+                            ardMedia.setCreateTime(new Date());
+                            ardMediaMapper.insertArdMedia(ardMedia);  // 插入数据库
+                        });
+            }
+        }
+    }
+
+    // 插入图片记录
+    private void syncPhotoToDatabase(ArdFlightTask flightTask, String serverId, String boxSn) {
+        // 查询图片
+        BoxPhotosDTO boxPhotosDTO = new BoxPhotosDTO();
+        boxPhotosDTO.setTaskId(flightTask.getTaskId());
+        boxPhotosDTO.setBoxSn(boxSn);
+        boxPhotosDTO.setPageIndex(1);
+        boxPhotosDTO.setPageSize(1000);
+
+        TyHttpCommonReceiver<List<BoxPhotosReceiver>> boxPhotos = tyClient.getBoxPhotos(serverId, boxPhotosDTO);
+        List<BoxPhotosReceiver> photoList = boxPhotos.getContent();
+
+        if (photoList != null && !photoList.isEmpty()) {
+            for (BoxPhotosReceiver photo : photoList) {
+                ArdMedia ardMedia = new ArdMedia();
+                ardMedia.setId(IdUtils.simpleUUID());
+                ardMedia.setTaskId(flightTask.getTaskId());
+                ardMedia.setMediaType(0); // 0表示图片
+                ardMedia.setFileSize(photo.getFileSize());
+                ardMedia.setFileMd5(photo.getFileMd5());
+                ardMedia.setSourceUrl(photo.getImgUrl());
+                // 异步上传文件到OSS,并在上传完成后插入数据库
+                CompletableFuture.supplyAsync(() -> uploadFileToOss(flightTask.getBoxSn(), photo.getImgUrl(), "pic",
+                                MimeTypeUtils.IMAGE_JPG), threadPoolTaskExecutor)
+                        .thenAccept(url -> {
+                            ardMedia.setMediaUrl(url);  // 获取上传后的文件 URL
+                            ardMedia.setCreateTime(new Date());
+                            ardMediaMapper.insertArdMedia(ardMedia);  // 插入数据库
+                        });
+            }
+        }
+    }
+
+    // 通用的上传方法,支持视频和图片等文件上传
+    public String uploadFileToOss(String boxSn, String fileUrl, String bucket, String contentType) {
+        InputStream fileInputStream = null;
+        String fileOssUrl = "";
+        try {
+            // 创建信任所有证书的SSLContext
+            TrustManager[] trustAllCerts = new TrustManager[]{
+                    new X509TrustManager() {
+                        @Override
+                        public java.security.cert.X509Certificate[] getAcceptedIssuers() {
+                            return null;
+                        }
+
+                        @Override
+                        public void checkClientTrusted(
+                                java.security.cert.X509Certificate[] certs, String authType) {
+                        }
+
+                        @Override
+                        public void checkServerTrusted(
+                                java.security.cert.X509Certificate[] certs, String authType) {
+                        }
+                    }
+            };
+
+            SSLContext sc = SSLContext.getInstance("SSL");
+            sc.init(null, trustAllCerts, new java.security.SecureRandom());
+            HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory());
+
+            // 创建所有主机名都信任的HostnameVerifier
+            HostnameVerifier allHostsValid = new HostnameVerifier() {
+                @Override
+                public boolean verify(String hostname, SSLSession session) {
+                    return true;
+                }
+            };
+            HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid);
+
+            // 现在可以正常访问
+
+            // 从URL创建InputStream
+            URL url = new URL(fileUrl);
+            fileInputStream = url.openStream();
+
+            // 文件名
+            String fileName = boxSn + "/" + IdUtils.fastSimpleUUID() + "." + MimeTypeUtils.getExtension(contentType);
+
+            // 调用OSS服务上下文进行文件上传
+            fileOssUrl = ossServiceContext.upload(bucket, fileName, fileInputStream, contentType);
+
+        } catch (Exception e) {
+            e.printStackTrace();
+            // 异常处理,可以选择记录日志或者抛出业务异常
+        } finally {
+            // 关闭InputStream
+            if (fileInputStream != null) {
+                try {
+                    fileInputStream.close();
+                } catch (Exception e) {
+                    e.printStackTrace();
+                }
+            }
+        }
+
+        return fileOssUrl; // 返回上传后的文件OSS URL
+    }
+
+}
diff --git a/ard-work/src/main/java/com/ard/work/service/impl/ArdServerInfoServiceImpl.java b/ard-work/src/main/java/com/ard/work/service/impl/ArdServerInfoServiceImpl.java
index bc42e90..351b7ab 100644
--- a/ard-work/src/main/java/com/ard/work/service/impl/ArdServerInfoServiceImpl.java
+++ b/ard-work/src/main/java/com/ard/work/service/impl/ArdServerInfoServiceImpl.java
@@ -150,6 +150,11 @@
         return ardServerInfoMapper.updateServerStatus(id,status);
     }
 
+    @Override
+    public List<String> getServerIdsList() {
+        return ardServerInfoMapper.getServerIdsList();
+    }
+
     private String getEncryptStr(ArdServerInfo serverInfo, long timeStamp) {
         String sb = serverInfo.getAccessKeyId() + serverInfo.getAccessKeySecret() + timeStamp;
         return DigestUtils.md5DigestAsHex(sb.getBytes());
diff --git a/ard-work/src/main/java/com/ard/work/service/impl/GimbalSpeedService.java b/ard-work/src/main/java/com/ard/work/service/impl/GimbalSpeedServiceImpl.java
similarity index 97%
rename from ard-work/src/main/java/com/ard/work/service/impl/GimbalSpeedService.java
rename to ard-work/src/main/java/com/ard/work/service/impl/GimbalSpeedServiceImpl.java
index dc5956f..be56386 100644
--- a/ard-work/src/main/java/com/ard/work/service/impl/GimbalSpeedService.java
+++ b/ard-work/src/main/java/com/ard/work/service/impl/GimbalSpeedServiceImpl.java
@@ -25,7 +25,7 @@
  */
 @Service
 @Slf4j
-public class GimbalSpeedService implements IGimbalSpeedService {
+public class GimbalSpeedServiceImpl implements IGimbalSpeedService {
 
     private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
     private final Map<String, ScheduledFuture<?>> tasks = new ConcurrentHashMap<>();
diff --git a/ard-work/src/main/java/com/ard/work/service/impl/IArdMediaServiceImpl.java b/ard-work/src/main/java/com/ard/work/service/impl/IArdMediaServiceImpl.java
new file mode 100644
index 0000000..26a7e85
--- /dev/null
+++ b/ard-work/src/main/java/com/ard/work/service/impl/IArdMediaServiceImpl.java
@@ -0,0 +1,31 @@
+package com.ard.work.service.impl;
+
+import com.ard.work.domain.entity.ArdMedia;
+import com.ard.work.mapper.ArdMediaMapper;
+import com.ard.work.service.IArdMediaService;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.List;
+
+/**
+ * Description:
+ * Author: lsy
+ * Date: 2025年11月18日13:17
+ **/
+@Service
+public class IArdMediaServiceImpl implements IArdMediaService {
+    @Resource
+    private ArdMediaMapper ardMediaMapper;
+
+    /**
+     * 查询媒体数据列表
+     *
+     * @param ardMedia 媒体数据
+     * @return 媒体数据集合
+     */
+    @Override
+    public List<ArdMedia> selectArdMediaList(ArdMedia ardMedia) {
+        return ardMediaMapper.selectArdMedia(ardMedia);
+    }
+}
diff --git a/ard-work/src/main/resources/mapper/ArdFlightTaskMapper.xml b/ard-work/src/main/resources/mapper/ArdFlightTaskMapper.xml
new file mode 100644
index 0000000..cdd7d25
--- /dev/null
+++ b/ard-work/src/main/resources/mapper/ArdFlightTaskMapper.xml
@@ -0,0 +1,117 @@
+<?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.ArdFlightTaskMapper">
+    <!-- 结果映射 -->
+    <resultMap id="ArdFlightTaskResult" type="ArdFlightTask">
+        <result property="taskId" column="task_id"/>
+        <result property="boxSn" column="box_sn"/>
+        <result property="boxName" column="box_name"/>
+        <result property="taskName" column="task_name"/>
+        <result property="videoCount" column="video_count"/>
+        <result property="photoCount" column="photo_count"/>
+        <result property="videoPath" column="video_path"/>
+        <result property="startTime" column="start_time"/>
+        <result property="endTime" column="end_time"/>
+        <result property="serverId" column="server_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"/>
+    </resultMap>
+    <!-- 查询任务记录并联接图片和视频 -->
+    <select id="selectArdFlightTask" resultMap="ArdFlightTaskResult">
+        SELECT
+        aft.task_id,
+        aft.box_sn,
+        aft.box_name,
+        aft.task_name,
+        aft.video_count,
+        aft.photo_count,
+        aft.video_path,
+        aft.start_time,
+        aft.end_time,
+        aft.server_id,
+        aft.create_by,
+        aft.create_time,
+        aft.update_by,
+        aft.update_time,
+        aft.remark
+        FROM ard_flight_task aft
+        <where>
+            <trim prefix="" suffix="" suffixOverrides="AND,OR">
+                <if test="taskId != null">AND aft.task_id = #{taskId}</if>
+                <if test="boxSn != null">AND aft.box_sn = #{boxSn}</if>
+                <if test="boxName != null">AND aft.box_name LIKE CONCAT('%', #{boxName}, '%')</if>
+                <if test="taskName != null">AND aft.task_name LIKE CONCAT('%', #{taskName}, '%')</if>
+                <if test="startTime != null">AND aft.start_time &gt;= #{startTime}</if>
+                <if test="endTime != null">AND aft.end_time &lt;= #{endTime}</if>
+                <if test="serverId != null">AND aft.server_id = #{serverId}</if>
+            </trim>
+        </where>
+        ORDER BY aft.task_id DESC
+    </select>
+
+    <!-- 插入飞行任务 -->
+    <insert id="insertArdFlightTask" parameterType="com.ard.work.domain.entity.ArdFlightTask">
+        insert into ard_flight_task
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="taskId != null">task_id,</if>
+            <if test="boxSn != null">box_sn,</if>
+            <if test="boxName != null">box_name,</if>
+            <if test="taskName != null">task_name,</if>
+            <if test="videoCount != null">video_count,</if>
+            <if test="photoCount != null">photo_count,</if>
+            <if test="videoPath != null">video_path,</if>
+            <if test="startTime != null">start_time,</if>
+            <if test="endTime != null">end_time,</if>
+            <if test="serverId != null">server_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>
+        </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="taskId != null">#{taskId},</if>
+            <if test="boxSn != null">#{boxSn},</if>
+            <if test="boxName != null">#{boxName},</if>
+            <if test="taskName != null">#{taskName},</if>
+            <if test="videoCount != null">#{videoCount},</if>
+            <if test="photoCount != null">#{photoCount},</if>
+            <if test="videoPath != null">#{videoPath},</if>
+            <if test="startTime != null">#{startTime},</if>
+            <if test="endTime != null">#{endTime},</if>
+            <if test="serverId != null">#{serverId},</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>
+        </trim>
+    </insert>
+
+
+    <!-- 根据任务ID获取飞行任务 -->
+    <select id="getArdFlightTaskById" resultType="ArdFlightTask">
+        SELECT *
+        FROM ard_flight_task
+        WHERE task_id = #{taskId}
+    </select>
+
+
+    <!-- 获取某个时间戳后的任务 -->
+    <select id="getTasksAfter" resultType="ArdFlightTask">
+        SELECT *
+        FROM ard_flight_task
+        WHERE end_time > #{lastSyncTime}
+    </select>
+    <select id="findMaxStartTimeByServerId" resultType="java.util.Date">
+        SELECT MAX(start_time)
+        FROM ard_flight_task
+        WHERE server_id = #{serverId}
+    </select>
+</mapper>
+
diff --git a/ard-work/src/main/resources/mapper/ArdMediaMapper.xml b/ard-work/src/main/resources/mapper/ArdMediaMapper.xml
new file mode 100644
index 0000000..2c8db02
--- /dev/null
+++ b/ard-work/src/main/resources/mapper/ArdMediaMapper.xml
@@ -0,0 +1,140 @@
+<?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.ArdMediaMapper">
+
+    <!-- 结果映射 -->
+    <resultMap id="ArdMediaResult" type="com.ard.work.domain.entity.ArdMedia">
+        <result property="id" column="id"/>
+        <result property="taskId" column="task_id"/>
+        <result property="mediaType" column="media_type"/>
+        <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="createTime" column="create_time"/>
+    </resultMap>
+
+    <!-- 查询媒体列表:根据条件动态生成SQL -->
+    <select id="selectArdMedia" resultMap="ArdMediaResult">
+        SELECT id, task_id, media_type, file_size, file_md5, media_url, source_url,create_time
+        FROM ard_media
+        <where>
+            <!-- 动态查询条件:任务ID -->
+            <if test="taskId != null">AND task_id = #{taskId}</if>
+
+            <!-- 动态查询条件:媒体类型 -->
+            <if test="mediaType != null">AND media_type = #{mediaType}</if>
+
+            <!-- 动态查询条件:文件大小 -->
+            <if test="fileSize != null">AND file_size = #{fileSize}</if>
+
+            <!-- 动态查询条件:文件MD5 -->
+            <if test="fileMd5 != null">AND file_md5 = #{fileMd5}</if>
+
+            <!-- 动态查询条件:媒体URL -->
+            <if test="mediaUrl != null">AND media_url = #{mediaUrl}</if>
+
+            <!-- 动态查询条件:时间戳 -->
+            <if test="createTime != null">AND create_time = #{createTime}</if>
+        </where>
+    </select>
+
+    <!-- 根据任务ID查询媒体 -->
+    <select id="selectArdMediaByTaskId" resultMap="ArdMediaResult">
+        SELECT id, task_id, media_type, file_size, file_md5, media_url, create_time
+        FROM ard_media
+        WHERE task_id = #{taskId}
+    </select>
+
+    <!-- 插入媒体记录 -->
+    <insert id="insertArdMedia" parameterType="com.ard.work.domain.entity.ArdMedia">
+        INSERT INTO ard_media
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="id != null">id,</if>
+            <if test="taskId != null">task_id,</if>
+            <if test="mediaType != null">media_type,</if>
+            <if test="fileSize != null">file_size,</if>
+            <if test="fileMd5 != null">file_md5,</if>
+            <if test="mediaUrl != null">media_url,</if>
+            <if test="sourceUrl != null">source_url,</if>
+            <if test="createTime != null">create_time,</if>
+        </trim>
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="id != null">#{id},</if>
+            <if test="taskId != null">#{taskId},</if>
+            <if test="mediaType != null">#{mediaType},</if>
+            <if test="fileSize != null">#{fileSize},</if>
+            <if test="fileMd5 != null">#{fileMd5},</if>
+            <if test="mediaUrl != null">#{mediaUrl},</if>
+            <if test="sourceUrl != null">#{sourceUrl},</if>
+            <if test="createTime != null">#{createTime},</if>
+        </trim>
+    </insert>
+
+
+    <!-- 批量插入媒体记录 -->
+    <insert id="insertArdMediaList" parameterType="java.util.List">
+        INSERT INTO ard_media
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="list != null and !list.isEmpty()">
+                <foreach item="item" collection="list" separator=",">
+                    <trim prefix="(" suffix=")" suffixOverrides=",">
+                        <if test="item.taskId != null">task_id,</if>
+                        <if test="item.mediaType != null">media_type,</if>
+                        <if test="item.fileSize != null">file_size,</if>
+                        <if test="item.fileMd5 != null">file_md5,</if>
+                        <if test="item.mediaUrl != null">media_url,</if>
+                        <if test="item.sourceUrl != null">source_url,</if>
+                        <if test="item.createTime != null">create_time,</if>
+                    </trim>
+                </foreach>
+            </if>
+        </trim>
+
+        <trim prefix="values " suffix=")" suffixOverrides=",">
+            <foreach item="item" collection="list" separator=",">
+                <trim prefix="(" suffix=")" suffixOverrides=",">
+                    <if test="item.taskId != null">#{item.taskId},</if>
+                    <if test="item.mediaType != null">#{item.mediaType},</if>
+                    <if test="item.fileSize != null">#{item.fileSize},</if>
+                    <if test="item.fileMd5 != null">#{item.fileMd5},</if>
+                    <if test="item.mediaUrl != null">#{item.mediaUrl},</if>
+                    <if test="item.sourceUrl != null">#{item.sourceUrl},</if>
+                    <if test="item.createTime != null">#{item.createTime},</if>
+                </trim>
+            </foreach>
+        </trim>
+    </insert>
+
+
+    <!-- 更新媒体记录 -->
+    <update id="updateArdMedia" parameterType="com.ard.work.domain.entity.ArdMedia">
+        UPDATE ard_media
+        <set>
+            <if test="mediaType != null">media_type = #{mediaType},</if>
+            <if test="fileSize != null">file_size = #{fileSize},</if>
+            <if test="fileMd5 != null">file_md5 = #{fileMd5},</if>
+            <if test="mediaUrl != null">media_url = #{mediaUrl},</if>
+            <if test="createTime != null">create_time = #{createTime},</if>
+        </set>
+        WHERE id = #{id}
+    </update>
+
+    <!-- 删除媒体记录 -->
+    <delete id="deleteArdMediaById" parameterType="Integer">
+        DELETE FROM ard_media WHERE id = #{id}
+    </delete>
+
+    <!-- 批量删除媒体记录 -->
+    <delete id="deleteArdMediaByIds" parameterType="java.util.List">
+        DELETE FROM ard_media WHERE id IN
+        <foreach item="id" collection="list" open="(" separator="," close=")">
+            #{id}
+        </foreach>
+    </delete>
+
+    <delete id="deleteArdMediaByTaskId" parameterType="java.lang.Integer">
+        DELETE FROM ard_media WHERE task_id = #{taskId}
+    </delete>
+
+</mapper>
diff --git a/ard-work/src/main/resources/mapper/ArdServerInfoMapper.xml b/ard-work/src/main/resources/mapper/ArdServerInfoMapper.xml
index 5f5a58a..ae58db9 100644
--- a/ard-work/src/main/resources/mapper/ArdServerInfoMapper.xml
+++ b/ard-work/src/main/resources/mapper/ArdServerInfoMapper.xml
@@ -148,4 +148,10 @@
             #{id}
         </foreach>
     </delete>
+
+    <select id="getServerIdsList" resultType="String">
+        SELECT id
+        FROM ard_server
+        where server_status = "1"
+    </select>
 </mapper>
\ No newline at end of file

--
Gitblit v1.9.3