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 selectArdDroneList(ArdDroneInfo ardDroneInfo) { List 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 existingDrones = getExistingDrones(boxSn); // 2. 授权数量验证 validateAuthorization(existingDrones.size(), boxSn.length); // 3. 获取并组装新的无人机信息 List 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 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 assembleNewDrones(String[] boxSn, Long deptId) { List allBoxes = tyService.getBoxList(); Set 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 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 servers = ardServerInfoService.selectArdServerInfoList(ardServerInfo); for (ArdServerInfo server : servers) { ArdDroneInfo droneInfo = new ArdDroneInfo(); droneInfo.setServerId(server.getId()); droneInfo.setUseDataScope(false); List localList = selectArdDroneList(droneInfo); Map localMap = localList.stream() .collect(Collectors.toMap(ArdDroneInfo::getBoxSn, drone -> drone)); List boxList = tyService.getBoxList(server.getId()); if (boxList == null) { updateArdDrone(ArdDroneInfo.builder().serverId(server.getId()).onLine(0).build()); continue; } for (BoxInfoReceiver remote : boxList) { String boxSn = remote.getBoxSn(); String redisKey = CacheConstants.UAV_ONLINE_KEY + boxSn; // 只处理在线的 Redis if (remote.getOnLine() == 1) { redisCache.setCacheObject(redisKey, remote, 30, TimeUnit.SECONDS); } else { redisCache.deleteObject(redisKey); } // 数据库同步 ArdDroneInfo local = localMap.get(boxSn); if (local == null) continue; if (updateChangedAttributes(local, remote)) { 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; } }