diff --git a/pom.xml b/pom.xml
index 8f498a1..205bf5b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -117,6 +117,12 @@
caffeine
+
+
+ org.springframework.boot
+ spring-boot-starter-websocket
+
+
org.springframework.integration
diff --git a/src/main/java/com/ruoyi/device/config/DeviceCacheConfig.java b/src/main/java/com/ruoyi/device/config/DeviceCacheConfig.java
index 972cbbb..bd235b5 100644
--- a/src/main/java/com/ruoyi/device/config/DeviceCacheConfig.java
+++ b/src/main/java/com/ruoyi/device/config/DeviceCacheConfig.java
@@ -35,6 +35,8 @@ public class DeviceCacheConfig {
public static final String PAYLOAD_CACHE = "payload";
public static final String DOCK_AIRCRAFT_CACHE = "dockAircraft";
public static final String AIRCRAFT_PAYLOAD_CACHE = "aircraftPayload";
+ public static final String THINGSBOARD_ATTRIBUTES_CACHE = "thingsboardAttributes";
+ public static final String THINGSBOARD_TELEMETRY_CACHE = "thingsboardTelemetry";
/**
* 配置缓存管理器
@@ -68,6 +70,12 @@ public class DeviceCacheConfig {
cacheConfigurations.put(DOCK_AIRCRAFT_CACHE, defaultConfig.entryTtl(Duration.ofMinutes(15)));
cacheConfigurations.put(AIRCRAFT_PAYLOAD_CACHE, defaultConfig.entryTtl(Duration.ofMinutes(15)));
+ // ThingsBoard 设备属性:90秒(属性数据,变化较少)
+ cacheConfigurations.put(THINGSBOARD_ATTRIBUTES_CACHE, defaultConfig.entryTtl(Duration.ofSeconds(90)));
+
+ // ThingsBoard 设备遥测:15秒(遥测数据,实时性要求高)
+ cacheConfigurations.put(THINGSBOARD_TELEMETRY_CACHE, defaultConfig.entryTtl(Duration.ofSeconds(15)));
+
return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(defaultConfig)
.withInitialCacheConfigurations(cacheConfigurations)
diff --git a/src/main/java/com/ruoyi/device/config/WebSocketConfig.java b/src/main/java/com/ruoyi/device/config/WebSocketConfig.java
new file mode 100644
index 0000000..55f30f9
--- /dev/null
+++ b/src/main/java/com/ruoyi/device/config/WebSocketConfig.java
@@ -0,0 +1,14 @@
+package com.ruoyi.device.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.socket.server.standard.ServerEndpointExporter;
+
+@Configuration
+public class WebSocketConfig {
+
+ @Bean
+ public ServerEndpointExporter serverEndpointExporter() {
+ return new ServerEndpointExporter();
+ }
+}
diff --git a/src/main/java/com/ruoyi/device/controller/StaticsController.java b/src/main/java/com/ruoyi/device/controller/StaticsController.java
index 5e3d6f1..c39cfae 100644
--- a/src/main/java/com/ruoyi/device/controller/StaticsController.java
+++ b/src/main/java/com/ruoyi/device/controller/StaticsController.java
@@ -53,9 +53,51 @@ public class StaticsController extends BaseController
*/
@GetMapping
public R getStatistics()
+ {
+ return R.ok(buildDjiStatisticsVO());
+ }
+
+
+
+
+
+ @GetMapping("/dji")
+ public R getDjiStatistics()
+ {
+ return R.ok(buildDjiStatisticsVO());
+ }
+
+
+ @GetMapping("/th")
+ public R getThStatistics()
{
StatisticsVO vo = new StatisticsVO();
+ // 机场统计
+ vo.setDockCount(0);
+ vo.setIdleDockCount(0);
+ vo.setWorkingDockCount(0);
+ vo.setDebuggingDockCount(0);
+ vo.setOfflineDockCount(0);
+
+ // 无人机统计
+ vo.setAircraftCount(0);
+ vo.setPowerOnInCabinCount(0);
+ vo.setPowerOffInCabinCount(0);
+ vo.setInMissionCount(0);
+ vo.setDebuggingAircraftCount(0);
+ vo.setOfflineAircraftCount(0);
+
+ // 挂载统计
+ vo.setPayloadCount(0);
+ vo.setOfflinePayloadCount(0);
+
+ return R.ok(vo);
+ }
+
+ private StatisticsVO buildDjiStatisticsVO (){
+ StatisticsVO vo = new StatisticsVO();
+
// 获取所有机场
List docks = dockService.selectDockList(new DockDTO());
vo.setDockCount(docks != null ? docks.size() : 0);
@@ -153,140 +195,6 @@ public class StaticsController extends BaseController
// 统计离线挂载数量(暂时设置为0,因为挂载状态需要从实时数据获取)
vo.setOfflinePayloadCount(0);
- return R.ok(vo);
- }
-
-
- @GetMapping("/dji")
- public R getDjiStatistics()
- {
- StatisticsVO vo = new StatisticsVO();
-
- // 获取所有机场
- List docks = dockService.selectDockList(new DockDTO());
- vo.setDockCount(docks != null ? docks.size() : 0);
-
- // 批量获取机场详情 - 优化:从N次查询减少到1次批量查询
- Map dockDetailsMap = null;
- if (docks != null && !docks.isEmpty()) {
- List dockIds = docks.stream()
- .map(DockDTO::getDockId)
- .collect(Collectors.toList());
- dockDetailsMap = bufferDeviceService.getDockDetailsByIds(dockIds);
- }
-
- // 统计各状态机场数量
- int idleCount = 0;
- int workingCount = 0;
- int debuggingCount = 0;
- int offlineCount = 0;
-
- if (docks != null && dockDetailsMap != null) {
- for (DockDTO dock : docks) {
- DockDetailDTO dockDetail = dockDetailsMap.get(dock.getDockId());
- if (dockDetail != null && dockDetail.getDockStatus() != null) {
- String status = dockDetail.getDockStatus();
- if (DockStatusEnum.IDLE.getCode().equals(status)) {
- idleCount++;
- } else if (DockStatusEnum.WORKING.getCode().equals(status)) {
- workingCount++;
- } else if (DockStatusEnum.Debugging.getCode().equals(status)) {
- debuggingCount++;
- } else {
- offlineCount++;
- }
- }
- }
- }
-
- vo.setIdleDockCount(idleCount);
- vo.setWorkingDockCount(workingCount);
- vo.setDebuggingDockCount(debuggingCount);
- vo.setOfflineDockCount(offlineCount);
-
- // 获取所有无人机
- List aircrafts = aircraftService.selectAircraftList(new AircraftDTO());
- vo.setAircraftCount(aircrafts != null ? aircrafts.size() : 0);
-
- // 批量获取无人机详情 - 优化:从N次查询减少到1次批量查询
- Map aircraftDetailsMap = null;
- if (aircrafts != null && !aircrafts.isEmpty()) {
- List aircraftIds = aircrafts.stream()
- .map(AircraftDTO::getAircraftId)
- .collect(Collectors.toList());
- aircraftDetailsMap = bufferDeviceService.getAircraftDetailsByIds(aircraftIds);
- }
-
- // 统计各状态无人机数量
- int powerOnInCabinCount = 0;
- int powerOffInCabinCount = 0;
- int inMissionCount = 0;
- int debuggingAircraftCount = 0;
- int offlineAircraftCount = 0;
-
- if (aircrafts != null && aircraftDetailsMap != null) {
- for (AircraftDTO aircraft : aircrafts) {
- AircraftDetailDTO aircraftDetail = aircraftDetailsMap.get(aircraft.getAircraftId());
- if (aircraftDetail != null && aircraftDetail.getAircraftStatus() != null) {
- String status = aircraftDetail.getAircraftStatus();
- if (AircraftStatusEnum.POWER_ON_IN_CABIN.getCode().equals(status)) {
- powerOnInCabinCount++;
- } else if (AircraftStatusEnum.POWER_OFF_IN_CABIN.getCode().equals(status)) {
- powerOffInCabinCount++;
- } else if (AircraftStatusEnum.IN_MISSION.getCode().equals(status)) {
- inMissionCount++;
- } else if (AircraftStatusEnum.DEBUGGING.getCode().equals(status)) {
- debuggingAircraftCount++;
- } else if (AircraftStatusEnum.OFFLINE.getCode().equals(status)) {
- offlineAircraftCount++;
- } else {
- offlineAircraftCount++;
- }
- }
- }
- }
-
- vo.setPowerOnInCabinCount(powerOnInCabinCount);
- vo.setPowerOffInCabinCount(powerOffInCabinCount);
- vo.setInMissionCount(inMissionCount);
- vo.setDebuggingAircraftCount(debuggingAircraftCount);
- vo.setOfflineAircraftCount(offlineAircraftCount);
-
- // 获取所有挂载
- List payloads = payloadService.selectPayloadList(new PayloadDTO());
- vo.setPayloadCount(payloads != null ? payloads.size() : 0);
-
- // 统计离线挂载数量(暂时设置为0,因为挂载状态需要从实时数据获取)
- vo.setOfflinePayloadCount(0);
-
- return R.ok(vo);
- }
-
-
- @GetMapping("/th")
- public R getThStatistics()
- {
- StatisticsVO vo = new StatisticsVO();
-
- // 机场统计
- vo.setDockCount(0);
- vo.setIdleDockCount(0);
- vo.setWorkingDockCount(0);
- vo.setDebuggingDockCount(0);
- vo.setOfflineDockCount(0);
-
- // 无人机统计
- vo.setAircraftCount(0);
- vo.setPowerOnInCabinCount(0);
- vo.setPowerOffInCabinCount(0);
- vo.setInMissionCount(0);
- vo.setDebuggingAircraftCount(0);
- vo.setOfflineAircraftCount(0);
-
- // 挂载统计
- vo.setPayloadCount(0);
- vo.setOfflinePayloadCount(0);
-
- return R.ok(vo);
+ return vo;
}
}
\ No newline at end of file
diff --git a/src/main/java/com/ruoyi/device/domain/api/IThingsBoardDomain.java b/src/main/java/com/ruoyi/device/domain/api/IThingsBoardDomain.java
index 2c5903d..1a8074c 100644
--- a/src/main/java/com/ruoyi/device/domain/api/IThingsBoardDomain.java
+++ b/src/main/java/com/ruoyi/device/domain/api/IThingsBoardDomain.java
@@ -91,4 +91,20 @@ public interface IThingsBoardDomain {
* @return 子设备ID列表,如果网关没有子设备则返回空列表
*/
List getGatewayChildDevices(String gatewayDeviceId);
+
+ /**
+ * 清除设备属性缓存
+ * 使 getDeviceAttributes 方法的缓存失效
+ *
+ * @param deviceId 设备ID
+ */
+ void evictDeviceAttributesCache(String deviceId);
+
+ /**
+ * 清除设备遥测数据缓存
+ * 使 getDeviceTelemetry 方法的缓存失效
+ *
+ * @param deviceId 设备ID
+ */
+ void evictDeviceTelemetryCache(String deviceId);
}
\ No newline at end of file
diff --git a/src/main/java/com/ruoyi/device/domain/impl/ThingsBoardDomainImpl.java b/src/main/java/com/ruoyi/device/domain/impl/ThingsBoardDomainImpl.java
index d464842..c293af7 100644
--- a/src/main/java/com/ruoyi/device/domain/impl/ThingsBoardDomainImpl.java
+++ b/src/main/java/com/ruoyi/device/domain/impl/ThingsBoardDomainImpl.java
@@ -1,9 +1,7 @@
package com.ruoyi.device.domain.impl;
-import com.github.benmanes.caffeine.cache.Cache;
-import com.github.benmanes.caffeine.cache.Caffeine;
-import com.github.benmanes.caffeine.cache.Expiry;
+import com.ruoyi.device.config.DeviceCacheConfig;
import com.ruoyi.device.domain.api.IThingsBoardDomain;
import com.ruoyi.device.domain.model.thingsboard.*;
import com.ruoyi.device.domain.model.thingsboard.constants.DeviceAttributes;
@@ -11,6 +9,8 @@ import com.ruoyi.device.domain.model.thingsboard.constants.DeviceTelemetry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Cacheable;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.thingsboard.rest.client.RestClient;
@@ -30,9 +30,7 @@ import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
-import java.util.Random;
import java.util.UUID;
-import java.util.concurrent.TimeUnit;
/**
* ThingsBoard设备服务实现类
@@ -46,10 +44,6 @@ public class ThingsBoardDomainImpl implements IThingsBoardDomain {
private final RestClient client;
private final int pageSize;
- private final Random random = new Random();
-
- private final Cache attributesCache;
- private final Cache telemetryCache;
/**
* 构造函数 - Spring 会自动装配
@@ -60,48 +54,6 @@ public class ThingsBoardDomainImpl implements IThingsBoardDomain {
@Value("${thingsboard.page-size:10}") int pageSize) {
this.client = clientManager.getClient();
this.pageSize = pageSize;
-
- this.attributesCache = Caffeine.newBuilder()
- .expireAfter(new Expiry() {
- @Override
- public long expireAfterCreate(String key, AttributeMap value, long currentTime) {
- long randomSeconds = 60 + random.nextInt(61);
- return TimeUnit.SECONDS.toNanos(randomSeconds);
- }
-
- @Override
- public long expireAfterUpdate(String key, AttributeMap value, long currentTime, long currentDuration) {
- long randomSeconds = 60 + random.nextInt(61);
- return TimeUnit.SECONDS.toNanos(randomSeconds);
- }
-
- @Override
- public long expireAfterRead(String key, AttributeMap value, long currentTime, long currentDuration) {
- return currentDuration;
- }
- })
- .build();
-
- this.telemetryCache = Caffeine.newBuilder()
- .expireAfter(new Expiry() {
- @Override
- public long expireAfterCreate(String key, TelemetryMap value, long currentTime) {
- long randomSeconds = 12 + random.nextInt(7);
- return TimeUnit.SECONDS.toNanos(randomSeconds);
- }
-
- @Override
- public long expireAfterUpdate(String key, TelemetryMap value, long currentTime, long currentDuration) {
- long randomSeconds = 12 + random.nextInt(7);
- return TimeUnit.SECONDS.toNanos(randomSeconds);
- }
-
- @Override
- public long expireAfterRead(String key, TelemetryMap value, long currentTime, long currentDuration) {
- return currentDuration;
- }
- })
- .build();
}
@Override
@@ -138,67 +90,65 @@ public class ThingsBoardDomainImpl implements IThingsBoardDomain {
}
@Override
+ @Cacheable(value = DeviceCacheConfig.THINGSBOARD_ATTRIBUTES_CACHE, key = "#deviceId", unless = "#result == null || #result.isEmpty()")
public AttributeMap getDeviceAttributes(String deviceId) {
- return attributesCache.get(deviceId, id -> {
- AttributeMap attributeMap = new AttributeMap();
+ AttributeMap attributeMap = new AttributeMap();
- try {
- DeviceId deviceIdObj = new DeviceId(UUID.fromString(id));
+ try {
+ DeviceId deviceIdObj = new DeviceId(UUID.fromString(deviceId));
- List attributeKeys = client.getAttributeKeys(deviceIdObj);
- if (attributeKeys == null || attributeKeys.isEmpty()) {
- log.debug("设备 {} 没有属性", id);
- return attributeMap;
- }
-
- List attributeKvEntries = client.getAttributeKvEntries(deviceIdObj, attributeKeys);
- if (attributeKvEntries == null || attributeKvEntries.isEmpty()) {
- log.debug("设备 {} 的属性值为空", id);
- return attributeMap;
- }
-
- for (AttributeKvEntry entry : attributeKvEntries) {
- parseAndPutAttribute(attributeMap, entry);
- }
-
- } catch (Exception e) {
- log.error("获取设备属性失败: deviceId={}, error={}", id, e.getMessage(), e);
+ List attributeKeys = client.getAttributeKeys(deviceIdObj);
+ if (attributeKeys == null || attributeKeys.isEmpty()) {
+ log.debug("设备 {} 没有属性", deviceId);
+ return attributeMap;
}
- return attributeMap;
- });
+ List attributeKvEntries = client.getAttributeKvEntries(deviceIdObj, attributeKeys);
+ if (attributeKvEntries == null || attributeKvEntries.isEmpty()) {
+ log.debug("设备 {} 的属性值为空", deviceId);
+ return attributeMap;
+ }
+
+ for (AttributeKvEntry entry : attributeKvEntries) {
+ parseAndPutAttribute(attributeMap, entry);
+ }
+
+ } catch (Exception e) {
+ log.error("获取设备属性失败: deviceId={}, error={}", deviceId, e.getMessage(), e);
+ }
+
+ return attributeMap;
}
@Override
+ @Cacheable(value = DeviceCacheConfig.THINGSBOARD_TELEMETRY_CACHE, key = "#deviceId", unless = "#result == null || #result.isEmpty()")
public TelemetryMap getDeviceTelemetry(String deviceId) {
- return telemetryCache.get(deviceId, id -> {
- TelemetryMap telemetryMap = new TelemetryMap();
+ TelemetryMap telemetryMap = new TelemetryMap();
- try {
- DeviceId deviceIdObj = new DeviceId(UUID.fromString(id));
+ try {
+ DeviceId deviceIdObj = new DeviceId(UUID.fromString(deviceId));
- List timeseriesKeys = client.getTimeseriesKeys(deviceIdObj);
- if (timeseriesKeys == null || timeseriesKeys.isEmpty()) {
- log.debug("设备 {} 没有遥测数据", id);
- return telemetryMap;
- }
-
- List latestTimeseries = client.getLatestTimeseries(deviceIdObj, timeseriesKeys);
- if (latestTimeseries == null || latestTimeseries.isEmpty()) {
- log.debug("设备 {} 的遥测数据为空", id);
- return telemetryMap;
- }
-
- for (TsKvEntry entry : latestTimeseries) {
- parseAndPutTelemetry(telemetryMap, entry);
- }
-
- } catch (Exception e) {
- log.error("获取设备遥测数据失败: deviceId={}, error={}", id, e.getMessage(), e);
+ List timeseriesKeys = client.getTimeseriesKeys(deviceIdObj);
+ if (timeseriesKeys == null || timeseriesKeys.isEmpty()) {
+ log.debug("设备 {} 没有遥测数据", deviceId);
+ return telemetryMap;
}
- return telemetryMap;
- });
+ List latestTimeseries = client.getLatestTimeseries(deviceIdObj, timeseriesKeys);
+ if (latestTimeseries == null || latestTimeseries.isEmpty()) {
+ log.debug("设备 {} 的遥测数据为空", deviceId);
+ return telemetryMap;
+ }
+
+ for (TsKvEntry entry : latestTimeseries) {
+ parseAndPutTelemetry(telemetryMap, entry);
+ }
+
+ } catch (Exception e) {
+ log.error("获取设备遥测数据失败: deviceId={}, error={}", deviceId, e.getMessage(), e);
+ }
+
+ return telemetryMap;
}
@@ -379,5 +329,15 @@ public class ThingsBoardDomainImpl implements IThingsBoardDomain {
}
}
+ @Override
+ @CacheEvict(value = DeviceCacheConfig.THINGSBOARD_ATTRIBUTES_CACHE, key = "#deviceId")
+ public void evictDeviceAttributesCache(String deviceId) {
+ // 空实现,仅用于清除缓存
+ }
+ @Override
+ @CacheEvict(value = DeviceCacheConfig.THINGSBOARD_TELEMETRY_CACHE, key = "#deviceId")
+ public void evictDeviceTelemetryCache(String deviceId) {
+ // 空实现,仅用于清除缓存
+ }
}
\ No newline at end of file
diff --git a/src/main/java/com/ruoyi/device/websocket/StatisticsWebSocket.java b/src/main/java/com/ruoyi/device/websocket/StatisticsWebSocket.java
new file mode 100644
index 0000000..1f4c495
--- /dev/null
+++ b/src/main/java/com/ruoyi/device/websocket/StatisticsWebSocket.java
@@ -0,0 +1,256 @@
+package com.ruoyi.device.websocket;
+
+import com.alibaba.fastjson2.JSON;
+import com.ruoyi.device.api.domain.StatisticsVO;
+import com.ruoyi.device.service.api.IBufferDeviceService;
+import com.ruoyi.device.service.api.IAircraftService;
+import com.ruoyi.device.service.api.IDockService;
+import com.ruoyi.device.service.api.IPayloadService;
+import com.ruoyi.device.service.dto.AircraftDTO;
+import com.ruoyi.device.service.dto.AircraftDetailDTO;
+import com.ruoyi.device.service.dto.DockDTO;
+import com.ruoyi.device.service.dto.DockDetailDTO;
+import com.ruoyi.device.service.dto.PayloadDTO;
+import com.ruoyi.device.api.enums.AircraftStatusEnum;
+import com.ruoyi.device.api.enums.DockStatusEnum;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import jakarta.websocket.*;
+import jakarta.websocket.server.PathParam;
+import jakarta.websocket.server.ServerEndpoint;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.stream.Collectors;
+
+@Component
+@ServerEndpoint("/websocket/statistics/{type}")
+public class StatisticsWebSocket {
+
+ private static final Logger log = LoggerFactory.getLogger(StatisticsWebSocket.class);
+
+ private static IBufferDeviceService bufferDeviceService;
+ private static IAircraftService aircraftService;
+ private static IDockService dockService;
+ private static IPayloadService payloadService;
+
+ private Session session;
+
+ private static final Map> sessions = new ConcurrentHashMap<>();
+
+ @Autowired
+ public void setBufferDeviceService(IBufferDeviceService bufferDeviceService) {
+ StatisticsWebSocket.bufferDeviceService = bufferDeviceService;
+ }
+
+ @Autowired
+ public void setAircraftService(IAircraftService aircraftService) {
+ StatisticsWebSocket.aircraftService = aircraftService;
+ }
+
+ @Autowired
+ public void setDockService(IDockService dockService) {
+ StatisticsWebSocket.dockService = dockService;
+ }
+
+ @Autowired
+ public void setPayloadService(IPayloadService payloadService) {
+ StatisticsWebSocket.payloadService = payloadService;
+ }
+
+ @OnOpen
+ public void onOpen(Session session, @PathParam("type") String type) {
+ this.session = session;
+ sessions.computeIfAbsent(type, k -> new CopyOnWriteArraySet<>()).add(this);
+ log.info("WebSocket连接建立: sessionId={}, type={}", session.getId(), type);
+
+ sendCurrentStatistics(type);
+ }
+
+ @OnClose
+ public void onClose(@PathParam("type") String type) {
+ CopyOnWriteArraySet typeSessions = sessions.get(type);
+ if (typeSessions != null) {
+ typeSessions.remove(this);
+ if (typeSessions.isEmpty()) {
+ sessions.remove(type);
+ }
+ }
+ log.info("WebSocket连接关闭: sessionId={}, type={}", session.getId(), type);
+ }
+
+ @OnMessage
+ public void onMessage(String message, @PathParam("type") String type) {
+ log.info("收到WebSocket消息: sessionId={}, type={}, message={}", session.getId(), type, message);
+ if ("refresh".equals(message)) {
+ sendCurrentStatistics(type);
+ }
+ }
+
+ @OnError
+ public void onError(Session session, Throwable error, @PathParam("type") String type) {
+ log.error("WebSocket错误: sessionId={}, type={}, error={}", session.getId(), type, error.getMessage(), error);
+ }
+
+ private void sendCurrentStatistics(String type) {
+ try {
+ StatisticsVO statistics = buildStatistics(type);
+ sendMessage(JSON.toJSONString(statistics));
+ } catch (Exception e) {
+ log.error("发送统计数据失败: type={}, error={}", type, e.getMessage(), e);
+ }
+ }
+
+ private StatisticsVO buildStatistics(String type) {
+ if ("th".equals(type)) {
+ return buildThStatistics();
+ } else {
+ return buildDjiStatistics();
+ }
+ }
+
+ private StatisticsVO buildDjiStatistics() {
+ StatisticsVO vo = new StatisticsVO();
+
+ List docks = dockService.selectDockList(new DockDTO());
+ vo.setDockCount(docks != null ? docks.size() : 0);
+
+ Map dockDetailsMap = null;
+ if (docks != null && !docks.isEmpty()) {
+ List dockIds = docks.stream()
+ .map(DockDTO::getDockId)
+ .collect(Collectors.toList());
+ dockDetailsMap = bufferDeviceService.getDockDetailsByIds(dockIds);
+ }
+
+ int idleCount = 0;
+ int workingCount = 0;
+ int debuggingCount = 0;
+ int offlineCount = 0;
+
+ if (docks != null && dockDetailsMap != null) {
+ for (DockDTO dock : docks) {
+ DockDetailDTO dockDetail = dockDetailsMap.get(dock.getDockId());
+ if (dockDetail != null && dockDetail.getDockStatus() != null) {
+ String status = dockDetail.getDockStatus();
+ if (DockStatusEnum.IDLE.getCode().equals(status)) {
+ idleCount++;
+ } else if (DockStatusEnum.WORKING.getCode().equals(status)) {
+ workingCount++;
+ } else if (DockStatusEnum.Debugging.getCode().equals(status)) {
+ debuggingCount++;
+ } else {
+ offlineCount++;
+ }
+ }
+ }
+ }
+
+ vo.setIdleDockCount(idleCount);
+ vo.setWorkingDockCount(workingCount);
+ vo.setDebuggingDockCount(debuggingCount);
+ vo.setOfflineDockCount(offlineCount);
+
+ List aircrafts = aircraftService.selectAircraftList(new AircraftDTO());
+ vo.setAircraftCount(aircrafts != null ? aircrafts.size() : 0);
+
+ Map aircraftDetailsMap = null;
+ if (aircrafts != null && !aircrafts.isEmpty()) {
+ List aircraftIds = aircrafts.stream()
+ .map(AircraftDTO::getAircraftId)
+ .collect(Collectors.toList());
+ aircraftDetailsMap = bufferDeviceService.getAircraftDetailsByIds(aircraftIds);
+ }
+
+ int powerOnInCabinCount = 0;
+ int powerOffInCabinCount = 0;
+ int inMissionCount = 0;
+ int debuggingAircraftCount = 0;
+ int offlineAircraftCount = 0;
+
+ if (aircrafts != null && aircraftDetailsMap != null) {
+ for (AircraftDTO aircraft : aircrafts) {
+ AircraftDetailDTO aircraftDetail = aircraftDetailsMap.get(aircraft.getAircraftId());
+ if (aircraftDetail != null && aircraftDetail.getAircraftStatus() != null) {
+ String status = aircraftDetail.getAircraftStatus();
+ if (AircraftStatusEnum.POWER_ON_IN_CABIN.getCode().equals(status)) {
+ powerOnInCabinCount++;
+ } else if (AircraftStatusEnum.POWER_OFF_IN_CABIN.getCode().equals(status)) {
+ powerOffInCabinCount++;
+ } else if (AircraftStatusEnum.IN_MISSION.getCode().equals(status)) {
+ inMissionCount++;
+ } else if (AircraftStatusEnum.DEBUGGING.getCode().equals(status)) {
+ debuggingAircraftCount++;
+ } else if (AircraftStatusEnum.OFFLINE.getCode().equals(status)) {
+ offlineAircraftCount++;
+ } else {
+ offlineAircraftCount++;
+ }
+ }
+ }
+ }
+
+ vo.setPowerOnInCabinCount(powerOnInCabinCount);
+ vo.setPowerOffInCabinCount(powerOffInCabinCount);
+ vo.setInMissionCount(inMissionCount);
+ vo.setDebuggingAircraftCount(debuggingAircraftCount);
+ vo.setOfflineAircraftCount(offlineAircraftCount);
+
+ List payloads = payloadService.selectPayloadList(new PayloadDTO());
+ vo.setPayloadCount(payloads != null ? payloads.size() : 0);
+ vo.setOfflinePayloadCount(0);
+
+ return vo;
+ }
+
+ private StatisticsVO buildThStatistics() {
+ StatisticsVO vo = new StatisticsVO();
+ vo.setDockCount(0);
+ vo.setIdleDockCount(0);
+ vo.setWorkingDockCount(0);
+ vo.setDebuggingDockCount(0);
+ vo.setOfflineDockCount(0);
+ vo.setAircraftCount(0);
+ vo.setPowerOnInCabinCount(0);
+ vo.setPowerOffInCabinCount(0);
+ vo.setInMissionCount(0);
+ vo.setDebuggingAircraftCount(0);
+ vo.setOfflineAircraftCount(0);
+ vo.setPayloadCount(0);
+ vo.setOfflinePayloadCount(0);
+ return vo;
+ }
+
+ private void sendMessage(String message) {
+ try {
+ if (session != null && session.isOpen()) {
+ session.getBasicRemote().sendText(message);
+ }
+ } catch (Exception e) {
+ log.error("发送WebSocket消息失败: error={}", e.getMessage(), e);
+ }
+ }
+
+ public static void broadcastToType(String type, String message) {
+ CopyOnWriteArraySet typeSessions = sessions.get(type);
+ if (typeSessions != null) {
+ for (StatisticsWebSocket ws : typeSessions) {
+ ws.sendMessage(message);
+ }
+ }
+ }
+
+ public static void broadcastDjiStatistics() {
+ StatisticsVO statistics = new StatisticsWebSocket().buildDjiStatistics();
+ broadcastToType("dji", JSON.toJSONString(statistics));
+ }
+
+ public static void broadcastThStatistics() {
+ StatisticsVO statistics = new StatisticsWebSocket().buildThStatistics();
+ broadcastToType("th", JSON.toJSONString(statistics));
+ }
+}