/* * Copyright (c) 2018-2028, Chill Zhuang All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * Neither the name of the dreamlu.net developer nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * Author: Chill 庄骞 (smallchill@163.com) */ package org.sxkj.gd.workorder.service.impl; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import lombok.extern.slf4j.Slf4j; import org.sxkj.gd.utils.GdGeoAddressUtil; import org.sxkj.gd.utils.GeomUtils; import org.sxkj.gd.workorder.dto.GdXingtuAirportListDTO; import org.sxkj.gd.workorder.dto.GdXingtuPayloadListDTO; import org.sxkj.gd.workorder.dto.GdXingtuPilotListDTO; import org.sxkj.gd.workorder.entity.GdManageDeviceEntity; import org.sxkj.gd.workorder.entity.GdManageDevicePayloadEntity; import org.sxkj.gd.workorder.param.GdManageDevicePageParam; import org.sxkj.gd.workorder.service.IGdManageDevicePayloadService; import org.sxkj.gd.workorder.vo.GdManageDeviceVO; import org.sxkj.gd.workorder.excel.GdManageDeviceExcel; import org.sxkj.gd.workorder.mapper.GdManageDeviceMapper; import org.sxkj.gd.workorder.service.IGdManageDeviceService; import org.sxkj.gd.xingtu.JianXingtuApiService; import org.springframework.stereotype.Service; import com.baomidou.mybatisplus.core.conditions.Wrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import lombok.AllArgsConstructor; import org.springblade.core.mp.base.BaseServiceImpl; import org.springblade.core.tool.api.R; import org.springblade.core.tool.utils.StringUtil; import java.text.SimpleDateFormat; import java.util.*; import java.util.stream.Collectors; /** * 设备信息 服务实现类 * * @author lw * @since 2026-01-14 */ @Service @AllArgsConstructor @Slf4j public class GdManageDeviceServiceImpl extends BaseServiceImpl implements IGdManageDeviceService { private final JianXingtuApiService jianXingtuApiService; private final IGdManageDevicePayloadService iGdManageDevicePayloadService; @Override public IPage selectGdManageDevicePage(IPage page, GdManageDevicePageParam gdManageDevice) { List gdManageDeviceVOS = baseMapper.selectGdManageDevicePage(page, gdManageDevice); return page.setRecords(gdManageDeviceVOS); } @Override public List selectGdManageDevice(GdManageDevicePageParam gdManageDevice) { return baseMapper.selectGdManageDevice(gdManageDevice); } @Override public List exportGdManageDevice(Wrapper queryWrapper) { List gdManageDeviceList = baseMapper.exportGdManageDevice(queryWrapper); // gdManageDeviceList.forEach(gdManageDevice -> { // gdManageDevice.setTypeName(DictCache.getValue(DictEnum.YES_NO, GdManageDevice.getType())); //}); return gdManageDeviceList; } /** * 保存或更新设备信息 * * @param gdManageDeviceEntity * @return * @throws Exception */ @Override public boolean saveOrUpdateDevice(GdManageDeviceEntity gdManageDeviceEntity) throws Exception { // 获取设备位置 生成缓冲区 if (gdManageDeviceEntity.getLongitude() != null && gdManageDeviceEntity.getLatitude() != null) { String bufferAroundPointAsString = GeomUtils.getBufferAroundPointAsString(gdManageDeviceEntity.getLongitude(), gdManageDeviceEntity.getLatitude(), 5 * 1000); log.info("saveOrUpdateDevice {}", bufferAroundPointAsString); gdManageDeviceEntity.setGeom(bufferAroundPointAsString); String location = GdGeoAddressUtil.getFormattedAddress(gdManageDeviceEntity.getLongitude(), gdManageDeviceEntity.getLatitude()); gdManageDeviceEntity.setLocation(location); } return saveOrUpdate(gdManageDeviceEntity); } /** * 同步星图设备(无人机、机巢) * * @return 同步数量 */ @Override public int syncXingtuDevice() throws Exception { int total = 0; // 2.同步机巢设备 total += syncAirportDevices(); // 1.同步无人机设备 total += syncPilotDevices(); log.info("更新或新增设备" + total); return total; } /** * 同步星图无人机设备并入库 * * @return 新增或更新数量 */ private int syncPilotDevices() throws Exception { R response = jianXingtuApiService.getDevicePilotList(null); List items = parseList(response, GdXingtuPilotListDTO.class); if (items.isEmpty()) { return 0; } int count = 0; for (GdXingtuPilotListDTO item : items) { GdManageDeviceEntity entity = buildFromPilot(item); if (entity == null) { continue; } if (saveOrUpdateByAirportId(entity)) { // 同步负载设备 List loadList = item.getLoadList(); syncPayloadDevices(loadList); count++; } } return count; } /** * 同步星图机巢设备并入库 * * @return 新增或更新数量 */ private int syncAirportDevices() throws Exception { R response = jianXingtuApiService.getDeviceAirportList(null); List items = parseList(response, GdXingtuAirportListDTO.class); if (items.isEmpty()) { return 0; } int count = 0; for (GdXingtuAirportListDTO item : items) { GdManageDeviceEntity entity = buildFromAirport(item); if (entity == null) { continue; } if (saveOrUpdateByAirportId(entity)) { count++; } } return count; } /** * 同步负载设备 * * @param loadList 负载设备列表 */ private void syncPayloadDevices(List loadList) throws Exception { // 1. 收集所有负载设备的飞行员序列号,用于后续查询和删除操作 Set allPilotSnCodes = new HashSet<>(); if (loadList != null && !loadList.isEmpty()) { for (GdXingtuPayloadListDTO load : loadList) { if (load.getPilotSnCode() != null) { allPilotSnCodes.add(load.getPilotSnCode()); } } } // 2. 获取当前数据库中这些飞行员序列号对应的所有负载设备 List existingPayloads = new ArrayList<>(); if (!allPilotSnCodes.isEmpty()) { existingPayloads = iGdManageDevicePayloadService.list( Wrappers.lambdaQuery().in(GdManageDevicePayloadEntity::getPilotSnCode, allPilotSnCodes) ); } // 3. 创建现有负载设备的序列号映射,用于快速查找 Map existingPayloadMap = new HashMap<>(); for (GdManageDevicePayloadEntity payload : existingPayloads) { if (payload.getPilotSnCode() != null && payload.getSnCode() != null) { existingPayloadMap.put(payload.getPilotSnCode() + "_" + payload.getSnCode() + "_" + payload.getName(), payload); } } // 4. 处理负载设备列表 if (loadList != null && !loadList.isEmpty()) { List toSaveOrUpdate = new ArrayList<>(); Set currentPayloadKeys = new HashSet<>(); // 对负载设备列表进行去重处理,确保每个负载设备只被处理一次 Map uniqueLoadMap = new HashMap<>(); for (GdXingtuPayloadListDTO load : loadList) { if (load.getSnCode() != null && load.getPilotSnCode() != null) { String payloadKey = load.getPilotSnCode() + "_" + load.getSnCode() + "_" + load.getName(); uniqueLoadMap.put(payloadKey, load); } } // 处理去重后的负载设备列表 for (GdXingtuPayloadListDTO load : uniqueLoadMap.values()) { String snCode = load.getSnCode(); String pilotSnCode1 = load.getPilotSnCode(); String name = load.getName(); String payloadKey = pilotSnCode1 + "_" + snCode + "_" + name; currentPayloadKeys.add(payloadKey); // 检查是否已存在 GdManageDevicePayloadEntity existing = existingPayloadMap.remove(payloadKey); if (existing != null) { // 更新现有负载 existing.setName(load.getName()); existing.setType(load.getType()); toSaveOrUpdate.add(existing); } else { // 新增负载 GdManageDevicePayloadEntity newPayload = new GdManageDevicePayloadEntity(); newPayload.setName(load.getName()); newPayload.setType(load.getType()); newPayload.setSnCode(snCode); newPayload.setPilotSnCode(pilotSnCode1); // 可以根据需要设置区域编码等其他字段 toSaveOrUpdate.add(newPayload); } } // 4. 批量保存或更新负载设备 if (!toSaveOrUpdate.isEmpty()) { iGdManageDevicePayloadService.saveOrUpdateBatch(toSaveOrUpdate); } // 5. 删除数据库中存在但负载列表中不存在的负载设备 if (!existingPayloadMap.isEmpty()) { List toDeleteIds = existingPayloadMap.values().stream() .map(GdManageDevicePayloadEntity::getId) .collect(Collectors.toList()); iGdManageDevicePayloadService.removeByIds(toDeleteIds); } } else { // 如果负载列表为空,删除该飞行员序列号对应的所有负载设备 if (!existingPayloads.isEmpty()) { List toDeleteIds = existingPayloads.stream() .map(GdManageDevicePayloadEntity::getId) .collect(Collectors.toList()); iGdManageDevicePayloadService.removeByIds(toDeleteIds); } } } /** * 按机场ID保存或更新设备信息 * * @param entity 设备信息 * @return 是否保存成功 */ private boolean saveOrUpdateByAirportId(GdManageDeviceEntity entity) throws Exception { if (StringUtil.isBlank(entity.getAirportId())) { return false; } // 获取设备信息 GdManageDeviceEntity exist = lambdaQuery() .eq(GdManageDeviceEntity::getAirportId, entity.getAirportId()) .one(); // 获取父级设备信息 GdManageDeviceEntity parent = getOne(Wrappers.lambdaQuery().eq(GdManageDeviceEntity::getChildSn, entity.getDeviceSn())); if (exist != null) { entity.setId(exist.getId()); if (entity.getLongitude() == null) { entity.setLongitude(exist.getLongitude()); } if (entity.getLatitude() == null) { entity.setLatitude(exist.getLatitude()); } if (entity.getInsureExpiredTime() == null) { entity.setInsureExpiredTime(exist.getInsureExpiredTime()); } // 获取父级设备位置信息 if (parent != null) { entity.setLongitude(parent.getLongitude()); entity.setLatitude(parent.getLatitude()); entity.setHeight(parent.getHeight()); entity.setModeCode(Long.valueOf(parent.getStatus())); } } return saveOrUpdateDevice(entity); } /** * 从星图无人机数据构建设备实体 * * @param item 星图无人机数据 * @return 设备实体 */ private GdManageDeviceEntity buildFromPilot(GdXingtuPilotListDTO item) { if (item == null) { return null; } GdManageDeviceEntity entity = new GdManageDeviceEntity(); entity.setAirportId(item.getId()); entity.setDeviceSn(item.getSnCode()); entity.setDeviceName(item.getName()); entity.setNickname(item.getName()); entity.setDeviceType(1); entity.setLongitude(parseDouble(item.getLongitude())); entity.setLatitude(parseDouble(item.getLatitude())); entity.setFirmwareVersion(item.getFirmwareVersion()); entity.setModeCode(parseModeCode(item.getStatus())); entity.setInsureExpiredTime(parseInsuranceDate(item.getInsuranceInfo())); entity.setAreaCode(extractAreaCode(item.getRegionCode())); entity.setIsWithDock(item.getIsWithDock()); entity.setCreateDept(parseDeptId(item.getDeptId())); entity.setMaintenanceState(item.getMaintenanceState()); entity.setDevicePayload( Optional.ofNullable(item.getLoadList()) .orElse(Collections.emptyList()) .stream() .filter(Objects::nonNull) .map(load -> load.getName()) .filter(Objects::nonNull) .distinct() // 添加去重逻辑 .collect(Collectors.joining(",")) ); return entity; } /** * 从星图机巢数据构建设备实体 * * @param item 星图机巢数据 * @return 设备实体 */ private GdManageDeviceEntity buildFromAirport(GdXingtuAirportListDTO item) { if (item == null) { return null; } GdManageDeviceEntity entity = new GdManageDeviceEntity(); entity.setAirportId(item.getId()); entity.setDeviceSn(item.getSnCode()); entity.setDeviceName(item.getName()); entity.setNickname(item.getName()); entity.setDeviceType(0); entity.setLongitude(parseDouble(item.getLongitude())); entity.setLatitude(parseDouble(item.getLatitude())); entity.setFirmwareVersion(item.getFirmwareVersion()); entity.setChildSn(item.getPilotSnCode()); entity.setModeCode(parseModeCode(item.getStatus())); entity.setInsureExpiredTime(parseInsuranceDate(item.getInsuranceInfo())); entity.setAreaCode(extractAreaCode(item.getRegionCode())); entity.setHeight(item.getHeight()); entity.setCreateDept(parseDeptId(item.getDeptId())); entity.setMaintenanceState(item.getMaintenanceState()); return entity; } /** * 将设备在线状态转换为模式码 * * @param status 状态文本 * @return 模式码 */ private Long parseModeCode(String status) { if ("在线".equals(status)) { return 0L; } if ("离线".equals(status)) { return 1L; } return null; } /** * 安全解析字符串为 Double * * @param value 字符串值 * @return Double 或 null */ private Double parseDouble(String value) { if (StringUtil.isBlank(value)) { return null; } try { return Double.valueOf(value); } catch (Exception ignored) { return null; } } /** * 提取区域编码中的末级编码 * * @param regionCode 区域编码 * @return 末级区域编码 */ private String extractAreaCode(String regionCode) { if (StringUtil.isBlank(regionCode)) { return null; } String[] parts = regionCode.split(","); if (parts.length == 1) { parts = regionCode.split("-"); } for (int i = parts.length - 1; i >= 0; i--) { if (StringUtil.isNotBlank(parts[i])) { return parts[i]; } } return regionCode; } /** * 解析保险信息并获取最新保险日期 * * @param insuranceInfo 保险信息JSON * @return 保险日期 */ private Date parseInsuranceDate(String insuranceInfo) { if (StringUtil.isBlank(insuranceInfo)) { return null; } try { JSONArray array = JSON.parseArray(insuranceInfo); if (array == null || array.isEmpty()) { return null; } JSONObject last = array.getJSONObject(array.size() - 1); if (last == null) { return null; } Object dateValue = last.get("isInsuranceDate"); String dateStr = null; if (dateValue instanceof JSONArray) { JSONArray dateArray = (JSONArray) dateValue; if (!dateArray.isEmpty()) { dateStr = dateArray.getString(dateArray.size() - 1); } } else if (dateValue != null) { dateStr = String.valueOf(dateValue); } if (StringUtil.isBlank(dateStr)) { return null; } SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); return format.parse(dateStr); } catch (Exception ignored) { return null; } } /** * 解析部门ID字符串,处理逗号分隔的情况 * 星图接口可能返回逗号分隔的多个部门ID,这里只取第一个 * * @param deptIdStr 部门ID字符串,可能包含逗号分隔的多个ID * @return 第一个部门ID,解析失败返回null */ private Long parseDeptId(String deptIdStr) { // 1. 参数校验 if (StringUtil.isBlank(deptIdStr)) { return null; } try { // 2. 处理逗号分隔的情况,只取第一个ID String firstDeptId = deptIdStr.split(",")[0].trim(); // 3. 转换为Long类型 return Long.valueOf(firstDeptId); } catch (NumberFormatException e) { // 4. 记录日志并返回null log.warn("解析部门ID失败,deptIdStr: {}", deptIdStr, e); return null; } } /** * 解析星图接口返回的列表数据 * * @param response 接口响应 * @param clazz 目标类型 * @return 列表数据 */ private List parseList(R response, Class clazz) { if (response == null || !response.isSuccess() || response.getData() == null) { return Collections.emptyList(); } Object data = response.getData(); if (data instanceof String && StringUtil.isBlank((String) data)) { return Collections.emptyList(); } return JSON.parseArray(JSON.toJSONString(data), clazz); } }