From 64972e9093fa9dde9ce346273b27445bd9d004b9 Mon Sep 17 00:00:00 2001
From: zhaowenyu <35589726@qq.com>
Date: Wed, 24 Dec 2025 15:14:25 +0800
Subject: [PATCH] 2025-12-24 zwy 增加计划任务轨迹功能,将在计划任务执行期间的飞行轨迹数据、任务状态数据保存;同时将计划任务数据保存到计划任务历史表中保存。并提供完整的轨迹提取接口。

---
 ard-sample/src/main/java/com/ard/sample/wayline/mapper/IWaylineJobHistoryMapper.java           |   10 
 ard-sample/src/main/java/com/ard/sample/wayline/service/impl/WaylineJobHistoryServiceImpl.java |  235 +++++++++++++++
 ard-sample/src/main/java/com/ard/sample/manage/model/dto/WaylineTrajectoryDTO.java             |   69 ++++
 ard-sample/src/main/java/com/ard/sample/manage/service/impl/DeviceOSDServiceImpl.java          |    7 
 ard-sample/src/main/java/com/ard/sample/wayline/model/entity/WaylineJobHistoryEntity.java      |   94 ++++++
 ard-sample/src/main/java/com/ard/sample/manage/mapper/IWaylineTrajectoryMapper.java            |   12 
 ard-sample/src/main/java/com/ard/sample/manage/service/impl/WaylineTrajectoryServiceImpl.java  |  213 ++++++++++++++
 ard-sample/src/main/java/com/ard/sample/manage/model/entity/WaylineTrajectoryEntity.java       |   83 +++++
 ard-admin/src/main/java/com/ard/web/controller/sample/wayline/WaylineTrajectoryController.java |   40 ++
 ard-sample/src/main/java/com/ard/sample/wayline/service/impl/FlightTaskServiceImpl.java        |   33 ++
 ard-sample/src/main/java/com/ard/sample/wayline/service/IWaylineJobHistoryService.java         |   57 +++
 ard-sample/src/main/java/com/ard/sample/manage/service/IWaylineTrajectoryService.java          |   24 +
 12 files changed, 877 insertions(+), 0 deletions(-)

diff --git a/ard-admin/src/main/java/com/ard/web/controller/sample/wayline/WaylineTrajectoryController.java b/ard-admin/src/main/java/com/ard/web/controller/sample/wayline/WaylineTrajectoryController.java
new file mode 100644
index 0000000..4af8443
--- /dev/null
+++ b/ard-admin/src/main/java/com/ard/web/controller/sample/wayline/WaylineTrajectoryController.java
@@ -0,0 +1,40 @@
+package com.ard.web.controller.sample.wayline;
+
+import com.ard.sample.common.model.ResponseResult;
+import com.ard.sample.manage.model.dto.WaylineTrajectoryDTO;
+import com.ard.sample.manage.service.IWaylineTrajectoryService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Optional;
+
+@Api(tags = "航线历史任务轨迹")
+@RequestMapping("/dock/waylineTrajectory")
+@RestController
+public class WaylineTrajectoryController {
+    @Autowired
+    private IWaylineTrajectoryService _waylineTrajectoryService;
+
+    @ApiOperation("获取航线任务轨迹")
+    @GetMapping("/flight-id")
+    public ResponseResult<WaylineTrajectoryDTO> getJob(@RequestParam(name = "job_id") String jobId) {
+        try {
+            if(jobId == null){
+                return ResponseResult.error(98,"任务ID不能为空");
+            }
+            WaylineTrajectoryDTO waylineJobDTO = _waylineTrajectoryService.getWayLineTrajectorybyJobID(jobId);
+            if(waylineJobDTO==null) {
+                return ResponseResult.error(99,"任务不存在");
+            }
+            return ResponseResult.success(waylineJobDTO);
+        } catch (Exception e) {
+            return ResponseResult.error(97, "获取航线轨迹发生异常:"+e.getMessage());
+        }
+    }
+}
+
diff --git a/ard-sample/src/main/java/com/ard/sample/manage/mapper/IWaylineTrajectoryMapper.java b/ard-sample/src/main/java/com/ard/sample/manage/mapper/IWaylineTrajectoryMapper.java
new file mode 100644
index 0000000..7141fa0
--- /dev/null
+++ b/ard-sample/src/main/java/com/ard/sample/manage/mapper/IWaylineTrajectoryMapper.java
@@ -0,0 +1,12 @@
+package com.ard.sample.manage.mapper;
+
+import com.ard.sample.manage.model.entity.WaylineTrajectoryEntity;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.List;
+
+
+public interface IWaylineTrajectoryMapper extends BaseMapper<WaylineTrajectoryEntity> {
+
+}
diff --git a/ard-sample/src/main/java/com/ard/sample/manage/model/dto/WaylineTrajectoryDTO.java b/ard-sample/src/main/java/com/ard/sample/manage/model/dto/WaylineTrajectoryDTO.java
new file mode 100644
index 0000000..794cca6
--- /dev/null
+++ b/ard-sample/src/main/java/com/ard/sample/manage/model/dto/WaylineTrajectoryDTO.java
@@ -0,0 +1,69 @@
+package com.ard.sample.manage.model.dto;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.util.List;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+public class WaylineTrajectoryDTO {
+    /**
+     * 主建
+     * */
+    private Integer id;
+
+    /**
+     * 计划任务id
+     * */
+    private String jobId;
+
+    /**
+     * 计划任务名称
+     * */
+    private String name;
+
+    /**
+     * 用于此任务的航线文件id。
+     * */
+    private String fileId;
+
+    /**
+     * 航线文件名称
+     * */
+    private String fileName;
+
+    /**
+     * 执行此任务的机场sn。
+     * */
+    private String dockSn;
+
+    /**
+     * 进度为ok时,为一次轨迹的结束
+     * */
+    private String progress;
+
+    /**
+     * 创建时间
+     * */
+    private Long createTime;
+
+    /**
+     * 更新时间
+     * */
+    private Long updateTime;
+
+    /**
+     * 飞行轨迹list
+     * */
+    private List<String> trajectoryList;
+
+    /**
+     * 任务状态list
+     * */
+    private List<String> taskList;
+}
diff --git a/ard-sample/src/main/java/com/ard/sample/manage/model/entity/WaylineTrajectoryEntity.java b/ard-sample/src/main/java/com/ard/sample/manage/model/entity/WaylineTrajectoryEntity.java
new file mode 100644
index 0000000..86b542e
--- /dev/null
+++ b/ard-sample/src/main/java/com/ard/sample/manage/model/entity/WaylineTrajectoryEntity.java
@@ -0,0 +1,83 @@
+package com.ard.sample.manage.model.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@TableName(value = "wayline_trajectory")
+public class WaylineTrajectoryEntity implements Serializable {
+
+    /**
+     * 主建
+     * */
+    @TableId(type = IdType.AUTO)
+    private Integer id;
+
+    /**
+     * 计划任务id
+     * */
+    @TableField(value = "job_id")
+    private String jobId;
+
+    /**
+     * 计划任务名称
+     * */
+    @TableField("name")
+    private String name;
+
+    /**
+     * 用于此任务的航线文件id。
+     * */
+    @TableField("file_id")
+    private String fileId;
+
+    /**
+     * 航线文件名称
+     * */
+    @TableField("file_name")
+    private String fileName;
+
+    /**
+     * 执行此任务的机场sn。
+     * */
+    @TableField("dock_sn")
+    private String dockSn;
+
+    /**
+     * 进度为ok时,为一次轨迹的结束
+     * */
+    @TableField("progress")
+    private String progress;
+
+    /**
+     * 创建时间
+     * */
+    @TableField(value = "create_time", fill = FieldFill.INSERT)
+    private Long createTime;
+
+    /**
+     * 更新时间
+     * */
+    @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
+    private Long updateTime;
+
+    /**
+     * 飞行轨迹list
+     * */
+    @TableField("trajectory_list")
+    private byte[] trajectoryList;
+
+    /**
+     * 任务状态list
+     * */
+    @TableField("task_list")
+    private byte[] taskList;
+}
diff --git a/ard-sample/src/main/java/com/ard/sample/manage/service/IWaylineTrajectoryService.java b/ard-sample/src/main/java/com/ard/sample/manage/service/IWaylineTrajectoryService.java
new file mode 100644
index 0000000..8a42c5c
--- /dev/null
+++ b/ard-sample/src/main/java/com/ard/sample/manage/service/IWaylineTrajectoryService.java
@@ -0,0 +1,24 @@
+package com.ard.sample.manage.service;
+
+import com.ard.sample.manage.model.dto.WaylineTrajectoryDTO;
+import com.ard.sample.manage.model.entity.WaylineTrajectoryEntity;
+
+import java.util.List;
+
+public interface IWaylineTrajectoryService {
+
+    /**
+     * 增加list数据,type为:0时,表示轨迹点列表;type为:1时,表示任务状态数据。
+     * @param sn 设备序列号
+     * @param type 类型
+     * @param dataJson json数据
+     * */
+    void setList(String sn, String type, String dataJson);
+
+    /**
+     * 返回航线轨迹完整的数据,拼接的轨迹点列表和任务状态数据。
+     * @param jobId 任务ID
+     * @return 航线轨迹完整的数据
+     * */
+    WaylineTrajectoryDTO getWayLineTrajectorybyJobID(String jobId);
+}
diff --git a/ard-sample/src/main/java/com/ard/sample/manage/service/impl/DeviceOSDServiceImpl.java b/ard-sample/src/main/java/com/ard/sample/manage/service/impl/DeviceOSDServiceImpl.java
index 9cd6bde..0dafd41 100644
--- a/ard-sample/src/main/java/com/ard/sample/manage/service/impl/DeviceOSDServiceImpl.java
+++ b/ard-sample/src/main/java/com/ard/sample/manage/service/impl/DeviceOSDServiceImpl.java
@@ -17,6 +17,7 @@
 import com.ard.sample.manage.model.receiver.OsdPayloadReceiver;
 import com.ard.sample.manage.model.receiver.OsdSubDeviceReceiver;
 import com.ard.sample.manage.service.IDeviceService;
+import com.ard.sample.manage.service.IWaylineTrajectoryService;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.node.ObjectNode;
@@ -41,6 +42,9 @@
 
     @Autowired
     private IDeviceService _deviceService;
+
+    @Autowired
+    private IWaylineTrajectoryService _waylineTrajectoryService;
 
     @Autowired
     private DockWebSocketHandler _dockWebSocketHandler;
@@ -118,6 +122,9 @@
             // 发送给websocket客户端
             _dockWebSocketHandler.sendMessageToSubscribers(device.getDeviceSn(), MessageType.STATUS.getType(), dockOsd, "");
 
+            // 将飞行器的OSD数据dockOsd入库;
+            _waylineTrajectoryService.setList(device.getDeviceSn(),"0", dockOsd);
+
             sendMessageService.sendBatch(webSessions, wsMessage);
             this.pushTelemetryData(data, device.getDeviceSn());
         }
diff --git a/ard-sample/src/main/java/com/ard/sample/manage/service/impl/WaylineTrajectoryServiceImpl.java b/ard-sample/src/main/java/com/ard/sample/manage/service/impl/WaylineTrajectoryServiceImpl.java
new file mode 100644
index 0000000..63eb534
--- /dev/null
+++ b/ard-sample/src/main/java/com/ard/sample/manage/service/impl/WaylineTrajectoryServiceImpl.java
@@ -0,0 +1,213 @@
+package com.ard.sample.manage.service.impl;
+
+import cn.hutool.core.util.SerializeUtil;
+import com.ard.common.core.text.Convert;
+import com.ard.sample.component.mqtt.model.EventsReceiver;
+import com.ard.sample.manage.mapper.IWaylineTrajectoryMapper;
+import com.ard.sample.manage.model.dto.WaylineTrajectoryDTO;
+import com.ard.sample.manage.model.entity.WaylineTrajectoryEntity;
+import com.ard.sample.manage.service.IWaylineTrajectoryService;
+import com.ard.sample.wayline.model.dto.WaylineJobDTO;
+import com.ard.sample.wayline.model.dto.WaylineTaskProgressReceiver;
+import com.ard.sample.wayline.service.IWaylineJobService;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.scheduling.annotation.Async;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Service
+@Slf4j
+public class WaylineTrajectoryServiceImpl implements IWaylineTrajectoryService {
+
+    @Resource
+    private IWaylineTrajectoryMapper _waylineTrajectoryMapper;
+
+    @Autowired
+    private IWaylineJobService _waylineJobService;
+
+    @Autowired
+    protected ObjectMapper mapper;
+    /**
+     * 轨迹点列表缓存
+     */
+    private final ConcurrentHashMap<String, List<String>> cacheTrajectoryMap = new ConcurrentHashMap<>();
+
+    /**
+     * 任务状态数据缓存
+     */
+    private final ConcurrentHashMap<String, List<String>> cacheTaskMap = new ConcurrentHashMap<>();
+
+    /**
+     * 给数据加个进度
+     */
+    private final ConcurrentHashMap<String, String> cacheProgress = new ConcurrentHashMap<>();
+    /**
+     * 分片保存阈值条数
+     */
+    private static final int CHUNK_SIZE = 300;
+
+    /**
+     * 增加list数据,type为:0时,表示轨迹点列表;type为:1时,表示任务状态数据。
+     * @param sn       设备序列号
+     * @param type     类型
+     * @param dataJson json数据
+     */
+    @Override
+    public void setList(String sn, String type, String dataJson) {
+        List<String> listTrajectory = cacheTrajectoryMap.computeIfAbsent(sn, k -> new ArrayList<>());
+        List<String> listTask = cacheTaskMap.computeIfAbsent(sn, k -> new ArrayList<>());
+        String progress = cacheProgress.computeIfAbsent(sn, k -> "0");
+        // 取进度值,就为了判断ok一个任务的结束
+        Integer pro = Convert.toInt(cacheProgress.get(sn));
+        // 取jobid、status字段值
+        String jobId = "";
+        String isOK = "";
+        if (type.equals("0")) {
+            // 飞行轨迹数据
+            listTrajectory.add(dataJson);
+            log.info("轨迹点数据添加到列表缓存,当前列表数量:{}", listTrajectory.size());
+        }
+        if (type.equals("1")) {
+            // 任务状态数据
+            listTask.add(dataJson);
+            log.info("任务状态数据添加到列表缓存,当前列表数量:{}", listTask.size());
+        }
+        // jobid、status字段赋值
+        if (!listTask.isEmpty()) {
+            String json = listTask.get(listTask.size() - 1);
+            EventsReceiver<WaylineTaskProgressReceiver> eventsReceiver = mapper.convertValue(json,
+                    new TypeReference<EventsReceiver<WaylineTaskProgressReceiver>>() {
+                    });
+            jobId = eventsReceiver.getOutput().getExt().getFlightId();
+            isOK = eventsReceiver.getOutput().getStatus();
+
+            log.info("当前任务jobId:{},任务执行进度isOK:{}", jobId, isOK);
+        }
+        if (listTrajectory.size() >= CHUNK_SIZE || listTask.size() >= CHUNK_SIZE) {
+            progress = Convert.toStr(pro + 1);
+            // 分片保存
+            save(sn, jobId);
+            log.info("当前任务保存成功,jobId:{},进度:{}",jobId , progress);
+        } else {
+            // 判断任务状态json中的status字段,如果为OK,则保存数据,一条完整的航线数据结束。
+            try {
+                if (isOK.equals("ok")) {
+                    progress = "ok";
+                    if (getWaylineTrajectoryByProgress(jobId, progress)) {
+                        log.info("当前任务已完成,不再保存数据!jobId:{},progress:{}", jobId, progress);
+                        return;
+                    }
+                    save(sn, jobId);
+                    log.info("当前任务保存成功,jobId:{},进度:{}",jobId , progress);
+                }
+            } catch (Exception e) {
+                log.error("json解析异常!{}", e.getMessage());
+            }
+        }
+    }
+
+    @Async
+    public void save(String sn, String JobId) {
+        List<String> trajectoryList = cacheTrajectoryMap.get(sn);
+        List<String> taskList = cacheTaskMap.get(sn);
+        String progress = cacheProgress.get(sn);
+
+        if (trajectoryList.isEmpty() && taskList.isEmpty()) {
+            return;
+        }
+        try {
+            byte[] blobTrajectory = SerializeUtil.serialize(trajectoryList);
+            byte[] blobTask = SerializeUtil.serialize(taskList);
+
+            Optional<WaylineJobDTO> waylineJobDTO = _waylineJobService.getJobByJobId(JobId);
+            WaylineTrajectoryEntity TrajectoryEntity = new WaylineTrajectoryEntity();
+            TrajectoryEntity.setJobId(JobId);
+            TrajectoryEntity.setName(waylineJobDTO.get().getJobName());
+            TrajectoryEntity.setFileId(waylineJobDTO.get().getFileId());
+            TrajectoryEntity.setFileName(waylineJobDTO.get().getFileName());
+            TrajectoryEntity.setDockSn(sn);
+            TrajectoryEntity.setProgress(progress);
+            TrajectoryEntity.setCreateTime(System.currentTimeMillis());
+            TrajectoryEntity.setUpdateTime(System.currentTimeMillis());
+            TrajectoryEntity.setTrajectoryList(blobTrajectory);
+            TrajectoryEntity.setTaskList(blobTask);
+            // 保存轨迹点列表数据
+            int i = _waylineTrajectoryMapper.insert(TrajectoryEntity);
+            if (i > 0) {
+                log.info("保存轨迹点列表数据成功!jobId:{}", JobId);
+                // 清空缓存
+                cacheTrajectoryMap.put(sn, new ArrayList<>());
+                cacheTaskMap.put(sn, new ArrayList<>());
+                if (progress.equals("ok")) {
+                    cacheProgress.put(sn, "0");
+                }
+            }
+
+        } catch (Exception e) {
+            log.error("保存轨迹点列表数据失败!jobId:{}.{}", JobId, e.getMessage());
+        }
+    }
+
+    /**
+     * 返回航线轨迹完整的数据,拼接的轨迹点列表和任务状态数据。
+     *
+     * @param jobId 任务ID
+     * @return 航线轨迹完整的数据
+     */
+    @Override
+    public WaylineTrajectoryDTO getWayLineTrajectorybyJobID(String jobId) {
+
+        List<WaylineTrajectoryEntity> waylineTrajectoryEntityList = new ArrayList<>(_waylineTrajectoryMapper.selectList(new LambdaQueryWrapper<WaylineTrajectoryEntity>()
+                .eq(jobId != null, WaylineTrajectoryEntity::getJobId, jobId)
+                .orderByAsc(WaylineTrajectoryEntity::getId)));
+
+        return TrajectoryEntityConvertToDTO(waylineTrajectoryEntityList);
+    }
+
+    /**
+     * 根据作业ID和进度获取航线轨迹
+     *
+     * @param jobId    作业ID,可为null
+     * @param progress 进度值,可为null
+     * @return 当存在符合条件的航线轨迹时返回true,否则返回false
+     */
+    public Boolean getWaylineTrajectoryByProgress(String jobId, String progress) {
+        List<WaylineTrajectoryEntity> waylineTrajectoryEntityList = new ArrayList<>(_waylineTrajectoryMapper.selectList(new LambdaQueryWrapper<WaylineTrajectoryEntity>()
+                .eq(jobId != null, WaylineTrajectoryEntity::getJobId, jobId)
+                .eq(progress != null, WaylineTrajectoryEntity::getProgress, progress)
+                .orderByAsc(WaylineTrajectoryEntity::getId)));
+
+        return !waylineTrajectoryEntityList.isEmpty();
+    }
+
+    public WaylineTrajectoryDTO TrajectoryEntityConvertToDTO(List<WaylineTrajectoryEntity> entity) {
+        if (entity == null) {
+            return null;
+        }
+        WaylineTrajectoryDTO dto = new WaylineTrajectoryDTO();
+        dto.setId(entity.get(0).getId());
+        dto.setJobId(entity.get(0).getJobId());
+        dto.setName(entity.get(0).getName());
+        dto.setFileId(entity.get(0).getFileId());
+        dto.setFileName(entity.get(0).getFileName());
+        dto.setDockSn(entity.get(0).getDockSn());
+        dto.setProgress(entity.get(0).getProgress());
+        dto.setCreateTime(entity.get(0).getCreateTime());
+        dto.setUpdateTime(entity.get(0).getUpdateTime());
+        for (WaylineTrajectoryEntity e : entity) {
+            dto.getTrajectoryList().addAll(SerializeUtil.deserialize(e.getTrajectoryList(), List.class));
+            dto.getTaskList().addAll(SerializeUtil.deserialize(e.getTaskList(), List.class));
+        }
+
+        return dto;
+    }
+}
diff --git a/ard-sample/src/main/java/com/ard/sample/wayline/mapper/IWaylineJobHistoryMapper.java b/ard-sample/src/main/java/com/ard/sample/wayline/mapper/IWaylineJobHistoryMapper.java
new file mode 100644
index 0000000..99f08d5
--- /dev/null
+++ b/ard-sample/src/main/java/com/ard/sample/wayline/mapper/IWaylineJobHistoryMapper.java
@@ -0,0 +1,10 @@
+package com.ard.sample.wayline.mapper;
+
+import com.ard.sample.wayline.model.entity.WaylineJobHistoryEntity;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+ * 任务历史信息
+ * */
+public interface IWaylineJobHistoryMapper extends BaseMapper<WaylineJobHistoryEntity> {
+}
diff --git a/ard-sample/src/main/java/com/ard/sample/wayline/model/entity/WaylineJobHistoryEntity.java b/ard-sample/src/main/java/com/ard/sample/wayline/model/entity/WaylineJobHistoryEntity.java
new file mode 100644
index 0000000..5e2004d
--- /dev/null
+++ b/ard-sample/src/main/java/com/ard/sample/wayline/model/entity/WaylineJobHistoryEntity.java
@@ -0,0 +1,94 @@
+package com.ard.sample.wayline.model.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@TableName("wayline_job_history")
+public class WaylineJobHistoryEntity implements Serializable {
+    @TableId(type = IdType.AUTO)
+    private Integer id;
+
+    @TableField("job_id")
+    private String jobId;
+
+    @TableField("name")
+    private String name;
+
+    @TableField("file_id")
+    private String fileId;
+
+    @TableField("file_name")
+    private String filename;
+
+    @TableField("dock_sn")
+    private String dockSn;
+
+    @TableField("workspace_id")
+    private String workspaceId;
+
+    @TableField("task_type")
+    private Integer taskType;
+
+    @TableField("wayline_type")
+    private Integer waylineType;
+
+    @TableField("username")
+    private String username;
+
+    /**
+     * 实际开始时间
+     * */
+    @TableField("execute_time")
+    private Long executeTime;
+
+    /**
+     * 计划结束时间
+     * */
+    @TableField("end_time")
+    private Long endTime;
+
+    @TableField("error_code")
+    private Integer errorCode;
+
+    @TableField("status")
+    private Integer status;
+
+    @TableField("rth_altitude")
+    private Integer rthAltitude;
+
+    @TableField("rth_mode")
+    private Integer rthMode;
+
+    @TableField("out_of_control")
+    private Integer outOfControlAction;
+
+    @TableField("media_count")
+    private Integer mediaCount;
+
+    @TableField(value = "create_time", fill = FieldFill.INSERT)
+    private Long createTime;
+
+    @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
+    private Long updateTime;
+
+    @TableField("begin_time")
+    private Long beginTime;
+
+    @TableField("completed_time")
+    private Long completedTime;
+
+    @TableField("parent_id")
+    private String parentId;
+
+    @TableField("loop_sum")
+    private Integer loopSum;
+}
diff --git a/ard-sample/src/main/java/com/ard/sample/wayline/service/IWaylineJobHistoryService.java b/ard-sample/src/main/java/com/ard/sample/wayline/service/IWaylineJobHistoryService.java
new file mode 100644
index 0000000..d3f93d7
--- /dev/null
+++ b/ard-sample/src/main/java/com/ard/sample/wayline/service/IWaylineJobHistoryService.java
@@ -0,0 +1,57 @@
+package com.ard.sample.wayline.service;
+
+import com.ard.sample.common.model.PaginationData;
+import com.ard.sample.wayline.model.dto.WaylineJobDTO;
+import com.ard.sample.wayline.model.entity.WaylineJobHistoryEntity;
+
+import java.util.Optional;
+
+/**
+ * 计划库历史表
+ * */
+public interface IWaylineJobHistoryService {
+
+    /**
+     * 在数据库中创建任务历史数据。
+     * @param entity
+     * @return
+     */
+    Optional<WaylineJobDTO> createWaylineJob(WaylineJobHistoryEntity entity);
+
+    /**
+     * 更新历史数据任务数据。
+     * @param dto
+     * @return
+     */
+    Boolean updateJob(WaylineJobDTO dto);
+
+    /**
+     * 查询历史任务列表
+     * @param page
+     * @param pageSize
+     * @param status
+     * @param startTime
+     * @param endTime
+     * @param taskType
+     * @param name
+     * @param isComplete
+     * */
+    PaginationData<WaylineJobDTO> getJobsList(long page, long pageSize, Integer status,
+                                              Long startTime, Long endTime, Integer taskType,
+                                              String name, Boolean isComplete);
+
+    /**
+     * 根据任务ID查询历史数据任务信息。
+     * @param jobId
+     * @return job 信息
+     */
+    Optional<WaylineJobDTO> getJobByJobId(String jobId);
+
+    /**
+     * 将WaylineJobDTO对象转换为WaylineJobHistoryEntity对象
+     *
+     * @param dto WaylineJobDTO对象
+     * @return WaylineJobHistoryEntity对象
+     */
+    WaylineJobHistoryEntity dto2Entity(WaylineJobDTO dto);
+}
diff --git a/ard-sample/src/main/java/com/ard/sample/wayline/service/impl/FlightTaskServiceImpl.java b/ard-sample/src/main/java/com/ard/sample/wayline/service/impl/FlightTaskServiceImpl.java
index 2d3e6a2..1832c0c 100644
--- a/ard-sample/src/main/java/com/ard/sample/wayline/service/impl/FlightTaskServiceImpl.java
+++ b/ard-sample/src/main/java/com/ard/sample/wayline/service/impl/FlightTaskServiceImpl.java
@@ -15,12 +15,15 @@
 import com.ard.sample.component.websocket.service.IWebSocketManageService;
 import com.ard.sample.manage.model.dto.DeviceDTO;
 import com.ard.sample.manage.model.enums.UserTypeEnum;
+import com.ard.sample.manage.service.IWaylineTrajectoryService;
 import com.ard.sample.media.model.MediaFileCountDTO;
 import com.ard.sample.wayline.model.dto.WaylineJobDTO;
 import com.ard.sample.wayline.model.dto.WaylineTaskProgressExt;
 import com.ard.sample.wayline.model.dto.WaylineTaskProgressReceiver;
+import com.ard.sample.wayline.model.entity.WaylineJobHistoryEntity;
 import com.ard.sample.wayline.model.enums.WaylineJobStatusEnum;
 import com.ard.sample.wayline.service.IFlightTaskService;
+import com.ard.sample.wayline.service.IWaylineJobHistoryService;
 import com.ard.sample.wayline.service.IWaylineJobService;
 import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.databind.ObjectMapper;
@@ -66,6 +69,12 @@
 
     @Autowired
     private IWaylineJobService waylineJobService;
+
+    @Autowired
+    private IWaylineTrajectoryService _waylineTrajectoryService;
+
+    @Autowired
+    private IWaylineJobHistoryService _waylineJobHistoryService;
 
     @Override
     @ServiceActivator(inputChannel = ChannelName.INBOUND_EVENTS_FLIGHT_TASK_PROGRESS, outputChannel = ChannelName.OUTBOUND)
@@ -143,6 +152,9 @@
 //        if (eventsReceiver.getOutput().getProgress().getPercent() >= 95) {
 //            return;
 //        }
+
+
+
         String json = "";
         try {
             json = mapper.writeValueAsString(eventsReceiver);
@@ -150,6 +162,27 @@
             e.printStackTrace();
         }
         _dockWebSocketHandler.sendMessageToSubscribers(dockSn, MessageType.STATUS.getType(), json, "");
+
+        _waylineTrajectoryService.setList(dockSn,"1",json);
+
+        // 计划任务成功后,保存计划任务到历史记录
+        String status = eventsReceiver.getOutput().getStatus();
+        String jobId = eventsReceiver.getOutput().getExt().getFlightId();
+        if(status.equals("ok") && jobId != null) {
+            Optional<WaylineJobDTO> job = waylineJobService.getJobByJobId(jobId);
+            if(job.isPresent()) {
+                if (_waylineJobHistoryService.getJobByJobId(jobId).isPresent()) {
+                    return;
+                }
+                WaylineJobHistoryEntity waylineJobHistoryEntity = _waylineJobHistoryService.dto2Entity(job.get());
+                Optional<WaylineJobDTO> jobHistory = _waylineJobHistoryService.createWaylineJob(waylineJobHistoryEntity);
+                if(jobHistory.isPresent()) {
+                    log.info("任务执行成功,保存到历史记录。 {}", jobHistory);
+                }
+
+            }
+
+        }
     }
 
     /**
diff --git a/ard-sample/src/main/java/com/ard/sample/wayline/service/impl/WaylineJobHistoryServiceImpl.java b/ard-sample/src/main/java/com/ard/sample/wayline/service/impl/WaylineJobHistoryServiceImpl.java
new file mode 100644
index 0000000..82d72bc
--- /dev/null
+++ b/ard-sample/src/main/java/com/ard/sample/wayline/service/impl/WaylineJobHistoryServiceImpl.java
@@ -0,0 +1,235 @@
+package com.ard.sample.wayline.service.impl;
+
+import com.ard.route.service.IArdRouteInfoService;
+import com.ard.sample.common.model.Pagination;
+import com.ard.sample.common.model.PaginationData;
+import com.ard.sample.manage.model.dto.DeviceDTO;
+import com.ard.sample.manage.service.IDeviceService;
+import com.ard.sample.media.service.IFileService;
+import com.ard.sample.wayline.mapper.IWaylineJobHistoryMapper;
+import com.ard.sample.wayline.model.dto.WaylineJobDTO;
+import com.ard.sample.wayline.model.entity.WaylineJobEntity;
+import com.ard.sample.wayline.model.entity.WaylineJobHistoryEntity;
+import com.ard.sample.wayline.model.enums.WaylineJobStatusEnum;
+import com.ard.sample.wayline.service.IWaylineJobHistoryService;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.util.CollectionUtils;
+
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+@Service
+@Transactional
+@Slf4j
+@SuppressWarnings("all")
+public class WaylineJobHistoryServiceImpl implements IWaylineJobHistoryService {
+    @Autowired
+    private IWaylineJobHistoryMapper mapper;
+
+    @Autowired
+    private IFileService fileService;
+
+    @Autowired
+    private IDeviceService deviceService;
+
+    @Override
+    public Optional<WaylineJobDTO> createWaylineJob(WaylineJobHistoryEntity entity) {
+        int id = mapper.insert(entity);
+        if (id <= 0) {
+            return Optional.empty();
+        }
+        return Optional.ofNullable(this.entity2Dto(entity));
+    }
+
+    @Override
+    public Boolean updateJob(WaylineJobDTO dto) {
+        int id = mapper.update(dto2Entity(dto),
+                new LambdaQueryWrapper<WaylineJobHistoryEntity>()
+                        .eq(WaylineJobHistoryEntity::getJobId, dto.getJobId()
+                ));
+        return id>0;
+    }
+
+    @Override
+    public PaginationData<WaylineJobDTO> getJobsList(long page, long pageSize, Integer status,
+                                                     Long startTime, Long endTime, Integer taskType,
+                                                     String name, Boolean isComplete) {
+        Page<WaylineJobHistoryEntity> pageData = mapper.selectPage(
+                new Page<WaylineJobHistoryEntity>(page, pageSize),
+                new LambdaQueryWrapper<WaylineJobHistoryEntity>()
+                        .isNull(WaylineJobHistoryEntity::getParentId)
+                        // 实际开始时间筛选,允许为空,为空则不进行筛选
+                        .ge(startTime != null, WaylineJobHistoryEntity::getBeginTime, startTime)
+                        .le(endTime != null, WaylineJobHistoryEntity::getBeginTime, endTime)
+                        // 任务类型筛选,允许为空,为空则不进行筛选
+                        .eq(taskType != null, WaylineJobHistoryEntity::getTaskType, taskType)
+                        // 任务名称筛选,允许为空,为空则不进行筛选;....模糊查询
+                        .like(name != null, WaylineJobHistoryEntity::getName, "%" + name + "%")
+                        // 根据是否完成筛选,允许为空,为空则不进行筛选
+                        .eq(isComplete != null && isComplete, WaylineJobHistoryEntity::getStatus, 3)
+                        .ne(isComplete != null && !isComplete, WaylineJobHistoryEntity::getStatus, 3)
+                        // 任务状态筛选,允许为空,为空则不进行筛选
+                        .eq(status != null, WaylineJobHistoryEntity::getStatus, status)
+                        .orderByDesc(WaylineJobHistoryEntity::getBeginTime));
+        List<WaylineJobDTO> records = pageData.getRecords()
+                .stream()
+                .map(this::entity2Dto)
+                .peek(job -> {
+                    List<WaylineJobDTO> childOpt = this.getJobByJobParentId(job.getJobId());
+                    job.setChildren(childOpt);
+                })
+                .collect(Collectors.toList());
+
+        return new PaginationData<WaylineJobDTO>(records, new Pagination(pageData));
+    }
+
+    public List<WaylineJobDTO> getJobByJobParentId(String parentId) {
+        List<WaylineJobHistoryEntity> jobEntities = mapper.selectList(
+                new LambdaQueryWrapper<WaylineJobHistoryEntity>()
+                        .eq(WaylineJobHistoryEntity::getParentId, parentId));
+        if (CollectionUtils.isEmpty(jobEntities)) {
+            return Collections.emptyList(); // 返回不可变空列表,更安全
+        }
+        return jobEntities.stream()
+                .map(this::entity2Dto) // 调用原有的单个转换方法
+                .collect(Collectors.toList());
+    }
+
+    @Override
+    public Optional<WaylineJobDTO> getJobByJobId(String jobId) {
+        WaylineJobHistoryEntity jobEntities = mapper.selectOne(
+                new LambdaQueryWrapper<WaylineJobHistoryEntity>()
+                        .eq(WaylineJobHistoryEntity::getJobId, jobId));
+        return Optional.ofNullable(this.entity2Dto(jobEntities));
+    }
+
+    /**
+     * 将WaylineJobDTO对象转换为WaylineJobHistoryEntity对象
+     *
+     * @param dto WaylineJobDTO对象
+     * @return WaylineJobHistoryEntity对象
+     */
+    @Override
+    public WaylineJobHistoryEntity dto2Entity(WaylineJobDTO dto) {
+        WaylineJobHistoryEntity.WaylineJobHistoryEntityBuilder builder = WaylineJobHistoryEntity.builder();
+
+        if (dto == null) {
+            return builder.build();
+        }
+        if (Objects.nonNull(dto.getBeginTime())) {
+            builder.beginTime(dto.getBeginTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
+        }
+        if (Objects.nonNull(dto.getEndTime())) {
+            builder.endTime(dto.getEndTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
+        }
+        if (Objects.nonNull(dto.getExecuteTime())) {
+            builder.executeTime(dto.getExecuteTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
+        }
+        if (Objects.nonNull(dto.getCompletedTime())) {
+            builder.completedTime(dto.getCompletedTime().atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());
+        }
+        return builder.status(dto.getStatus())
+                .mediaCount(dto.getMediaCount())
+                .name(dto.getJobName())
+                .errorCode(dto.getCode())
+                .jobId(dto.getJobId())
+                .fileId(dto.getFileId())
+                .filename(dto.getFileName())
+                .dockSn(dto.getDockSn())
+                .taskType(dto.getTaskType())
+                .waylineType(dto.getWaylineType())
+                .username(dto.getUsername())
+                .rthAltitude(dto.getRthAltitude())
+                .outOfControlAction(dto.getOutOfControlAction())
+                .parentId(dto.getParentId())
+                .loopSum(dto.getLoopSum())
+                .build();
+    }
+
+    /**
+     * 将实体对象转换为数据传输对象
+     *
+     * @param entity WaylineJobEntity 实体对象
+     * @return WaylineJobDTO 数据传输对象
+     */
+    private WaylineJobDTO entity2Dto(WaylineJobHistoryEntity entity) {
+        if (entity == null) {
+            return null;
+        }
+        // 修改航线文件名称,从ard_route_info航线表中取;
+        String fileId = entity.getFileId();
+        WaylineJobDTO.WaylineJobDTOBuilder builder = WaylineJobDTO.builder()
+                .jobId(entity.getJobId())
+                .jobName(entity.getName())
+                .fileId(entity.getFileId())
+                .fileName(entity.getFilename())
+                .dockSn(entity.getDockSn())
+                .dockName(deviceService.getDeviceBySn(entity.getDockSn())
+                        .orElse(DeviceDTO.builder().build()).getNickname())
+                .username(entity.getUsername())
+                .status(entity.getStatus())
+                .code(entity.getErrorCode())
+                .beginTime(LocalDateTime.ofInstant(Instant.ofEpochMilli(entity.getBeginTime()), ZoneId.systemDefault()))
+                .endTime(Objects.nonNull(entity.getEndTime()) ?
+                        LocalDateTime.ofInstant(Instant.ofEpochMilli(entity.getEndTime()), ZoneId.systemDefault()) : null)
+                .executeTime(Objects.nonNull(entity.getExecuteTime()) ?
+                        LocalDateTime.ofInstant(Instant.ofEpochMilli(entity.getExecuteTime()), ZoneId.systemDefault()) : null)
+                .completedTime(WaylineJobStatusEnum.find(entity.getStatus()).getEnd() ?
+                        LocalDateTime.ofInstant(Instant.ofEpochMilli(entity.getUpdateTime()), ZoneId.systemDefault()) : null)
+                .taskType(entity.getTaskType())
+                .waylineType(entity.getWaylineType())
+                .rthAltitude(entity.getRthAltitude())
+                .rthMode(entity.getRthMode())
+                .outOfControlAction(entity.getOutOfControlAction())
+                .mediaCount(entity.getMediaCount())
+                .loopSum(entity.getLoopSum());
+
+
+//        if (Objects.nonNull(entity.getEndTime())) {
+//            builder.endTime(LocalDateTime.ofInstant(Instant.ofEpochMilli(entity.getEndTime()), ZoneId.systemDefault()));
+//        }
+//        if (WaylineJobStatusEnum.IN_PROGRESS.getVal() == entity.getStatus() && RedisOpsUtils.getExpire(entity.getJobId()) > 0) {
+//            EventsReceiver<WaylineTaskProgressReceiver> taskProgress = (EventsReceiver<WaylineTaskProgressReceiver>) RedisOpsUtils.get(RedisConst.WAYLINE_JOB_RUNNING_PREFIX + entity.getDockSn());
+//            if (Objects.nonNull(taskProgress.getOutput()) && Objects.nonNull(taskProgress.getOutput().getProgress())) {
+//                builder.progress(taskProgress.getOutput().getProgress().getPercent());
+//            }
+//        }
+//
+//        if (entity.getMediaCount() == 0) {
+//            return builder.build();
+//        }
+//
+//        // sync the number of media files
+//        String key = RedisConst.MEDIA_HIGHEST_PRIORITY_PREFIX + entity.getDockSn();
+//        String countKey = RedisConst.MEDIA_FILE_PREFIX + entity.getDockSn();
+//        Object mediaFileCount = RedisOpsUtils.hashGet(countKey, entity.getJobId());
+//        if (Objects.nonNull(mediaFileCount)) {
+//            builder.uploadedCount(((MediaFileCountDTO) mediaFileCount).getUploadedCount())
+//                    .uploading(RedisOpsUtils.checkExist(key) && entity.getJobId().equals(((MediaFileCountDTO) RedisOpsUtils.get(key)).getJobId()));
+//            return builder.build();
+//        }
+
+//        int uploadedSize = fileService.getFilesByWorkspaceAndJobId(entity.getJobId()).size();
+//        // All media for this job have been uploaded.
+//        if (uploadedSize >= entity.getMediaCount()) {
+//            return builder.uploadedCount(uploadedSize).build();
+//        }
+//        RedisOpsUtils.hashSet(countKey, entity.getJobId(),
+//                MediaFileCountDTO.builder()
+//                        .jobId(entity.getJobId())
+//                        .mediaCount(entity.getMediaCount())
+//                        .uploadedCount(uploadedSize).build());
+        return builder.build();
+    }
+}

--
Gitblit v1.9.3