| | |
| | | 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 com.ard.work.util.UtilMethod; |
| | | 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 ruoyi |
| | | * @author ard |
| | | * @date 2025-03-04 |
| | | */ |
| | | @Slf4j |
| | |
| | | |
| | | @Resource |
| | | private AuthorizeService authorizeService; |
| | | |
| | | @Resource |
| | | private RedisCache redisCache; |
| | | |
| | | @Autowired |
| | | private IArdServerInfoService ardServerInfoService; |
| | | |
| | | // 自动注入 ApplicationEventPublisher |
| | | @Autowired |
| | | private ApplicationEventPublisher eventPublisher; |
| | | |
| | | /** |
| | | * 查询无人机盒子 |
| | |
| | | /** |
| | | * 查询无人机盒子列表 |
| | | * |
| | | * @param ArdDroneInfo 无人机盒子 |
| | | * @param ardDroneInfo 无人机盒子 |
| | | * @return 无人机盒子 |
| | | */ |
| | | @Override |
| | | public List<ArdDroneInfo> selectArdDroneList(ArdDroneInfo ArdDroneInfo) { |
| | | return ArdDroneInfoMapper.selectArdDroneList(ArdDroneInfo); |
| | | @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; |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | @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 boxSn 盒子编码数组 |
| | | * @param boxSn 盒子编码数组 |
| | | * @param deptId 部门ID |
| | | * @return 成功插入的记录数 |
| | | */ |
| | | @Override |
| | | public int insertArdDroneLs(String[] boxSn, Long deptId) { |
| | | public int insertArdDroneLs(String[] boxSn,Long deptId,String serverId) { |
| | | // 参数校验 |
| | | if (boxSn == null || boxSn.length == 0) { |
| | | log.warn("传入的盒子编码为空"); |
| | |
| | | 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); |
| | | List<ArdDroneInfo> newDrones = assembleNewDrones(boxSn,deptId,serverId); |
| | | // 4. 批量插入 |
| | | return batchInsertDrones(newDrones); |
| | | } catch (Exception e) { |
| | |
| | | .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 List<ArdDroneInfo> assembleNewDrones(String[] boxSn, Long deptId) { |
| | | 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)) |
| | | .map(box -> createDroneInfo(box, deptId, serverId)) |
| | | .collect(Collectors.toList()); |
| | | } |
| | | |
| | | /** |
| | | * 创建无人机信息对象 |
| | | */ |
| | | private ArdDroneInfo createDroneInfo(BoxInfoReceiver box, Long deptId) { |
| | | 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.setBoxAliasName(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); |
| | | |
| | | drone.setCreateTime(new Date()); |
| | | drone.setUpdateTime(new Date()); |
| | | drone.setServerId(serverId); |
| | | drone.setBoxVersion(box.getBoxVersion()); |
| | | return drone; |
| | | } |
| | | |
| | | /** |
| | | * 设置创建和更新信息 |
| | | */ |
| | | private void setCreateAndUpdateInfo(ArdDroneInfo drone) { |
| | | Date currentTime = new Date(); |
| | | drone.setCreateTime(currentTime); |
| | | drone.setUpdateTime(currentTime); |
| | | } |
| | | |
| | | /** |
| | | * 批量插入无人机信息 |
| | |
| | | |
| | | /** |
| | | * 同步盒子属性 |
| | | * |
| | | * @return 结果 |
| | | */ |
| | | @Scheduled(fixedRate = 1000 * 10) |
| | | @Scheduled(fixedRate = 5000) |
| | | public void syncBoxInfo() { |
| | | List<ArdDroneInfo> ArdDroneeListEntity = selectArdDroneList(new ArdDroneInfo()); |
| | | // 将本地列表转换为Map,方便快速查找 |
| | | Map<String, ArdDroneInfo> localArdDroneMap = ArdDroneeListEntity.stream() |
| | | .collect(Collectors.toMap(ArdDroneInfo::getBoxSn, drone -> drone)); |
| | | //遍历接口最新获取的无人机盒子信息 |
| | | tyService.getBoxList().forEach(remoteArdDrone -> { |
| | | ArdDroneInfo localArdDroneInfo = localArdDroneMap.get(remoteArdDrone.getBoxSn()); |
| | | if (localArdDroneInfo != null) { |
| | | if (!localArdDroneInfo.isSameAs(remoteArdDrone)) { |
| | | // 修改无人机(只更新变化的属性) |
| | | updateChangedAttributes(localArdDroneInfo, remoteArdDrone); |
| | | updateArdDrone(localArdDroneInfo); |
| | | log.debug("定时执行同步盒子属性任务"); |
| | | try { |
| | | 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); |
| | | // 只要远端设备在线,就刷新Redis缓存(无论状态是否变化),保证缓存一定存在 |
| | | if (currentOnline) { |
| | | redisCache.setCacheObject(redisKey, remote); |
| | | } else { |
| | | // 远端离线,删除Redis缓存 |
| | | redisCache.deleteObject(redisKey); |
| | | } |
| | | // 如果状态变化才更新 |
| | | if (needUpdate) { |
| | | // 发布事件 |
| | | eventPublisher.publishEvent(new DeviceStatusEvent(boxSn, currentOnline)); |
| | | // 更新数据库 |
| | | if (local != null) { |
| | | local.setOnLine(currentOnline ? 1 : 0); |
| | | updateArdDrone(local); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | }); |
| | | } catch (Exception e) { |
| | | log.error("同步盒子属性任务异常", e); |
| | | } |
| | | } |
| | | |
| | | // 更新变化的属性 |
| | | 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()); |
| | | } |
| | | // 更新其他属性 |
| | | } |
| | | |
| | | |
| | | } |