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;
}
}