2025-12-24 zwy 增加计划任务轨迹功能,将在计划任务执行期间的飞行轨迹数据、任务状态数据保存;同时将计划任务数据保存到计划任务历史表中保存。并提供完整的轨迹提取接口。
10 files added
2 files modified
877 ■■■■■ changed files
ard-admin/src/main/java/com/ard/web/controller/sample/wayline/WaylineTrajectoryController.java 40 ●●●●● patch | view | raw | blame | history
ard-sample/src/main/java/com/ard/sample/manage/mapper/IWaylineTrajectoryMapper.java 12 ●●●●● patch | view | raw | blame | history
ard-sample/src/main/java/com/ard/sample/manage/model/dto/WaylineTrajectoryDTO.java 69 ●●●●● patch | view | raw | blame | history
ard-sample/src/main/java/com/ard/sample/manage/model/entity/WaylineTrajectoryEntity.java 83 ●●●●● patch | view | raw | blame | history
ard-sample/src/main/java/com/ard/sample/manage/service/IWaylineTrajectoryService.java 24 ●●●●● patch | view | raw | blame | history
ard-sample/src/main/java/com/ard/sample/manage/service/impl/DeviceOSDServiceImpl.java 7 ●●●●● patch | view | raw | blame | history
ard-sample/src/main/java/com/ard/sample/manage/service/impl/WaylineTrajectoryServiceImpl.java 213 ●●●●● patch | view | raw | blame | history
ard-sample/src/main/java/com/ard/sample/wayline/mapper/IWaylineJobHistoryMapper.java 10 ●●●●● patch | view | raw | blame | history
ard-sample/src/main/java/com/ard/sample/wayline/model/entity/WaylineJobHistoryEntity.java 94 ●●●●● patch | view | raw | blame | history
ard-sample/src/main/java/com/ard/sample/wayline/service/IWaylineJobHistoryService.java 57 ●●●●● patch | view | raw | blame | history
ard-sample/src/main/java/com/ard/sample/wayline/service/impl/FlightTaskServiceImpl.java 33 ●●●●● patch | view | raw | blame | history
ard-sample/src/main/java/com/ard/sample/wayline/service/impl/WaylineJobHistoryServiceImpl.java 235 ●●●●● patch | view | raw | blame | history
ard-admin/src/main/java/com/ard/web/controller/sample/wayline/WaylineTrajectoryController.java
New file
@@ -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());
        }
    }
}
ard-sample/src/main/java/com/ard/sample/manage/mapper/IWaylineTrajectoryMapper.java
New file
@@ -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> {
}
ard-sample/src/main/java/com/ard/sample/manage/model/dto/WaylineTrajectoryDTO.java
New file
@@ -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;
}
ard-sample/src/main/java/com/ard/sample/manage/model/entity/WaylineTrajectoryEntity.java
New file
@@ -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;
}
ard-sample/src/main/java/com/ard/sample/manage/service/IWaylineTrajectoryService.java
New file
@@ -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);
}
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());
        }
ard-sample/src/main/java/com/ard/sample/manage/service/impl/WaylineTrajectoryServiceImpl.java
New file
@@ -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;
    }
}
ard-sample/src/main/java/com/ard/sample/wayline/mapper/IWaylineJobHistoryMapper.java
New file
@@ -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> {
}
ard-sample/src/main/java/com/ard/sample/wayline/model/entity/WaylineJobHistoryEntity.java
New file
@@ -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;
}
ard-sample/src/main/java/com/ard/sample/wayline/service/IWaylineJobHistoryService.java
New file
@@ -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);
}
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);
                }
            }
        }
    }
    /**
ard-sample/src/main/java/com/ard/sample/wayline/service/impl/WaylineJobHistoryServiceImpl.java
New file
@@ -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();
    }
}