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.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.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;
|
|
/**
|
* 查询无人机盒子
|
*
|
* @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) {
|
// 参数校验
|
if (boxSn == null || boxSn.length == 0) {
|
log.warn("传入的盒子编码为空");
|
return 1;
|
}
|
if (deptId == null) {
|
throw new IllegalArgumentException("部门ID不能为空");
|
}
|
try {
|
// 1. 检查已存在的无人机盒子
|
List<ArdDroneInfo> existingDrones = getExistingDrones(boxSn);
|
// 2. 授权数量验证
|
validateAuthorization(existingDrones.size(), boxSn.length);
|
// 3. 获取并组装新的无人机信息
|
List<ArdDroneInfo> newDrones = assembleNewDrones(boxSn, deptId);
|
// 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 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) {
|
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))
|
.collect(Collectors.toList());
|
}
|
|
/**
|
* 创建无人机信息对象
|
*/
|
private ArdDroneInfo createDroneInfo(BoxInfoReceiver box, Long deptId) {
|
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);
|
|
// 设置创建信息和更新信息
|
setCreateAndUpdateInfo(drone);
|
|
return drone;
|
}
|
|
/**
|
* 设置创建和更新信息
|
*/
|
private void setCreateAndUpdateInfo(ArdDroneInfo drone) {
|
Date currentTime = new Date();
|
drone.setCreateTime(currentTime);
|
drone.setUpdateTime(currentTime);
|
}
|
|
/**
|
* 批量插入无人机信息
|
*/
|
private int batchInsertDrones(List<ArdDroneInfo> drones) {
|
if (drones.isEmpty()) {
|
return 0;
|
}
|
return ArdDroneInfoMapper.insertArdDroneLs(drones);
|
}
|
|
/**
|
* 同步盒子属性
|
*/
|
@Scheduled(fixedRate = 1000 * 5)
|
public void syncBoxInfo() {
|
ArdServerInfo ardServerInfo=new ArdServerInfo();
|
ardServerInfo.setUseDataScope(false);
|
List<ArdServerInfo> servers = ardServerInfoService.selectArdServerInfoList(ardServerInfo);
|
servers.stream()
|
.filter(server -> server.getServerStatus().equals("1"))
|
.forEach(server -> {
|
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, drone -> drone));
|
List<BoxInfoReceiver> boxList = tyService.getBoxList(server.getId());
|
if (boxList == null) {
|
updateArdDrone(ArdDroneInfo.builder().serverId(server.getId()).onLine(0).build());
|
return;
|
}
|
boxList.forEach(remote -> {
|
String boxSn = remote.getBoxSn();
|
|
// ✅ 维护在线状态缓存
|
// 只有在线的无人机才存 Redis
|
String redisKey = CacheConstants.UAV_ONLINE_KEY + boxSn;
|
if (remote.getOnLine() == 1) {
|
redisCache.setCacheObject(redisKey, remote, 30, TimeUnit.SECONDS);
|
} else {
|
redisCache.deleteObject(CacheConstants.UAV_ONLINE_KEY + boxSn);
|
}
|
// 数据库同步
|
ArdDroneInfo local = localMap.get(boxSn);
|
if (local != null && !local.isSameAs(remote)) {
|
updateChangedAttributes(local, remote);
|
updateArdDrone(local);
|
}
|
});
|
});
|
}
|
|
// 更新变化的属性
|
private void updateChangedAttributes(ArdDroneInfo localBoxInfo, BoxInfoReceiver remoteArdDrone) {
|
if (!Objects.equals(localBoxInfo.getBoxName(), remoteArdDrone.getBoxName())) {
|
localBoxInfo.setBoxName(remoteArdDrone.getBoxName());
|
}
|
if (!Objects.equals(localBoxInfo.getBoxVersion(), remoteArdDrone.getBoxVersion())) {
|
localBoxInfo.setBoxVersion(remoteArdDrone.getBoxVersion());
|
}
|
if (!Objects.equals(localBoxInfo.getLastTaskId(), remoteArdDrone.getLastTaskId())) {
|
localBoxInfo.setLastTaskId(remoteArdDrone.getLastTaskId());
|
}
|
if (!Objects.equals(localBoxInfo.getOnLine(), remoteArdDrone.getOnLine())) {
|
localBoxInfo.setOnLine(remoteArdDrone.getOnLine());
|
}
|
// 更新其他属性
|
}
|
}
|