package com.xiaopeng.map.service.serviceimpl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.xiaopeng.api.feignClient.vehicleConvoy.XpVehicleClient; import com.xiaopeng.api.model.operation.XpElectronicFence; import com.xiaopeng.api.model.operation.XpElectronicFenceArea; import com.xiaopeng.api.model.vehicleConvoy.XpVehicle; import com.xiaopeng.common.core.domain.R; import com.xiaopeng.map.dto.AreaInput; import com.xiaopeng.map.mapper.XpElectronicFenceAreaMapper; import com.xiaopeng.map.mapper.XpElectronicFenceMapper; import com.xiaopeng.map.service.AdcFencePushService; import com.xiaopeng.map.service.AdcOssHandleService; import com.xiaopeng.map.utils.XngpAreaRTreeFbsUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; /** * ADC 围栏推送编排服务实现。 *

* 版本管理:围栏主表 xp_electronic_fence.xpu_version 字段, * 每次触发推送时更新为秒级时间戳(与OSS文件名中的version一致),车端初始化时比对该版本号。 *

* 注意:AdcOssHandleService 使用 @ConditionalOnBean(RobotaxiOssService.class), * 如果 OSS 配置缺失则不会注册,因此这里用 required=false 注入,为空时跳过推送。 */ @Slf4j @Service public class AdcFencePushServiceImpl implements AdcFencePushService { @Autowired(required = false) private AdcOssHandleService adcOssHandleService; @Autowired private XpVehicleClient xpVehicleClient; @Autowired private XpElectronicFenceAreaMapper xpElectronicFenceAreaMapper; @Autowired private XpElectronicFenceMapper xpElectronicFenceMapper; @Override public void pushFenceToVehicles(Long electronicId) { log.info("[ADC推送] pushFenceToVehicles 开始, electronicId={}", electronicId); if (!isOssAvailable()) return; List areaInputs = queryAreaInputs(electronicId); if (areaInputs.isEmpty()) { log.info("[ADC推送] 围栏{}无启用区域,跳过推送", electronicId); return; } log.info("[ADC推送] 围栏{}查询到{}个启用区域", electronicId, areaInputs.size()); List vehicles = queryBoundVehicles(electronicId); if (vehicles.isEmpty()) { log.info("[ADC推送] 围栏{}无绑定车辆,跳过推送", electronicId); return; } log.info("[ADC推送] 围栏{}查询到{}辆绑定车辆", electronicId, vehicles.size()); // 先更新版本号 long version = updateXpuVersion(electronicId); for (XpVehicle vehicle : vehicles) { pushToSingleVehicle(vehicle.getVehicleVin(), areaInputs, electronicId, version); } log.info("[ADC推送] pushFenceToVehicles 完成, electronicId={}", electronicId); } @Override public void deactivateFenceForVehicles(Long electronicId) { if (!isOssAvailable()) return; List vehicles = queryBoundVehicles(electronicId); if (vehicles.isEmpty()) return; // 更新版本号(秒级时间戳) long version = updateXpuVersion(electronicId); for (XpVehicle vehicle : vehicles) { try { if (StringUtils.hasText(vehicle.getVehicleVin())) { adcOssHandleService.uploadEmptyFence(vehicle.getVehicleVin(), version); log.info("[ADC推送] 围栏{}失效,已向车辆{}上传空FBS", electronicId, vehicle.getVehicleVin()); } } catch (Exception e) { log.info("[ADC推送] 向车辆{}上传失效空FBS失败", vehicle.getVehicleVin(), e); } } } @Override public void pushFenceToSingleVehicle(String vehicleVin, Long electronicId) { if (!isOssAvailable()) return; List areaInputs = queryAreaInputs(electronicId); if (areaInputs.isEmpty()) { log.info("[ADC推送] 围栏{}无启用区域,跳过推送至车辆{}", electronicId, vehicleVin); return; } // 更新版本号(秒级时间戳) long version = updateXpuVersion(electronicId); pushToSingleVehicle(vehicleVin, areaInputs, electronicId, version); } @Override public String getLatestPushVersion(String vehicleVin) { try { // 通过VIN查询车辆绑定的围栏,再返回围栏的 xpu_version R vehicleR = xpVehicleClient.findByVehicleVin(vehicleVin); if (vehicleR == null || vehicleR.getData() == null || vehicleR.getData().getElectronicId() == null) { log.info("[ADC推送] 车辆{}未绑定围栏,无版本号", vehicleVin); return null; } Long electronicId = vehicleR.getData().getElectronicId(); XpElectronicFence fence = xpElectronicFenceMapper.selectById(electronicId); if (fence == null || fence.getXpuVersion() == null) { return null; } return String.valueOf(fence.getXpuVersion()); } catch (Exception e) { log.info("[ADC推送] 查询车辆{}绑定围栏版本失败", vehicleVin, e); } return null; } // ==================== 内部方法 ==================== private void pushToSingleVehicle(String vehicleVin, List areaInputs, Long electronicId, long version) { if (!StringUtils.hasText(vehicleVin)) return; long start = System.currentTimeMillis(); try { adcOssHandleService.uploadOddLayerFile(areaInputs, vehicleVin, version); long cost = System.currentTimeMillis() - start; log.info("[ADC推送] 围栏{}推送至车辆{}成功,区域数={},version={},耗时={}ms", electronicId, vehicleVin, areaInputs.size(), version, cost); } catch (Exception e) { long cost = System.currentTimeMillis() - start; log.info("[ADC推送] 围栏{}推送至车辆{}失败,耗时={}ms", electronicId, vehicleVin, cost, e); } } /** * 将围栏的 xpu_version 更新为当前秒级时间戳(与 OSS 文件名中的 version 保持一致) * * @return 写入的版本号(秒级时间戳) */ private long updateXpuVersion(Long electronicId) { long version = System.currentTimeMillis() / 1000; try { xpElectronicFenceMapper.update(null, new LambdaUpdateWrapper() .eq(XpElectronicFence::getId, electronicId) .set(XpElectronicFence::getXpuVersion, version)); log.info("[ADC推送] 围栏{}版本号已更新为{}", electronicId, version); } catch (Exception e) { log.info("[ADC推送] 围栏{}版本号更新失败", electronicId, e); } return version; } private List queryAreaInputs(Long electronicId) { List areas = xpElectronicFenceAreaMapper.selectList( new LambdaQueryWrapper() .eq(XpElectronicFenceArea::getElectronicId, electronicId) .eq(XpElectronicFenceArea::getAreaStatus, 1) ); if (CollectionUtils.isEmpty(areas)) { log.info("[ADC推送] 围栏{}查询启用区域结果为空", electronicId); return Collections.emptyList(); } log.info("[ADC推送] 围栏{}查询到{}个启用区域记录", electronicId, areas.size()); List result = new java.util.ArrayList<>(); for (XpElectronicFenceArea area : areas) { AreaInput input = convertToAreaInput(area); if (input != null) { result.add(input); } } log.info("[ADC推送] 围栏{}转换后有效AreaInput数量={}", electronicId, result.size()); return result; } private AreaInput convertToAreaInput(XpElectronicFenceArea area) { log.info("[ADC推送] convertToAreaInput: areaId={}, areaType={}, areaStatus={}, fencePoints={}", area.getId(), area.getAreaType(), area.getAreaStatus(), area.getFencePoints() == null ? "null" : (area.getFencePoints().length() > 200 ? area.getFencePoints().substring(0, 200) + "..." : area.getFencePoints())); if (!StringUtils.hasText(area.getFencePoints())) { log.info("[ADC推送] 区域{}的fencePoints为空,跳过", area.getId()); return null; } List points = XngpAreaRTreeFbsUtil.parseGcl(area.getFencePoints()); log.info("[ADC推送] 区域{}解析坐标点数量={}", area.getId(), points == null ? 0 : points.size()); if (CollectionUtils.isEmpty(points)) { log.info("[ADC推送] 区域{}解析后坐标点为空,fencePoints原始值={}", area.getId(), area.getFencePoints()); return null; } return AreaInput.builder() .areaId(area.getId().intValue()) .isBlacklist(area.getAreaType() == 2) .points(points) .build(); } private List queryBoundVehicles(Long electronicId) { try { R> result = xpVehicleClient.listAllByFenceId(electronicId); if (result != null && result.getData() != null) { return result.getData(); } } catch (Exception e) { log.info("[ADC推送] 查询围栏{}绑定车辆失败", electronicId, e); } return Collections.emptyList(); } private boolean isOssAvailable() { if (adcOssHandleService == null) { log.info("[ADC推送] AdcOssHandleService未注册(OSS配置缺失),跳过推送"); return false; } return true; } }