/* * 【机场1】从 dock2 原类 com.ard.sample.manage.service.impl.WaylineTrajectoryServiceImpl 复制/独立适配(DJI Dock 1 / M30)。 * 与 dock2 原类同服务共存;本文件内的适配差异以「// 机场1:」内联注释标出。 */ package com.ard.sample.dock1.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.dock1.manage.service.IWaylineTrajectoryService; import com.ard.sample.wayline.model.dto.WaylineJobDTO; import com.ard.sample.wayline.model.dto.WaylineTaskProgressReceiver; import com.ard.sample.dock1.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; import com.ard.sample.manage.service.impl.WaylineTrajectoryServiceImpl; @Service @Slf4j public class Dock1WaylineTrajectoryServiceImpl implements IWaylineTrajectoryService { @Resource private IWaylineTrajectoryMapper _waylineTrajectoryMapper; @Autowired private IWaylineJobService _waylineJobService; @Autowired protected ObjectMapper mapper; /** * 轨迹点列表缓存 */ private final ConcurrentHashMap> cacheTrajectoryMap = new ConcurrentHashMap<>(); /** * 任务状态数据缓存 */ private final ConcurrentHashMap> cacheTaskMap = new ConcurrentHashMap<>(); /** * 给数据加个进度 */ // private final ConcurrentHashMap 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 listTrajectory = cacheTrajectoryMap.computeIfAbsent(sn, k -> new ArrayList<>()); List 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")) { // 飞行轨迹数据 if(!listTask.isEmpty()) { listTrajectory.add(dataJson); log.info("SN:{} 轨迹点数据添加到列表缓存,当前列表数量:{}", sn, listTrajectory.size()); } else { cacheTrajectoryMap.put(sn, new ArrayList<>()); log.info("SN:{} 任务状态数据为空,当前无任务,不保存轨迹点数据", sn); } // log.info("SN:{} 轨迹点数据:{}", sn, dataJson); } if (type.equals("1")) { // 任务状态数据 listTask.add(dataJson); log.info("SN:{} 任务状态数据添加到列表缓存,当前列表数量:{}", sn, listTask.size()); } // jobid、status字段赋值 if (!listTask.isEmpty()) { String json = listTask.get(listTask.size() - 1); log.info("SN:{} 当前任务记录最后一条数据:{}", sn, json); try { EventsReceiver eventsReceiver = mapper.readValue(json, new TypeReference>() { }); jobId = eventsReceiver.getOutput().getExt().getFlightId(); isOK = eventsReceiver.getOutput().getStatus(); log.info("SN:{} 当前任务jobId:{},任务执行进度isOK:{}", sn, jobId, isOK); } catch (Exception e) { log.error("SN:{} 解析任务状态JSON失败: {}", sn, e.getMessage()); log.error("SN:{} JSON内容: {}", sn, json); } } else { // 当前任务状态数据为空时,清空缓存 log.info("SN:{} 任务状态数据为空,当前无任务清空缓存", sn); // 清空缓存 cacheTrajectoryMap.put(sn, new ArrayList<>()); cacheTaskMap.put(sn, new ArrayList<>()); return; } // listTrajectory.size() >= CHUNK_SIZE || if (listTask.size() >= CHUNK_SIZE) { // 分片保存 save(sn, jobId, "0"); log.info("SN:{} 当前任务保存成功,jobId:{}, listTrajectory.size():{},listTask.size():{}", sn, jobId, listTrajectory.size(), listTask.size()); // log.info("当前任务保存成功,jobId:{},进度:{}",jobId , progress); } else { // 判断任务状态json中的status字段,如果为OK,则保存数据,一条完整的航线数据结束。 try { if (isOK.equals("ok")) { String progress = "ok"; if (getWaylineTrajectoryByProgress(jobId, progress)) { log.info("SN:{} 当前任务已完成,不再保存数据!jobId:{},progress:{}", sn, jobId, progress); // 清空缓存 cacheTrajectoryMap.put(sn, new ArrayList<>()); cacheTaskMap.put(sn, new ArrayList<>()); return; } save(sn, jobId, progress); log.info("SN:{} 当前任务保存成功,jobId:{},进度:{}", sn,jobId , progress); } } catch (Exception e) { log.error("SN:{} json解析异常!{}", sn, e.getMessage()); } } } @Async public void save(String sn, String JobId, String progress) { // List trajectoryList = cacheTrajectoryMap.get(sn); // List taskList = cacheTaskMap.get(sn); // String progress = cacheProgress.get(sn); // 关键修复:原子性remove获取缓存(拿到当前所有数据)+ 清空缓存,避免异步竞态 List trajectoryList = cacheTrajectoryMap.remove(sn); List taskList = cacheTaskMap.remove(sn); // 处理remove返回null的情况(缓存中无该sn) trajectoryList = trajectoryList == null ? new ArrayList<>() : trajectoryList; taskList = taskList == null ? new ArrayList<>() : taskList; // 序列化前打印关键日志(确认拿到的数据) log.info("SN:{} save方法原子性获取缓存 - 轨迹数据size:{},任务数据size:{}", sn, trajectoryList.size(), taskList.size()); // 空数据直接返回 if (trajectoryList.isEmpty() && taskList.isEmpty()) { log.warn("SN:{} 原子性获取的缓存数据为空,无需保存!jobId:{}", sn, JobId); return; } try { log.info("SN:{} 开始保存轨迹点列表数据!jobId:{},轨迹数量:{},任务状态数量:{}", sn, JobId, trajectoryList.size(), taskList.size()); byte[] blobTrajectory = SerializeUtil.serialize(trajectoryList); byte[] blobTask = SerializeUtil.serialize(taskList); log.info("SN:{} 序列化结果 - 轨迹字节长度:{},任务字节长度:{}", sn, blobTrajectory.length, blobTask.length); Optional waylineJobDTO = _waylineJobService.getJobByJobId(JobId); WaylineTrajectoryEntity TrajectoryEntity = new WaylineTrajectoryEntity(); if(waylineJobDTO.isPresent()) { 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("SN:{} 保存数据异常!jobId:{}, 异常信息:{}", sn, JobId, e.getMessage(), e); // 异常时把数据放回缓存(避免数据丢失) if (!trajectoryList.isEmpty()) { cacheTrajectoryMap.computeIfAbsent(sn, k -> new ArrayList<>()).addAll(trajectoryList); } if (!taskList.isEmpty()) { cacheTaskMap.computeIfAbsent(sn, k -> new ArrayList<>()).addAll(taskList); } } } /** * 返回航线轨迹完整的数据,拼接的轨迹点列表和任务状态数据。 * * @param jobId 任务ID * @return 航线轨迹完整的数据 */ @Override public WaylineTrajectoryDTO getWayLineTrajectorybyJobID(String jobId) { List waylineTrajectoryEntityList = new ArrayList<>(_waylineTrajectoryMapper.selectList(new LambdaQueryWrapper() .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 waylineTrajectoryEntityList = new ArrayList<>(_waylineTrajectoryMapper.selectList(new LambdaQueryWrapper() .eq(jobId != null, WaylineTrajectoryEntity::getJobId, jobId) .eq(progress != null, WaylineTrajectoryEntity::getProgress, progress) .orderByAsc(WaylineTrajectoryEntity::getId))); return !waylineTrajectoryEntityList.isEmpty(); } public WaylineTrajectoryDTO TrajectoryEntityConvertToDTO(List entity) { if (entity == null) { return null; } WaylineTrajectoryDTO dto = new WaylineTrajectoryDTO(); try { 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)); } } catch (Exception e) { log.error("获取任务id轨迹点列表数据失败!{}", e.getMessage()); } return dto; } }