liusuyi
2025-12-23 c7fa563f9a951c647725c3d0e02b7bce366d23ea
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
package com.ard.work.service.impl;
 
import cn.hutool.core.util.SerializeUtil;
import com.ard.work.domain.cache.UavStateCache;
import com.ard.work.domain.cache.UavTelemetryDataCache;
import com.ard.work.domain.dto.UavFlightStateDTO;
import com.ard.work.domain.dto.state.UavState;
import com.ard.work.domain.dto.telemetry.TelemetryData;
import com.ard.work.domain.entity.ArdFlightTaskPart;
import com.ard.work.mapper.ArdFlightTaskPartMapper;
import com.ard.work.service.IArdFlightTaskPartService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
/**
 * 轨迹分片处理业务
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class ArdFlightTaskPartServiceImpl implements IArdFlightTaskPartService {
 
    private final ArdFlightTaskPartMapper ardflightTaskPartMapper;
 
    /**
     * 缓存:boxSn -> 当前分片数据
     */
    private final ConcurrentHashMap<String, List<UavFlightStateDTO>> cacheMap = new ConcurrentHashMap<>();
 
    /**
     * 分片保存阈值条数
     */
    private static final int CHUNK_SIZE = 300;
 
    /**
     * 定时任务:每秒读取一次设备状态数据
     * 注意:这里模拟读取状态,实际应该调用设备接口或缓存
     */
    @Scheduled(fixedRate = 1000)
    public void readTelemetryTask() {
        long now = System.currentTimeMillis();
        UavTelemetryDataCache.forEach((boxSn, telemetryData) -> {
 
            // 1️⃣ 从状态缓存里取当前状态
            UavState uavState = UavStateCache.get(boxSn);
            if (uavState == null) {
                return; // 状态不存在,不记录
            }
 
            // 2️⃣ 使用遥测自身时间戳判断是否还活着
            long dataTs = telemetryData.getTimestamp(); // 设备时间 / 服务器接收时间
            if (now - dataTs > 3000) {
                return; // 超过 3 秒没有新遥测 → 认为已离线或冻结,不记录
            }
 
            // 3️⃣ 飞行状态必须是飞行中
            if(uavState.getFlightControllerState().getFlightStatus()!=2)
            {
                return; // 飞行状态不是飞行中,不记录
            }
 
            UavFlightStateDTO dto = new UavFlightStateDTO(
                    uavState,
                    telemetryData,
                    dataTs
            );
            addTelemetryData(dto);
        });
    }
 
    /**
     * 添加一条遥测数据到缓存
     */
    @Override
    public void addTelemetryData(UavFlightStateDTO uavFlightStateDTO) {
        String boxSn = uavFlightStateDTO.getTelemetryData().getBoxSn();
        List<UavFlightStateDTO> list = cacheMap.computeIfAbsent(boxSn, k -> new ArrayList<>());
        list.add(uavFlightStateDTO);
 
        // 当缓存条数达到阈值 或 时间间隔超过 CHUNK_INTERVAL 时,分片保存
        if (list.size() >= CHUNK_SIZE) {
            saveChunk(boxSn); // taskId 可根据你的业务传入
        }
    }
 
    /**
     * 分片保存缓存数据到数据库
     */
    @Async
    public void saveChunk(String boxSn) {
        List<UavFlightStateDTO> dataList = cacheMap.getOrDefault(boxSn, new ArrayList<>());
        if (dataList.isEmpty()) {
            return;
        }
 
        try {
            byte[] blobData = SerializeUtil.serialize(dataList);
 
            ArdFlightTaskPart part = ArdFlightTaskPart.builder()
                    .boxSn(boxSn)
                    .count(dataList.size())
                    .dataPart(blobData)
                    .build();
            // 使用 list 中第一条数据的时间作为 createTime
            long firstTimeMillis = dataList.get(0).getTimestamp();
            Date createTime = new Date(firstTimeMillis);
            part.setCreateTime(createTime);
            ardflightTaskPartMapper.insert(part);
 
            log.info("设备 {} 分片保存 {} 条飞行数据到数据库成功", boxSn, dataList.size());
 
            // 清空缓存,更新上次保存时间
            cacheMap.put(boxSn, new ArrayList<>());
 
        } catch (Exception e) {
            log.error("设备 {} 分片保存飞行数据失败", boxSn, e);
        }
    }
 
 
    /**
     * 将剩余缓存分片保存
     */
    @Override
    public void flushRemaining(String boxSn) {
        List<UavFlightStateDTO> dataList = cacheMap.getOrDefault(boxSn, new ArrayList<>());
        if (!dataList.isEmpty()) {
            saveChunk(boxSn);
        }
    }
 
    /**
     * 获取完整航线轨迹(合并所有分片)
     */
    @Override
    public List<UavFlightStateDTO> getCompleteFlightData(ArdFlightTaskPart ardFlightTaskPart) {
        List<UavFlightStateDTO> allData = new ArrayList<>();
        String boxSn = ardFlightTaskPart.getBoxSn();
        List<ArdFlightTaskPart> parts = ardflightTaskPartMapper.selectByTaskStartTime(ardFlightTaskPart);
        for (ArdFlightTaskPart part : parts) {
            try {
                List<UavFlightStateDTO> partData = SerializeUtil.deserialize(part.getDataPart());
                allData.addAll(partData);
            } catch (Exception e) {
                log.error("反序列化 boxSn={} 分片数据失败", boxSn, e);
            }
        }
        // 按 telemetryData.timestamp 升序排序
        allData.sort(Comparator.comparingLong(dto -> dto.getTelemetryData().getTimestamp()));
 
        return allData;
    }
}