package com.ard.work.service.impl;
|
|
import com.alibaba.fastjson2.JSONObject;
|
import com.ard.common.annotation.DataScope;
|
import com.ard.common.constant.CacheConstants;
|
import com.ard.common.core.redis.RedisCache;
|
import com.ard.common.exception.ServiceException;
|
import com.ard.common.utils.DateUtils;
|
import com.ard.common.utils.uuid.IdUtils;
|
import com.ard.system.authorize.service.AuthorizeService;
|
import com.ard.work.domain.dto.BoxSettingsDTO;
|
import com.ard.work.domain.entity.ArdDroneInfo;
|
import com.ard.work.domain.entity.ArdServerInfo;
|
import com.ard.work.domain.event.DeviceStatusEvent;
|
import com.ard.work.domain.receiver.BoxInfoReceiver;
|
import com.ard.work.domain.receiver.BoxStreamUrlReceiver;
|
import com.ard.work.mapper.ArdDroneInfoMapper;
|
import com.ard.work.service.IArdDroneInfoService;
|
import com.ard.work.service.IArdServerInfoService;
|
import com.ard.work.service.ITyService;
|
import lombok.extern.slf4j.Slf4j;
|
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.context.ApplicationEventPublisher;
|
import org.springframework.scheduling.annotation.Scheduled;
|
import org.springframework.stereotype.Service;
|
|
import javax.annotation.Resource;
|
import java.util.*;
|
import java.util.concurrent.TimeUnit;
|
import java.util.stream.Collectors;
|
|
/**
|
* 无人机盒子Service业务层处理
|
*
|
* @author ard
|
* @date 2025-03-04
|
*/
|
@Slf4j
|
@Service
|
public class ArdDroneInfoServiceImpl implements IArdDroneInfoService {
|
|
@Resource
|
private ArdDroneInfoMapper ArdDroneInfoMapper;
|
|
@Resource
|
private ITyService tyService;
|
|
@Resource
|
private AuthorizeService authorizeService;
|
|
@Resource
|
private RedisCache redisCache;
|
|
@Autowired
|
private IArdServerInfoService ardServerInfoService;
|
|
// 自动注入 ApplicationEventPublisher
|
@Autowired
|
private ApplicationEventPublisher eventPublisher;
|
|
/**
|
* 查询无人机盒子
|
*
|
* @param id 无人机盒子主键
|
* @return 无人机盒子
|
*/
|
@Override
|
public ArdDroneInfo selectArdDroneById(String id) {
|
return ArdDroneInfoMapper.selectArdDroneById(id);
|
}
|
|
/**
|
* 查询无人机盒子
|
*
|
* @param boxSn 盒子编码
|
* @return 无人机盒子
|
*/
|
@Override
|
public ArdDroneInfo selectArdDroneByboxSn(String boxSn) {
|
return ArdDroneInfoMapper.selectArdDroneByBoxSn(boxSn);
|
}
|
|
/**
|
* 查询无人机盒子列表
|
*
|
* @param ardDroneInfo 无人机盒子
|
* @return 无人机盒子
|
*/
|
@Override
|
@DataScope(deptAlias = "d", userAlias = "u")
|
public List<ArdDroneInfo> selectArdDroneList(ArdDroneInfo ardDroneInfo) {
|
List<ArdDroneInfo> ardDroneInfos = ArdDroneInfoMapper.selectArdDroneList(ardDroneInfo);
|
// 使用传统的 for 循环避免 forEach 带来的性能开销
|
for (ArdDroneInfo uav : ardDroneInfos) {
|
try {
|
BoxStreamUrlReceiver boxLiveUrl = tyService.getBoxLiveUrl(uav.getBoxSn());
|
if (boxLiveUrl != null) {
|
uav.setWebrtcUrl(boxLiveUrl.getRtc());
|
}
|
} catch (Exception e) {
|
log.error("获取设备 {} 的直播地址失败", uav.getBoxSn(), e);
|
}
|
}
|
return ardDroneInfos;
|
}
|
|
/**
|
* 新增无人机盒子
|
*
|
* @param ardDroneInfo 无人机盒子
|
* @return 结果
|
*/
|
@Override
|
public int insertArdDrone(ArdDroneInfo ardDroneInfo) {
|
ardDroneInfo.setId(IdUtils.fastUUID());
|
ardDroneInfo.setCreateTime(DateUtils.getNowDate());
|
return ArdDroneInfoMapper.insertArdDrone(ardDroneInfo);
|
}
|
|
/**
|
* 修改无人机盒子
|
*
|
* @param ArdDroneInfo 无人机盒子
|
* @return 结果
|
*/
|
@Override
|
public int updateArdDrone(ArdDroneInfo ArdDroneInfo) {
|
Boolean b =
|
tyService.updateBoxSettings(BoxSettingsDTO.builder().boxSn(ArdDroneInfo.getBoxSn()).boxName(ArdDroneInfo.getBoxName()).build());
|
if (!b) {
|
throw new ServiceException("修改无人机盒子名称失败");
|
}
|
ArdDroneInfo.setUpdateTime(DateUtils.getNowDate());
|
return ArdDroneInfoMapper.updateArdDrone(ArdDroneInfo);
|
}
|
|
/**
|
* 批量删除无人机盒子
|
*
|
* @param ids 需要删除的无人机盒子主键
|
* @return 结果
|
*/
|
@Override
|
public int deleteArdDroneByIds(String[] ids) {
|
return ArdDroneInfoMapper.deleteArdDroneByIds(ids);
|
}
|
|
/**
|
* 删除无人机盒子信息
|
*
|
* @param id 无人机盒子主键
|
* @return 结果
|
*/
|
@Override
|
public int deleteArdDroneById(String id) {
|
return ArdDroneInfoMapper.deleteArdDroneById(id);
|
}
|
|
/**
|
* 批量新增无人机盒子
|
*
|
* @param boxSn 盒子编码数组
|
* @param deptId 部门ID
|
* @return 成功插入的记录数
|
*/
|
@Override
|
public int insertArdDroneLs(String[] boxSn,Long deptId,String serverId) {
|
// 参数校验
|
if (boxSn == null || boxSn.length == 0) {
|
log.warn("传入的盒子编码为空");
|
return 1;
|
}
|
if (deptId == null) {
|
throw new IllegalArgumentException("部门ID不能为空");
|
}
|
try {
|
// 1. 检查已存在的无人机盒子
|
List<ArdDroneInfo> existingDrones = getExistingDrones(boxSn);
|
// 1.1 新增时不能添加重复的boxSn
|
checkDuplicateBox(existingDrones, boxSn);
|
// 2. 授权数量验证
|
validateAuthorization(existingDrones.size(), boxSn.length);
|
// 3. 获取并组装新的无人机信息
|
List<ArdDroneInfo> newDrones = assembleNewDrones(boxSn,deptId,serverId);
|
// 4. 批量插入
|
return batchInsertDrones(newDrones);
|
} catch (Exception e) {
|
log.error("批量新增无人机盒子失败, boxSn: {}, deptId: {}", Arrays.toString(boxSn), deptId, e);
|
throw new ServiceException("批量新增无人机盒子失败: " + e.getMessage());
|
}
|
}
|
|
/**
|
* 修改无人机状态
|
*/
|
@Override
|
public int updateDroneStatus(String serverId, String status) {
|
return ArdDroneInfoMapper.updateDroneStatus(serverId, status);
|
}
|
|
/**
|
* 获取已存在的无人机信息
|
*/
|
private List<ArdDroneInfo> getExistingDrones(String[] boxSn) {
|
return Arrays.stream(boxSn)
|
.map(ArdDroneInfoMapper::selectArdDroneByBoxSn)
|
.filter(Objects::nonNull)
|
.collect(Collectors.toList());
|
}
|
|
/**
|
* 检查是否有重复的盒子编码
|
*/
|
private void checkDuplicateBox(List<ArdDroneInfo> existingDrone,String[] boxSn) {
|
Set<String> existingBoxSnSet = new HashSet<>();
|
for (ArdDroneInfo droneInfo : existingDrone) {
|
existingBoxSnSet.add(droneInfo.getBoxSn());
|
}
|
|
// 检查是否有重复的盒子编码
|
List<String> duplicateBoxSns = new ArrayList<>();
|
for (String sn : boxSn) {
|
if (existingBoxSnSet.contains(sn)) {
|
duplicateBoxSns.add(sn);
|
}
|
}
|
|
if (!duplicateBoxSns.isEmpty()) {
|
log.warn("存在重复的无人机盒子编码: {}", duplicateBoxSns);
|
throw new ServiceException("以下无人机盒子编码已存在: " + String.join(", ", duplicateBoxSns));
|
}
|
}
|
|
/**
|
* 验证授权数量
|
*/
|
private void validateAuthorization(int existingCount, int newCount) {
|
int totalCount = existingCount + newCount;
|
|
JSONObject authInfo = authorizeService.getAuthorizationInfo();
|
String authType = authInfo.getString("Type");
|
|
switch (authType) {
|
case "normal":
|
int allowedDroneNum = authInfo.getIntValue("droneNum");
|
if (totalCount > allowedDroneNum) {
|
throw new RuntimeException(String.format(
|
"授权数量%d,当前操作将导致总数达到%d,超过授权限制",
|
allowedDroneNum, totalCount));
|
}
|
break;
|
case "expired":
|
throw new RuntimeException("授权文件已过期");
|
case "mismatch":
|
throw new RuntimeException("授权文件不匹配");
|
default:
|
throw new RuntimeException("未知的授权类型: " + authType);
|
}
|
}
|
|
/**
|
* 组装新的无人机信息
|
*/
|
private List<ArdDroneInfo> assembleNewDrones(String[] boxSn,Long deptId,String serverId) {
|
List<BoxInfoReceiver> allBoxes = tyService.getBoxList();
|
Set<String> targetBoxSnSet = new HashSet<>(Arrays.asList(boxSn));
|
return allBoxes.stream()
|
.filter(box -> targetBoxSnSet.contains(box.getBoxSn()))
|
.map(box -> createDroneInfo(box, deptId, serverId))
|
.collect(Collectors.toList());
|
}
|
|
/**
|
* 创建无人机信息对象
|
*/
|
private ArdDroneInfo createDroneInfo(BoxInfoReceiver box, Long deptId ,String serverId) {
|
ArdDroneInfo drone = new ArdDroneInfo();
|
drone.setId(IdUtils.simpleUUID());
|
drone.setBoxSn(box.getBoxSn());
|
drone.setBoxName(box.getBoxName());
|
drone.setStartTime(new Date());
|
drone.setOnLine(box.getOnLine());
|
drone.setVideoServer(box.getVideoServer());
|
drone.setVideoPort(box.getVideoPort());
|
drone.setLastTaskId(box.getLastTaskId());
|
drone.setDeptId(deptId);
|
drone.setCreateTime(new Date());
|
drone.setUpdateTime(new Date());
|
drone.setServerId(serverId);
|
drone.setBoxVersion(box.getBoxVersion());
|
return drone;
|
}
|
|
|
/**
|
* 批量插入无人机信息
|
*/
|
private int batchInsertDrones(List<ArdDroneInfo> drones) {
|
if (drones.isEmpty()) {
|
return 0;
|
}
|
return ArdDroneInfoMapper.insertArdDroneLs(drones);
|
}
|
|
/**
|
* 同步盒子属性
|
*/
|
@Scheduled(fixedRate = 5000)
|
public void syncBoxInfo() {
|
ArdServerInfo ardServerInfo = new ArdServerInfo();
|
ardServerInfo.setUseDataScope(false);
|
List<ArdServerInfo> servers = ardServerInfoService.selectArdServerInfoList(ardServerInfo);
|
|
for (ArdServerInfo server : servers) {
|
ArdDroneInfo droneInfo = new ArdDroneInfo();
|
droneInfo.setServerId(server.getId());
|
droneInfo.setUseDataScope(false);
|
List<ArdDroneInfo> localList = selectArdDroneList(droneInfo);
|
Map<String, ArdDroneInfo> localMap = localList.stream().collect(Collectors.toMap(ArdDroneInfo::getBoxSn,
|
d -> d));
|
List<BoxInfoReceiver> boxList = tyService.getBoxList(server.getId());
|
// server 没有返回设备列表 → 全部离线
|
if (boxList == null || boxList.isEmpty()) {
|
for (ArdDroneInfo local : localList) {
|
if (local.getOnLine() != 0) { // 仅当状态变化才更新
|
local.setOnLine(0);
|
ArdDroneInfoMapper.updateArdDrone(local);
|
eventPublisher.publishEvent(new DeviceStatusEvent(local.getBoxSn(), false));
|
redisCache.deleteObject(CacheConstants.UAV_ONLINE_KEY + local.getBoxSn());
|
}
|
}
|
continue;
|
}
|
// server 返回设备列表 → 通过缓存判断状态
|
for (BoxInfoReceiver remote : boxList) {
|
String boxSn = remote.getBoxSn();
|
String redisKey = CacheConstants.UAV_ONLINE_KEY + boxSn;
|
boolean currentOnline = remote.getOnLine() == 1;
|
|
ArdDroneInfo local = localMap.get(boxSn);
|
boolean dbOnline = local != null && local.getOnLine() == 1;
|
boolean needUpdate = (dbOnline != currentOnline);
|
|
// 如果状态变化才更新
|
if (needUpdate){
|
// 更新 Redis
|
if (currentOnline) {
|
redisCache.setCacheObject(redisKey, remote);
|
} else {
|
redisCache.deleteObject(redisKey);
|
}
|
// 发布事件
|
eventPublisher.publishEvent(new DeviceStatusEvent(boxSn, currentOnline));
|
// 更新数据库
|
if (local != null) {
|
local.setOnLine(currentOnline ? 1 : 0);
|
updateArdDrone(local);
|
}
|
}
|
}
|
}
|
}
|
|
|
/**
|
* 更新 localBoxInfo 的字段,如果有变化返回 true,否则返回 false
|
*/
|
private boolean updateChangedAttributes(ArdDroneInfo localBoxInfo, BoxInfoReceiver remoteArdDrone) {
|
boolean changed = false;
|
|
if (!Objects.equals(localBoxInfo.getBoxName(), remoteArdDrone.getBoxName())) {
|
localBoxInfo.setBoxName(remoteArdDrone.getBoxName());
|
changed = true;
|
}
|
if (!Objects.equals(localBoxInfo.getBoxVersion(), remoteArdDrone.getBoxVersion())) {
|
localBoxInfo.setBoxVersion(remoteArdDrone.getBoxVersion());
|
changed = true;
|
}
|
if (!Objects.equals(localBoxInfo.getLastTaskId(), remoteArdDrone.getLastTaskId())) {
|
localBoxInfo.setLastTaskId(remoteArdDrone.getLastTaskId());
|
changed = true;
|
}
|
if (!Objects.equals(localBoxInfo.getOnLine(), remoteArdDrone.getOnLine())) {
|
localBoxInfo.setOnLine(remoteArdDrone.getOnLine());
|
changed = true;
|
}
|
|
// TODO: 更新其他属性时,如果变化也设置 changed = true
|
|
return changed;
|
}
|
|
}
|