Compare commits
4 Commits
a0fd20ca51
...
ac95f0a163
| Author | SHA1 | Date |
|---|---|---|
|
|
ac95f0a163 | |
|
|
ec4985d819 | |
|
|
296d535b25 | |
|
|
f728d1f0c1 |
|
|
@ -27,6 +27,14 @@ public interface IAircraftDomain
|
||||||
*/
|
*/
|
||||||
Aircraft selectAircraftByAircraftId(Long aircraftId);
|
Aircraft selectAircraftByAircraftId(Long aircraftId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据设备主键查询无人机
|
||||||
|
*
|
||||||
|
* @param deviceId 设备主键
|
||||||
|
* @return 无人机
|
||||||
|
*/
|
||||||
|
Aircraft selectAircraftByDeviceId(Long deviceId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 新增无人机
|
* 新增无人机
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,14 @@ public interface IDeviceDomain
|
||||||
*/
|
*/
|
||||||
Device selectDeviceByDeviceId(Long deviceId);
|
Device selectDeviceByDeviceId(Long deviceId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据IOT设备ID查询设备
|
||||||
|
*
|
||||||
|
* @param iotDeviceId IOT设备ID
|
||||||
|
* @return 设备
|
||||||
|
*/
|
||||||
|
Device selectDeviceByIotDeviceId(String iotDeviceId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 新增设备
|
* 新增设备
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,14 @@ public interface IDockDomain
|
||||||
*/
|
*/
|
||||||
Dock selectDockByDockId(Long dockId);
|
Dock selectDockByDockId(Long dockId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据设备主键查询机场
|
||||||
|
*
|
||||||
|
* @param deviceId 设备主键
|
||||||
|
* @return 机场
|
||||||
|
*/
|
||||||
|
Dock selectDockByDeviceId(Long deviceId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 新增机场
|
* 新增机场
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,23 @@ public interface IThingsBoardDomain {
|
||||||
*/
|
*/
|
||||||
Iterable<List<DeviceInfo>> getAllDevices();
|
Iterable<List<DeviceInfo>> getAllDevices();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有网关设备的迭代器
|
||||||
|
* 每次迭代返回一页网关设备列表
|
||||||
|
* 只返回 additionalInfo.gateway = true 的设备
|
||||||
|
*
|
||||||
|
* @return 网关设备迭代器
|
||||||
|
*/
|
||||||
|
Iterable<List<DeviceInfo>> getAllGatewayDevices();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据设备ID获取设备信息
|
||||||
|
*
|
||||||
|
* @param deviceId 设备ID
|
||||||
|
* @return 设备信息,如果设备不存在则返回 null
|
||||||
|
*/
|
||||||
|
DeviceInfo getDeviceInfo(String deviceId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据设备ID获取设备的所有属性
|
* 根据设备ID获取设备的所有属性
|
||||||
* 只返回已注册的属性键对应的数据,未注册的键会被忽略
|
* 只返回已注册的属性键对应的数据,未注册的键会被忽略
|
||||||
|
|
@ -55,4 +72,22 @@ public interface IThingsBoardDomain {
|
||||||
* @return 类型安全的遥测数据映射,只包含预定义的遥测数据
|
* @return 类型安全的遥测数据映射,只包含预定义的遥测数据
|
||||||
*/
|
*/
|
||||||
TelemetryMap getPredefinedDeviceTelemetry(String deviceId);
|
TelemetryMap getPredefinedDeviceTelemetry(String deviceId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取设备所属的网关设备ID
|
||||||
|
* 通过 ThingsBoard 的 EntityRelation 查询设备与网关的 "Contains" 关系
|
||||||
|
*
|
||||||
|
* @param deviceId 设备ID
|
||||||
|
* @return 网关设备ID,如果设备不属于任何网关则返回 null
|
||||||
|
*/
|
||||||
|
String getDeviceGatewayId(String deviceId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取网关设备的所有子设备ID列表
|
||||||
|
* 通过 ThingsBoard 的 EntityRelation 查询网关的 "Contains" 关系
|
||||||
|
*
|
||||||
|
* @param gatewayDeviceId 网关设备ID
|
||||||
|
* @return 子设备ID列表,如果网关没有子设备则返回空列表
|
||||||
|
*/
|
||||||
|
List<String> getGatewayChildDevices(String gatewayDeviceId);
|
||||||
}
|
}
|
||||||
|
|
@ -40,6 +40,13 @@ public class AircraftDomainImpl implements IAircraftDomain
|
||||||
return AircraftDomainConvert.toModel(entity);
|
return AircraftDomainConvert.toModel(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Aircraft selectAircraftByDeviceId(Long deviceId)
|
||||||
|
{
|
||||||
|
AircraftEntity entity = aircraftMapper.selectAircraftByDeviceId(deviceId);
|
||||||
|
return AircraftDomainConvert.toModel(entity);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int insertAircraft(Aircraft aircraft)
|
public int insertAircraft(Aircraft aircraft)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,13 @@ public class DeviceDomainImpl implements IDeviceDomain
|
||||||
return DeviceDomainConvert.toModel(entity);
|
return DeviceDomainConvert.toModel(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Device selectDeviceByIotDeviceId(String iotDeviceId)
|
||||||
|
{
|
||||||
|
DeviceEntity entity = deviceMapper.selectDeviceByIotDeviceId(iotDeviceId);
|
||||||
|
return DeviceDomainConvert.toModel(entity);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int insertDevice(Device device)
|
public int insertDevice(Device device)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,13 @@ public class DockDomainImpl implements IDockDomain
|
||||||
return DockDomainConvert.toModel(entity);
|
return DockDomainConvert.toModel(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Dock selectDockByDeviceId(Long deviceId)
|
||||||
|
{
|
||||||
|
DockEntity entity = dockMapper.selectDockByDeviceId(deviceId);
|
||||||
|
return DockDomainConvert.toModel(entity);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int insertDock(Dock dock)
|
public int insertDock(Dock dock)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,15 @@ import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.scheduling.annotation.Scheduled;
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.thingsboard.rest.client.RestClient;
|
import org.thingsboard.rest.client.RestClient;
|
||||||
|
import org.thingsboard.server.common.data.Device;
|
||||||
import org.thingsboard.server.common.data.id.DeviceId;
|
import org.thingsboard.server.common.data.id.DeviceId;
|
||||||
|
import org.thingsboard.server.common.data.id.EntityId;
|
||||||
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
|
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
|
||||||
import org.thingsboard.server.common.data.kv.TsKvEntry;
|
import org.thingsboard.server.common.data.kv.TsKvEntry;
|
||||||
|
import org.thingsboard.server.common.data.relation.EntityRelation;
|
||||||
|
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
@ -48,6 +53,34 @@ public class ThingsBoardDomainImpl implements IThingsBoardDomain {
|
||||||
return new DeviceIterator(client, pageSize);
|
return new DeviceIterator(client, pageSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterable<List<DeviceInfo>> getAllGatewayDevices() {
|
||||||
|
return new GatewayDeviceIterator(client, pageSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DeviceInfo getDeviceInfo(String deviceId) {
|
||||||
|
try {
|
||||||
|
DeviceId id = new DeviceId(UUID.fromString(deviceId));
|
||||||
|
Optional<Device> deviceOptional = client.getDeviceById(id);
|
||||||
|
if (deviceOptional.isEmpty()) {
|
||||||
|
log.warn("设备不存在: deviceId={}", deviceId);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DeviceInfo(
|
||||||
|
deviceOptional.get().getName(),
|
||||||
|
deviceOptional.get().getId().getId().toString(),
|
||||||
|
deviceOptional.get().getType(),
|
||||||
|
deviceOptional.get().getId(),
|
||||||
|
deviceOptional.get().getAdditionalInfo()
|
||||||
|
);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取设备信息失败: deviceId={}, error={}", deviceId, e.getMessage(), e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public AttributeMap getDeviceAttributes(String deviceId) {
|
public AttributeMap getDeviceAttributes(String deviceId) {
|
||||||
AttributeMap attributeMap = new AttributeMap();
|
AttributeMap attributeMap = new AttributeMap();
|
||||||
|
|
@ -173,6 +206,71 @@ public class ThingsBoardDomainImpl implements IThingsBoardDomain {
|
||||||
return predefinedTelemetry;
|
return predefinedTelemetry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getDeviceGatewayId(String deviceId) {
|
||||||
|
try {
|
||||||
|
DeviceId id = new DeviceId(UUID.fromString(deviceId));
|
||||||
|
|
||||||
|
// 查询指向该设备的 "Contains" 关系(网关 -> 设备)
|
||||||
|
List<EntityRelation> relations = client.findByTo(
|
||||||
|
id,
|
||||||
|
EntityRelation.CONTAINS_TYPE,
|
||||||
|
RelationTypeGroup.COMMON
|
||||||
|
);
|
||||||
|
|
||||||
|
if (relations == null || relations.isEmpty()) {
|
||||||
|
log.debug("设备 {} 不属于任何网关", deviceId);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取第一个关系的 from 实体(网关设备)
|
||||||
|
EntityId gatewayEntityId = relations.get(0).getFrom();
|
||||||
|
String gatewayId = gatewayEntityId.getId().toString();
|
||||||
|
|
||||||
|
log.debug("设备 {} 属于网关 {}", deviceId, gatewayId);
|
||||||
|
return gatewayId;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取设备网关关系失败: deviceId={}, error={}", deviceId, e.getMessage(), e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getGatewayChildDevices(String gatewayDeviceId) {
|
||||||
|
List<String> childDeviceIds = new ArrayList<>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
DeviceId gatewayId = new DeviceId(UUID.fromString(gatewayDeviceId));
|
||||||
|
|
||||||
|
// 查询从网关出发的 "Contains" 关系(网关 -> 子设备)
|
||||||
|
List<EntityRelation> relations = client.findByFrom(
|
||||||
|
gatewayId,
|
||||||
|
EntityRelation.CONTAINS_TYPE,
|
||||||
|
RelationTypeGroup.COMMON
|
||||||
|
);
|
||||||
|
|
||||||
|
if (relations == null || relations.isEmpty()) {
|
||||||
|
log.debug("网关 {} 没有子设备", gatewayDeviceId);
|
||||||
|
return childDeviceIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取所有子设备的ID
|
||||||
|
for (EntityRelation relation : relations) {
|
||||||
|
EntityId childEntityId = relation.getTo();
|
||||||
|
String childDeviceId = childEntityId.getId().toString();
|
||||||
|
childDeviceIds.add(childDeviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug("网关 {} 有 {} 个子设备", gatewayDeviceId, childDeviceIds.size());
|
||||||
|
return childDeviceIds;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取网关子设备失败: gatewayDeviceId={}, error={}", gatewayDeviceId, e.getMessage(), e);
|
||||||
|
return childDeviceIds;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解析属性并添加到AttributeMap
|
* 解析属性并添加到AttributeMap
|
||||||
* 使用延迟注册机制,自动处理所有属性
|
* 使用延迟注册机制,自动处理所有属性
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.ruoyi.device.domain.model.thingsboard;
|
package com.ruoyi.device.domain.model.thingsboard;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import org.thingsboard.server.common.data.id.DeviceId;
|
import org.thingsboard.server.common.data.id.DeviceId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -10,12 +11,14 @@ public class DeviceInfo {
|
||||||
private final String id;
|
private final String id;
|
||||||
private final String type;
|
private final String type;
|
||||||
private final DeviceId deviceId;
|
private final DeviceId deviceId;
|
||||||
|
private final JsonNode additionalInfo;
|
||||||
|
|
||||||
public DeviceInfo(String name, String id, String type, DeviceId deviceId) {
|
public DeviceInfo(String name, String id, String type, DeviceId deviceId, JsonNode additionalInfo) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.deviceId = deviceId;
|
this.deviceId = deviceId;
|
||||||
|
this.additionalInfo = additionalInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
|
|
@ -34,12 +37,30 @@ public class DeviceInfo {
|
||||||
return deviceId;
|
return deviceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public JsonNode getAdditionalInfo() {
|
||||||
|
return additionalInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断设备是否为网关
|
||||||
|
* 根据 ThingsBoard 标准:检查 additionalInfo 中的 "gateway" 字段
|
||||||
|
*
|
||||||
|
* @return true 如果是网关设备,否则返回 false
|
||||||
|
*/
|
||||||
|
public boolean isGateway() {
|
||||||
|
if (additionalInfo == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return additionalInfo.has("gateway") && additionalInfo.get("gateway").asBoolean();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "DeviceInfo{" +
|
return "DeviceInfo{" +
|
||||||
"name='" + name + '\'' +
|
"name='" + name + '\'' +
|
||||||
", id='" + id + '\'' +
|
", id='" + id + '\'' +
|
||||||
", type='" + type + '\'' +
|
", type='" + type + '\'' +
|
||||||
|
", isGateway=" + isGateway() +
|
||||||
'}';
|
'}';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -50,7 +50,8 @@ public class DeviceIterator implements Iterable<List<DeviceInfo>> {
|
||||||
device.getName(),
|
device.getName(),
|
||||||
device.getId().getId().toString(),
|
device.getId().getId().toString(),
|
||||||
device.getType(),
|
device.getType(),
|
||||||
device.getId()
|
device.getId(),
|
||||||
|
device.getAdditionalInfo()
|
||||||
))
|
))
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,106 @@
|
||||||
|
package com.ruoyi.device.domain.model.thingsboard;
|
||||||
|
|
||||||
|
import org.thingsboard.rest.client.RestClient;
|
||||||
|
import org.thingsboard.server.common.data.Device;
|
||||||
|
import org.thingsboard.server.common.data.page.PageData;
|
||||||
|
import org.thingsboard.server.common.data.page.PageLink;
|
||||||
|
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网关设备迭代器
|
||||||
|
* 支持分页遍历所有网关设备
|
||||||
|
* 优化策略:自动跳过没有网关设备的页,只返回包含网关设备的批次
|
||||||
|
*
|
||||||
|
* 注意:由于ThingsBoard不支持服务端过滤网关设备,
|
||||||
|
* 本迭代器会在客户端过滤,并自动跳过空结果页
|
||||||
|
*/
|
||||||
|
public class GatewayDeviceIterator implements Iterable<List<DeviceInfo>> {
|
||||||
|
|
||||||
|
private final RestClient client;
|
||||||
|
private final int pageSize;
|
||||||
|
|
||||||
|
public GatewayDeviceIterator(RestClient client, int pageSize) {
|
||||||
|
this.client = client;
|
||||||
|
this.pageSize = pageSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<List<DeviceInfo>> iterator() {
|
||||||
|
return new Iterator<List<DeviceInfo>>() {
|
||||||
|
private PageLink pageLink = new PageLink(pageSize);
|
||||||
|
private List<DeviceInfo> nextBatch = null;
|
||||||
|
private boolean hasMorePages = true;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
// 如果已经有预加载的数据,直接返回true
|
||||||
|
if (nextBatch != null && !nextBatch.isEmpty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 尝试加载下一批网关设备
|
||||||
|
loadNextBatch();
|
||||||
|
|
||||||
|
// 检查是否成功加载到数据
|
||||||
|
return nextBatch != null && !nextBatch.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<DeviceInfo> next() {
|
||||||
|
if (!hasNext()) {
|
||||||
|
throw new NoSuchElementException("No more gateway devices");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回预加载的数据
|
||||||
|
List<DeviceInfo> result = nextBatch;
|
||||||
|
nextBatch = null; // 清空,下次调用hasNext时会重新加载
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载下一批网关设备
|
||||||
|
* 会自动跳过没有网关设备的页,直到找到至少一个网关设备或没有更多页
|
||||||
|
*/
|
||||||
|
private void loadNextBatch() {
|
||||||
|
nextBatch = null;
|
||||||
|
|
||||||
|
// 循环直到找到至少一个网关设备,或者没有更多页
|
||||||
|
while (hasMorePages && (nextBatch == null || nextBatch.isEmpty())) {
|
||||||
|
// 获取当前页数据
|
||||||
|
PageData<Device> currentPage = client.getTenantDevices("", pageLink);
|
||||||
|
|
||||||
|
// 转换为DeviceInfo列表,并过滤出网关设备
|
||||||
|
nextBatch = currentPage.getData().stream()
|
||||||
|
.filter(device -> {
|
||||||
|
// 检查 additionalInfo 中的 gateway 字段
|
||||||
|
if (device.getAdditionalInfo() == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return device.getAdditionalInfo().has("gateway")
|
||||||
|
&& device.getAdditionalInfo().get("gateway").asBoolean();
|
||||||
|
})
|
||||||
|
.map(device -> new DeviceInfo(
|
||||||
|
device.getName(),
|
||||||
|
device.getId().getId().toString(),
|
||||||
|
device.getType(),
|
||||||
|
device.getId(),
|
||||||
|
device.getAdditionalInfo()
|
||||||
|
))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
// 准备下一页
|
||||||
|
if (currentPage.hasNext()) {
|
||||||
|
pageLink = pageLink.nextPageLink();
|
||||||
|
} else {
|
||||||
|
hasMorePages = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -84,6 +84,20 @@ public class DeviceAttributes {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 机场SN号 - String
|
||||||
|
public static final AttributeKey<String> DOCK_SN = AttributeKey.of(
|
||||||
|
"dock_sn",
|
||||||
|
String.class,
|
||||||
|
value -> value != null ? value.toString() : null
|
||||||
|
);
|
||||||
|
|
||||||
|
// 子设备SN号 - String
|
||||||
|
public static final AttributeKey<String> SUB_DEVICE_SN = AttributeKey.of(
|
||||||
|
"sub_device.device_sn",
|
||||||
|
String.class,
|
||||||
|
value -> value != null ? value.toString() : null
|
||||||
|
);
|
||||||
|
|
||||||
private DeviceAttributes() {
|
private DeviceAttributes() {
|
||||||
// 工具类,禁止实例化
|
// 工具类,禁止实例化
|
||||||
}
|
}
|
||||||
|
|
@ -101,7 +115,9 @@ public class DeviceAttributes {
|
||||||
LAST_CONNECT_TIME,
|
LAST_CONNECT_TIME,
|
||||||
ACTIVE,
|
ACTIVE,
|
||||||
LAST_ACTIVITY_TIME,
|
LAST_ACTIVITY_TIME,
|
||||||
LAST_DISCONNECT_TIME
|
LAST_DISCONNECT_TIME,
|
||||||
|
DOCK_SN,
|
||||||
|
SUB_DEVICE_SN
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,14 @@ public interface AircraftMapper
|
||||||
*/
|
*/
|
||||||
AircraftEntity selectAircraftByAircraftId(Long aircraftId);
|
AircraftEntity selectAircraftByAircraftId(Long aircraftId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据设备主键查询无人机
|
||||||
|
*
|
||||||
|
* @param deviceId 设备主键
|
||||||
|
* @return 无人机信息
|
||||||
|
*/
|
||||||
|
AircraftEntity selectAircraftByDeviceId(Long deviceId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据设备主键查询无人机列表
|
* 根据设备主键查询无人机列表
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,14 @@ public interface DeviceMapper
|
||||||
*/
|
*/
|
||||||
DeviceEntity selectDeviceByDeviceId(Long deviceId);
|
DeviceEntity selectDeviceByDeviceId(Long deviceId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据IOT设备ID查询设备
|
||||||
|
*
|
||||||
|
* @param iotDeviceId IOT设备ID
|
||||||
|
* @return 设备信息
|
||||||
|
*/
|
||||||
|
DeviceEntity selectDeviceByIotDeviceId(String iotDeviceId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询设备列表
|
* 查询设备列表
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,14 @@ public interface DockMapper
|
||||||
*/
|
*/
|
||||||
DockEntity selectDockByDockId(Long dockId);
|
DockEntity selectDockByDockId(Long dockId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据设备主键查询机场
|
||||||
|
*
|
||||||
|
* @param deviceId 设备主键
|
||||||
|
* @return 机场信息
|
||||||
|
*/
|
||||||
|
DockEntity selectDockByDeviceId(Long deviceId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据设备主键查询机场列表
|
* 根据设备主键查询机场列表
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
package com.ruoyi.device.service.enums;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备类型枚举
|
||||||
|
* Service层使用
|
||||||
|
*
|
||||||
|
* @author ruoyi
|
||||||
|
* @date 2026-01-17
|
||||||
|
*/
|
||||||
|
public enum DeviceType {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 无人机
|
||||||
|
*/
|
||||||
|
AIRCRAFT("AIRCRAFT", "无人机"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 机场
|
||||||
|
*/
|
||||||
|
DOCK("DOCK", "机场"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 网关
|
||||||
|
*/
|
||||||
|
GATEWAY("GATEWAY", "网关");
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
private final String description;
|
||||||
|
|
||||||
|
DeviceType(String code, String description) {
|
||||||
|
this.code = code;
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCode() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDescription() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -46,48 +46,6 @@ public class DeviceServiceImpl implements IDeviceService {
|
||||||
Device model = deviceDomain.selectDeviceByDeviceId(deviceId);
|
Device model = deviceDomain.selectDeviceByDeviceId(deviceId);
|
||||||
return DeviceServiceConvert.toDTO(model);
|
return DeviceServiceConvert.toDTO(model);
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* 定时任务:定期打印所有设备信息
|
|
||||||
* 执行时间:启动后1分钟开始,每2分钟执行一次(可通过配置文件修改)
|
|
||||||
* 配置项:device.schedule.print-devices.initial-delay 初始延迟时间(毫秒)
|
|
||||||
* device.schedule.print-devices.fixed-delay 执行间隔时间(毫秒)
|
|
||||||
*/
|
|
||||||
@Scheduled(initialDelayString = "${device.schedule.update-devices.initial-delay:60000}",
|
|
||||||
fixedDelayString = "${device.schedule.update-devices.fixed-delay:120000}")
|
|
||||||
public void updateDevicesScheduled() {
|
|
||||||
try {
|
|
||||||
log.info("========== 开始执行定时任务:打印所有设备信息 ==========");
|
|
||||||
|
|
||||||
Iterable<List<DeviceInfo>> allDevices = iThingsBoardDomain.getAllDevices();
|
|
||||||
int totalCount = 0;
|
|
||||||
|
|
||||||
for (List<DeviceInfo> deviceBatch : allDevices) {
|
|
||||||
for (DeviceInfo device : deviceBatch) {
|
|
||||||
// 获取设备属性以获取活跃状态
|
|
||||||
Boolean activeStatus = false;
|
|
||||||
try {
|
|
||||||
AttributeMap attributes = iThingsBoardDomain.getDeviceAttributes(device.getId());
|
|
||||||
// 尝试从 AttributeMap 中获取 active 属性
|
|
||||||
Optional<Boolean> active = attributes.get(DeviceAttributes.ACTIVE);
|
|
||||||
if (active.isPresent()) {
|
|
||||||
activeStatus = active.get();
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.debug("获取设备 {} 的活跃状态失败: {}", device.getId(), e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info("Device Name: {}, Device ID: {}, Device Type: {}, Active: {}",
|
|
||||||
device.getName(),
|
|
||||||
device.getId(),
|
|
||||||
device.getType(),
|
|
||||||
activeStatus);
|
|
||||||
totalCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info("========== 定时任务执行完成,共打印 {} 个设备 ==========", totalCount);
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("定时任务执行失败: {}", e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,494 @@
|
||||||
|
package com.ruoyi.device.service.impl;
|
||||||
|
|
||||||
|
import com.ruoyi.device.domain.api.IAircraftDomain;
|
||||||
|
import com.ruoyi.device.domain.api.IDeviceDomain;
|
||||||
|
import com.ruoyi.device.domain.api.IDockAircraftDomain;
|
||||||
|
import com.ruoyi.device.domain.api.IDockDomain;
|
||||||
|
import com.ruoyi.device.domain.api.IThingsBoardDomain;
|
||||||
|
import com.ruoyi.device.domain.model.Aircraft;
|
||||||
|
import com.ruoyi.device.domain.model.Device;
|
||||||
|
import com.ruoyi.device.domain.model.Dock;
|
||||||
|
import com.ruoyi.device.domain.model.DockAircraft;
|
||||||
|
import com.ruoyi.device.domain.model.thingsboard.AttributeMap;
|
||||||
|
import com.ruoyi.device.domain.model.thingsboard.DeviceInfo;
|
||||||
|
import com.ruoyi.device.domain.model.thingsboard.constants.DeviceAttributes;
|
||||||
|
import com.ruoyi.device.service.enums.DeviceType;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class SynService {
|
||||||
|
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(SynService.class);
|
||||||
|
|
||||||
|
private final IThingsBoardDomain iThingsBoardDomain;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备名称过滤正则表达式列表(从配置文件读取)
|
||||||
|
* 匹配这些正则表达式的设备将被跳过,不进行同步
|
||||||
|
*/
|
||||||
|
@Value("${device.sync.exclude-patterns:}")
|
||||||
|
private String excludePatterns;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编译后的正则表达式模式列表(延迟初始化)
|
||||||
|
*/
|
||||||
|
private List<Pattern> excludePatternList;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IDeviceDomain deviceDomain;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IDockDomain dockDomain;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IAircraftDomain aircraftDomain;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IDockAircraftDomain dockAircraftDomain;
|
||||||
|
|
||||||
|
public SynService(IThingsBoardDomain iThingsBoardDomain) {
|
||||||
|
this.iThingsBoardDomain = iThingsBoardDomain;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化设备名称过滤正则表达式列表
|
||||||
|
* 延迟初始化,在第一次使用时编译正则表达式
|
||||||
|
*/
|
||||||
|
private void initExcludePatterns() {
|
||||||
|
if (excludePatternList == null) {
|
||||||
|
excludePatternList = new ArrayList<>();
|
||||||
|
|
||||||
|
if (StringUtils.hasText(excludePatterns)) {
|
||||||
|
String[] patterns = excludePatterns.split(",");
|
||||||
|
for (String pattern : patterns) {
|
||||||
|
String trimmedPattern = pattern.trim();
|
||||||
|
if (StringUtils.hasText(trimmedPattern)) {
|
||||||
|
try {
|
||||||
|
excludePatternList.add(Pattern.compile(trimmedPattern));
|
||||||
|
log.info("加载设备名称过滤规则: {}", trimmedPattern);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("无效的正则表达式: {}, error={}", trimmedPattern, e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (excludePatternList.isEmpty()) {
|
||||||
|
log.info("未配置设备名称过滤规则");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断设备名称是否应该被过滤(跳过同步)
|
||||||
|
*
|
||||||
|
* @param deviceName 设备名称
|
||||||
|
* @return true 表示应该被过滤,false 表示不过滤
|
||||||
|
*/
|
||||||
|
private boolean shouldExcludeDevice(String deviceName) {
|
||||||
|
// 延迟初始化
|
||||||
|
initExcludePatterns();
|
||||||
|
|
||||||
|
// 如果没有配置过滤规则,不过滤任何设备
|
||||||
|
if (excludePatternList.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查设备名称是否匹配任何过滤规则
|
||||||
|
for (Pattern pattern : excludePatternList) {
|
||||||
|
if (pattern.matcher(deviceName).matches()) {
|
||||||
|
log.debug("设备 {} 匹配过滤规则 {},跳过同步", deviceName, pattern.pattern());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 建立设备到网关的映射关系
|
||||||
|
* 优化策略:先识别所有网关设备,然后为每个网关查询其子设备
|
||||||
|
*
|
||||||
|
* @return 设备ID到网关ID的映射 Map<子设备ID, 网关ID>
|
||||||
|
*/
|
||||||
|
private Map<String, String> buildDeviceToGatewayMap() {
|
||||||
|
Map<String, String> deviceToGatewayMap = new HashMap<>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 获取所有设备
|
||||||
|
Iterable<List<DeviceInfo>> allDevices = iThingsBoardDomain.getAllDevices();
|
||||||
|
List<DeviceInfo> gatewayDevices = new ArrayList<>();
|
||||||
|
|
||||||
|
// 第一步:识别所有网关设备
|
||||||
|
for (List<DeviceInfo> deviceBatch : allDevices) {
|
||||||
|
for (DeviceInfo deviceInfo : deviceBatch) {
|
||||||
|
if (deviceInfo.isGateway()) {
|
||||||
|
gatewayDevices.add(deviceInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("发现 {} 个网关设备", gatewayDevices.size());
|
||||||
|
|
||||||
|
// 第二步:为每个网关查询其子设备,建立映射关系
|
||||||
|
for (DeviceInfo gateway : gatewayDevices) {
|
||||||
|
String gatewayId = gateway.getId();
|
||||||
|
List<String> childDeviceIds = iThingsBoardDomain.getGatewayChildDevices(gatewayId);
|
||||||
|
|
||||||
|
for (String childDeviceId : childDeviceIds) {
|
||||||
|
deviceToGatewayMap.put(childDeviceId, gatewayId);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.debug("网关 {} 有 {} 个子设备", gateway.getName(), childDeviceIds.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("成功建立设备到网关的映射关系,共 {} 个子设备", deviceToGatewayMap.size());
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("建立设备到网关映射关系失败: {}", e.getMessage(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return deviceToGatewayMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定时任务:同步基础表数据
|
||||||
|
* 执行时间:启动后1分钟开始,每2分钟执行一次(可通过配置文件修改)
|
||||||
|
* 配置项:device.schedule.print-devices.initial-delay 初始延迟时间(毫秒)
|
||||||
|
* device.schedule.print-devices.fixed-delay 执行间隔时间(毫秒)
|
||||||
|
*
|
||||||
|
* 优化策略:
|
||||||
|
* 1. 先识别所有网关设备
|
||||||
|
* 2. 遍历每个网关,同步网关及其子设备
|
||||||
|
* 3. 同步非网关的独立设备(机场和无人机)
|
||||||
|
*/
|
||||||
|
@Scheduled(initialDelayString = "${device.schedule.update-devices.initial-delay:60000}",
|
||||||
|
fixedDelayString = "${device.schedule.update-devices.fixed-delay:120000}")
|
||||||
|
public void updateDevicesScheduled() {
|
||||||
|
try {
|
||||||
|
log.info("========== 开始执行定时任务:同步基础表数据 ==========");
|
||||||
|
|
||||||
|
int totalCount = 0;
|
||||||
|
int skippedCount = 0;
|
||||||
|
|
||||||
|
// 第一步:直接获取所有网关设备(服务端过滤)
|
||||||
|
Iterable<List<DeviceInfo>> gatewayDevices = iThingsBoardDomain.getAllGatewayDevices();
|
||||||
|
|
||||||
|
// 第二步:遍历每个网关,同步网关及其子设备
|
||||||
|
for (List<DeviceInfo> gatewayBatch : gatewayDevices) {
|
||||||
|
for (DeviceInfo gatewayInfo : gatewayBatch) {
|
||||||
|
try {
|
||||||
|
// 按照名字过滤网关设备
|
||||||
|
if (shouldExcludeDevice(gatewayInfo.getName())) {
|
||||||
|
log.info("网关 {} 匹配过滤规则,跳过同步", gatewayInfo.getName());
|
||||||
|
skippedCount++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 同步网关设备本身
|
||||||
|
log.info("开始同步网关: {}", gatewayInfo.getName());
|
||||||
|
syncDevice(gatewayInfo, DeviceType.GATEWAY, null);
|
||||||
|
totalCount++;
|
||||||
|
|
||||||
|
// 获取该网关的所有子设备ID
|
||||||
|
List<String> childDeviceIds = iThingsBoardDomain.getGatewayChildDevices(gatewayInfo.getId());
|
||||||
|
log.info("网关 {} 有 {} 个子设备", gatewayInfo.getName(), childDeviceIds.size());
|
||||||
|
|
||||||
|
// 遍历该网关的子设备
|
||||||
|
for (String childDeviceId : childDeviceIds) {
|
||||||
|
// 通过API获取子设备信息
|
||||||
|
DeviceInfo childDeviceInfo = iThingsBoardDomain.getDeviceInfo(childDeviceId);
|
||||||
|
if (childDeviceInfo == null) {
|
||||||
|
log.warn("子设备 {} 不存在,跳过", childDeviceId);
|
||||||
|
skippedCount++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按照名字过滤子设备
|
||||||
|
if (shouldExcludeDevice(childDeviceInfo.getName())) {
|
||||||
|
log.info("子设备 {} 匹配过滤规则,跳过同步", childDeviceInfo.getName());
|
||||||
|
skippedCount++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 获取子设备属性
|
||||||
|
AttributeMap attributes = iThingsBoardDomain.getDeviceAttributes(childDeviceId);
|
||||||
|
|
||||||
|
// 判断设备类型
|
||||||
|
DeviceType deviceType = determineDeviceType(childDeviceInfo, attributes);
|
||||||
|
|
||||||
|
// 同步子设备(传入网关ID)
|
||||||
|
Long deviceId = syncDevice(childDeviceInfo, deviceType, gatewayInfo.getId());
|
||||||
|
|
||||||
|
// 根据设备类型进行不同的处理
|
||||||
|
syncDeviceByType(deviceId, childDeviceInfo.getName(), deviceType, attributes);
|
||||||
|
|
||||||
|
totalCount++;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("同步网关子设备失败: gatewayName={}, childDeviceId={}, error={}",
|
||||||
|
gatewayInfo.getName(), childDeviceId, e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("同步网关失败: gatewayName={}, error={}",
|
||||||
|
gatewayInfo.getName(), e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("========== 数据同步任务完成,共同步 {} 个设备,跳过 {} 个设备 ==========", totalCount, skippedCount);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("数据同步任务执行失败: {}", e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据设备类型进行不同的同步处理
|
||||||
|
*
|
||||||
|
* @param deviceId 设备主键ID
|
||||||
|
* @param deviceName 设备名称
|
||||||
|
* @param deviceType 设备类型
|
||||||
|
* @param attributes 设备属性
|
||||||
|
*/
|
||||||
|
private void syncDeviceByType(Long deviceId, String deviceName, DeviceType deviceType, AttributeMap attributes) {
|
||||||
|
if (deviceType == DeviceType.DOCK) {
|
||||||
|
// 机场:同步机场表
|
||||||
|
syncDock(deviceId, deviceName);
|
||||||
|
|
||||||
|
// 获取机场挂载的无人机SN号
|
||||||
|
Optional<String> subDeviceSnOpt = attributes.get(DeviceAttributes.SUB_DEVICE_SN);
|
||||||
|
if (subDeviceSnOpt.isPresent() && StringUtils.hasText(subDeviceSnOpt.get())) {
|
||||||
|
String aircraftSn = subDeviceSnOpt.get();
|
||||||
|
Device aircraftDevice = findDeviceBySn(aircraftSn);
|
||||||
|
if (aircraftDevice != null) {
|
||||||
|
syncDockAircraft(deviceId, aircraftDevice.getDeviceId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (deviceType == DeviceType.AIRCRAFT) {
|
||||||
|
// 无人机:同步无人机表
|
||||||
|
syncAircraft(deviceId, deviceName);
|
||||||
|
}
|
||||||
|
// 网关类型不需要额外处理
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断设备类型
|
||||||
|
* 优化后的判断逻辑:
|
||||||
|
* 1. 优先使用 ThingsBoard 标准的 additionalInfo.gateway 字段判断网关
|
||||||
|
* 2. 对于非网关设备,通过 dock_sn 属性区分机场和无人机
|
||||||
|
*
|
||||||
|
* @param deviceInfo ThingsBoard设备信息
|
||||||
|
* @param attributes 设备属性
|
||||||
|
* @return 设备类型
|
||||||
|
*/
|
||||||
|
private DeviceType determineDeviceType(DeviceInfo deviceInfo, AttributeMap attributes) {
|
||||||
|
String deviceName = deviceInfo.getName();
|
||||||
|
|
||||||
|
// 1. 使用 ThingsBoard 标准方式判断网关:检查 additionalInfo 中的 gateway 字段
|
||||||
|
if (deviceInfo.isGateway()) {
|
||||||
|
return DeviceType.GATEWAY;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 非网关设备:通过 dock_sn 属性区分机场和无人机
|
||||||
|
Optional<String> dockSnOpt = attributes.get(DeviceAttributes.DOCK_SN);
|
||||||
|
|
||||||
|
if (dockSnOpt.isPresent() && StringUtils.hasText(dockSnOpt.get())) {
|
||||||
|
String dockSn = dockSnOpt.get();
|
||||||
|
// dock_sn 等于设备名称 -> 机场
|
||||||
|
// dock_sn 不等于设备名称 -> 无人机(挂载在该机场下)
|
||||||
|
return deviceName.equals(dockSn) ? DeviceType.DOCK : DeviceType.AIRCRAFT;
|
||||||
|
}
|
||||||
|
|
||||||
|
// dock_sn 属性不存在或为空,无法判断设备类型
|
||||||
|
throw new IllegalStateException("无法确定设备类型:设备 " + deviceName + " 缺少 dock_sn 属性");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步设备数据
|
||||||
|
*
|
||||||
|
* @param deviceInfo ThingsBoard设备信息
|
||||||
|
* @param deviceType 设备类型
|
||||||
|
* @param gatewayId 网关设备ID(从映射中获取,避免重复API调用)
|
||||||
|
* @return 设备主键ID
|
||||||
|
*/
|
||||||
|
private Long syncDevice(DeviceInfo deviceInfo, DeviceType deviceType, String gatewayId) {
|
||||||
|
String iotDeviceId = deviceInfo.getId();
|
||||||
|
String deviceName = deviceInfo.getName();
|
||||||
|
String deviceSn = deviceName; // 使用设备名称作为SN号,网关其实是没有SN号的
|
||||||
|
|
||||||
|
// 查询设备是否已存在
|
||||||
|
Device existingDevice = deviceDomain.selectDeviceByIotDeviceId(iotDeviceId);
|
||||||
|
|
||||||
|
if (existingDevice == null) {
|
||||||
|
// 设备不存在,插入新设备
|
||||||
|
Device newDevice = new Device();
|
||||||
|
newDevice.setDeviceName(deviceName);
|
||||||
|
newDevice.setIotDeviceId(iotDeviceId);
|
||||||
|
newDevice.setDeviceType(deviceType.getCode());
|
||||||
|
newDevice.setDeviceSn(deviceSn);
|
||||||
|
newDevice.setGateway(gatewayId);
|
||||||
|
newDevice.setCreateBy("system");
|
||||||
|
|
||||||
|
deviceDomain.insertDevice(newDevice);
|
||||||
|
log.info("插入新设备: iotDeviceId={}, deviceName={}, deviceType={}", iotDeviceId, deviceName, deviceType);
|
||||||
|
return newDevice.getDeviceId();
|
||||||
|
} else {
|
||||||
|
// 设备已存在,检查是否需要更新
|
||||||
|
boolean needUpdate = false;
|
||||||
|
|
||||||
|
if (!Objects.equals(existingDevice.getDeviceName(), deviceName)) {
|
||||||
|
existingDevice.setDeviceName(deviceName);
|
||||||
|
needUpdate = true;
|
||||||
|
}
|
||||||
|
if (!Objects.equals(existingDevice.getDeviceType(), deviceType.getCode())) {
|
||||||
|
existingDevice.setDeviceType(deviceType.getCode());
|
||||||
|
needUpdate = true;
|
||||||
|
}
|
||||||
|
if (!Objects.equals(existingDevice.getDeviceSn(), deviceSn)) {
|
||||||
|
existingDevice.setDeviceSn(deviceSn);
|
||||||
|
needUpdate = true;
|
||||||
|
}
|
||||||
|
if (!Objects.equals(existingDevice.getGateway(), gatewayId)) {
|
||||||
|
existingDevice.setGateway(gatewayId);
|
||||||
|
needUpdate = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needUpdate) {
|
||||||
|
existingDevice.setUpdateBy("system");
|
||||||
|
deviceDomain.updateDevice(existingDevice);
|
||||||
|
log.info("更新设备: iotDeviceId={}, deviceName={}, deviceType={}", iotDeviceId, deviceName, deviceType);
|
||||||
|
}
|
||||||
|
|
||||||
|
return existingDevice.getDeviceId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步机场数据
|
||||||
|
*
|
||||||
|
* @param deviceId 设备主键ID
|
||||||
|
* @param deviceName 设备名称
|
||||||
|
*/
|
||||||
|
private void syncDock(Long deviceId, String deviceName) {
|
||||||
|
// 查询机场是否已存在
|
||||||
|
Dock existingDock = dockDomain.selectDockByDeviceId(deviceId);
|
||||||
|
|
||||||
|
if (existingDock == null) {
|
||||||
|
// 机场不存在,插入新机场
|
||||||
|
Dock newDock = new Dock();
|
||||||
|
newDock.setDockName(deviceName);
|
||||||
|
newDock.setDeviceId(deviceId);
|
||||||
|
newDock.setCreateBy("system");
|
||||||
|
|
||||||
|
dockDomain.insertDock(newDock);
|
||||||
|
log.info("插入新机场: deviceId={}, dockName={}", deviceId, deviceName);
|
||||||
|
}
|
||||||
|
// 机场已存在,无需更新
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步无人机数据
|
||||||
|
*
|
||||||
|
* @param deviceId 设备主键ID
|
||||||
|
* @param deviceName 设备名称
|
||||||
|
* @return 无人机主键ID
|
||||||
|
*/
|
||||||
|
private Long syncAircraft(Long deviceId, String deviceName) {
|
||||||
|
// 查询无人机是否已存在
|
||||||
|
Aircraft existingAircraft = aircraftDomain.selectAircraftByDeviceId(deviceId);
|
||||||
|
|
||||||
|
if (existingAircraft == null) {
|
||||||
|
// 无人机不存在,插入新无人机
|
||||||
|
Aircraft newAircraft = new Aircraft();
|
||||||
|
newAircraft.setAircraftName(deviceName);
|
||||||
|
newAircraft.setDeviceId(deviceId);
|
||||||
|
newAircraft.setCreateBy("system");
|
||||||
|
|
||||||
|
aircraftDomain.insertAircraft(newAircraft);
|
||||||
|
log.info("插入新无人机: deviceId={}, aircraftName={}", deviceId, deviceName);
|
||||||
|
return newAircraft.getAircraftId();
|
||||||
|
}
|
||||||
|
// 无人机已存在,无需更新
|
||||||
|
return existingAircraft.getAircraftId();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据设备SN号查找设备
|
||||||
|
*
|
||||||
|
* @param deviceSn 设备SN号
|
||||||
|
* @return 设备信息
|
||||||
|
*/
|
||||||
|
private Device findDeviceBySn(String deviceSn) {
|
||||||
|
Device queryDevice = new Device();
|
||||||
|
queryDevice.setDeviceSn(deviceSn);
|
||||||
|
List<Device> devices = deviceDomain.selectDeviceList(queryDevice);
|
||||||
|
return (devices != null && !devices.isEmpty()) ? devices.get(0) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步机场无人机关联数据
|
||||||
|
* 按照一个机场只会挂载一台无人机的逻辑进行处理
|
||||||
|
*
|
||||||
|
* @param dockDeviceId 机场设备主键ID
|
||||||
|
* @param aircraftDeviceId 无人机设备主键ID
|
||||||
|
*/
|
||||||
|
private void syncDockAircraft(Long dockDeviceId, Long aircraftDeviceId) {
|
||||||
|
// 获取机场主键
|
||||||
|
Dock dock = dockDomain.selectDockByDeviceId(dockDeviceId);
|
||||||
|
if (dock == null) {
|
||||||
|
log.warn("机场不存在,无法同步机场无人机关联: dockDeviceId={}", dockDeviceId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取无人机主键
|
||||||
|
Aircraft aircraft = aircraftDomain.selectAircraftByDeviceId(aircraftDeviceId);
|
||||||
|
if (aircraft == null) {
|
||||||
|
log.warn("无人机不存在,无法同步机场无人机关联: aircraftDeviceId={}", aircraftDeviceId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Long dockId = dock.getDockId();
|
||||||
|
Long aircraftId = aircraft.getAircraftId();
|
||||||
|
|
||||||
|
// 查询该机场是否已有关联
|
||||||
|
List<DockAircraft> existingRelations = dockAircraftDomain.selectDockAircraftByDockId(dockId);
|
||||||
|
|
||||||
|
if (existingRelations == null || existingRelations.isEmpty()) {
|
||||||
|
// 机场没有关联,插入新关联
|
||||||
|
DockAircraft newRelation = new DockAircraft();
|
||||||
|
newRelation.setDockId(dockId);
|
||||||
|
newRelation.setAircraftId(aircraftId);
|
||||||
|
newRelation.setCreateBy("system");
|
||||||
|
|
||||||
|
dockAircraftDomain.insertDockAircraft(newRelation);
|
||||||
|
log.info("插入机场无人机关联: dockId={}, aircraftId={}", dockId, aircraftId);
|
||||||
|
} else {
|
||||||
|
// 机场已有关联,检查是否需要更新
|
||||||
|
DockAircraft existingRelation = existingRelations.get(0);
|
||||||
|
if (!Objects.equals(existingRelation.getAircraftId(), aircraftId)) {
|
||||||
|
// 无人机发生变化,更新关联
|
||||||
|
existingRelation.setAircraftId(aircraftId);
|
||||||
|
existingRelation.setUpdateBy("system");
|
||||||
|
dockAircraftDomain.updateDockAircraft(existingRelation);
|
||||||
|
log.info("更新机场无人机关联: dockId={}, aircraftId={}", dockId, aircraftId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -27,3 +27,10 @@ spring:
|
||||||
# 共享配置
|
# 共享配置
|
||||||
shared-configs:
|
shared-configs:
|
||||||
- application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
|
- application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}
|
||||||
|
# 设备同步配置
|
||||||
|
device:
|
||||||
|
sync:
|
||||||
|
# 设备名称过滤正则表达式(多个用逗号分隔)
|
||||||
|
# 匹配这些正则表达式的设备将被跳过,不进行同步
|
||||||
|
# 当前配置:过滤所有以 TH 开头的设备
|
||||||
|
exclude-patterns: TH.*
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
where aircraft_id = #{aircraftId}
|
where aircraft_id = #{aircraftId}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<select id="selectAircraftByDeviceId" parameterType="Long" resultMap="AircraftResult">
|
||||||
|
<include refid="selectAircraftVo"/>
|
||||||
|
where device_id = #{deviceId}
|
||||||
|
limit 1
|
||||||
|
</select>
|
||||||
|
|
||||||
<select id="selectAircraftListByDeviceId" parameterType="Long" resultMap="AircraftResult">
|
<select id="selectAircraftListByDeviceId" parameterType="Long" resultMap="AircraftResult">
|
||||||
<include refid="selectAircraftVo"/>
|
<include refid="selectAircraftVo"/>
|
||||||
where device_id = #{deviceId}
|
where device_id = #{deviceId}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
where device_id = #{deviceId}
|
where device_id = #{deviceId}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<select id="selectDeviceByIotDeviceId" parameterType="String" resultMap="DeviceResult">
|
||||||
|
<include refid="selectDeviceVo"/>
|
||||||
|
where iot_device_id = #{iotDeviceId}
|
||||||
|
</select>
|
||||||
|
|
||||||
<select id="selectDeviceList" parameterType="com.ruoyi.device.mapper.entity.DeviceEntity" resultMap="DeviceResult">
|
<select id="selectDeviceList" parameterType="com.ruoyi.device.mapper.entity.DeviceEntity" resultMap="DeviceResult">
|
||||||
<include refid="selectDeviceVo"/>
|
<include refid="selectDeviceVo"/>
|
||||||
<where>
|
<where>
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||||
where dock_id = #{dockId}
|
where dock_id = #{dockId}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
<select id="selectDockByDeviceId" parameterType="Long" resultMap="DockResult">
|
||||||
|
<include refid="selectDockVo"/>
|
||||||
|
where device_id = #{deviceId}
|
||||||
|
limit 1
|
||||||
|
</select>
|
||||||
|
|
||||||
<select id="selectDockListByDeviceId" parameterType="Long" resultMap="DockResult">
|
<select id="selectDockListByDeviceId" parameterType="Long" resultMap="DockResult">
|
||||||
<include refid="selectDockVo"/>
|
<include refid="selectDockVo"/>
|
||||||
where device_id = #{deviceId}
|
where device_id = #{deviceId}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue