kong
2026-04-12 7f3081da3c0597cfbfc8f0fc4e342c4ceb901f6d
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/service/serviceimpl/XpPointServiceImpl.java
@@ -10,9 +10,12 @@
import com.xiaopeng.api.dto.operation.PointMapQuery;
import com.xiaopeng.api.dto.order.HistoryEndPointsDto;
import com.xiaopeng.api.dto.order.HistoryStartPointsDto;
import com.xiaopeng.api.feignClient.account.XpAppUserCollectAddrClient;
import com.xiaopeng.api.model.operation.XpElectronicFenceArea;
import com.xiaopeng.api.model.operation.XpElectronicFence;
import com.xiaopeng.api.model.operation.XpPoint;
import com.xiaopeng.api.model.operation.XpRegion;
import com.xiaopeng.api.model.operation.XpStation;
import com.xiaopeng.api.model.operation.XpSystemSetting;
import com.xiaopeng.api.query.operation.PointListQuery;
import com.xiaopeng.api.query.operation.PointPageListListQuery;
@@ -21,6 +24,7 @@
import com.xiaopeng.api.vo.order.XpPointsListVo;
import com.xiaopeng.api.vo.order.XpPointsSearchVo;
import com.xiaopeng.common.core.constant.CacheConstants;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.exception.ServiceException;
import com.xiaopeng.common.core.utils.StringUtils;
import com.xiaopeng.common.core.web.page.PageInfo;
@@ -28,18 +32,23 @@
import com.xiaopeng.evi.oss.service.AliOssService;
import com.xiaopeng.map.constant.I18nKeyConstant;
import com.xiaopeng.map.dto.NearestDto;
import com.xiaopeng.map.dto.StationDetailQueryDto;
import com.xiaopeng.map.mapper.XpPointMapper;
import com.xiaopeng.map.mapper.XpRegionMapper;
import com.xiaopeng.map.mapper.XpSystemSettingMapper;
import com.xiaopeng.map.query.PointByNameQuery;
import com.xiaopeng.map.service.XpElectronicFenceService;
import com.xiaopeng.map.service.XpElectronicFenceAreaService;
import com.xiaopeng.map.service.XpPointService;
import com.xiaopeng.map.service.XpStationService;
import com.xiaopeng.map.utils.TimeRangeChecker;
import com.xiaopeng.map.utils.holiday.HolidayUtil;
import com.xiaopeng.map.utils.map.map.FastDistanceCalculator;
import com.xiaopeng.map.vo.NearestNextVo;
import com.xiaopeng.map.vo.NearestVo;
import com.xiaopeng.map.vo.OrderDistanceVo;
import com.xiaopeng.map.vo.StationDetailVo;
import com.xiaopeng.map.vo.StationPointItemVo;
import lombok.extern.slf4j.Slf4j;
import net.sourceforge.pinyin4j.PinyinHelper;
import org.apache.shardingsphere.transaction.annotation.ShardingSphereTransactionType;
@@ -53,6 +62,9 @@
import javax.annotation.Resource;
import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
@@ -87,6 +99,15 @@
    @Resource
    private XpElectronicFenceAreaService xpElectronicFenceAreaService;
    @Resource
    private XpElectronicFenceService xpElectronicFenceService;
    @Resource
    private XpStationService xpStationService;
    @Resource
    private XpAppUserCollectAddrClient xpAppUserCollectAddrClient;
    @Resource
    private XpRegionMapper xpRegionMapper;
@@ -370,6 +391,63 @@
    }
    @Override
    public StationDetailVo queryStationDetail(StationDetailQueryDto dto, String uid) {
        StationDetailVo stationDetailVo = new StationDetailVo();
        if (dto == null || dto.getStationId() == null) {
            return stationDetailVo;
        }
        XpStation station = xpStationService.getById(dto.getStationId());
        if (station == null || !isStationAvailable(station)) {
            return stationDetailVo;
        }
        // 仅查询场站下运营点位,最终结果还会叠加时段/围栏有效性过滤。
        List<XpPoint> stationPoints = this.lambdaQuery()
                .eq(XpPoint::getStationId, station.getId())
                .eq(XpPoint::getPointType, 1)
                .eq(XpPoint::getOperationStatus, 2)
                .list();
        if (CollUtil.isEmpty(stationPoints)) {
            return stationDetailVo;
        }
        Map<Long, XpElectronicFenceArea> areaMap = buildAreaMap(stationPoints.stream()
                .map(XpPoint::getElectronicAreaId)
                .filter(Objects::nonNull)
                .distinct()
                .collect(Collectors.toList()));
        Map<Long, XpElectronicFence> fenceMap = buildFenceMap(areaMap.values().stream()
                .map(XpElectronicFenceArea::getElectronicId)
                .filter(Objects::nonNull)
                .distinct()
                .collect(Collectors.toList()));
        Set<Long> collectStationIds = getCollectStationIds(uid);
        Set<Long> collectPointIds = getCollectPointIds(uid);
        // 默认点位优先置顶,其余点位再按场站内排序和距离排序。
        List<StationPointItemVo> points = stationPoints.stream()
                .filter(point -> isPointAvailable(point, areaMap, fenceMap))
                .map(point -> toStationPointItem(point, station.getDefaultPointId(), dto.getLatitude(), dto.getLongitude(), collectPointIds))
                .sorted(buildStationPointComparator())
                .collect(Collectors.toList());
        if (points.isEmpty()) {
            return new StationDetailVo();
        }
        stationDetailVo.setStationId(station.getId());
        stationDetailVo.setStationName(station.getStationName());
        stationDetailVo.setDefaultPointId(station.getDefaultPointId());
        stationDetailVo.setCenterLon(station.getCenterLon());
        stationDetailVo.setCenterLat(station.getCenterLat());
        if (collectStationIds.contains(station.getId())) {
            stationDetailVo.setIsCollect(1);
        }
        stationDetailVo.setPoints(points);
        return stationDetailVo;
    }
    @Override
    public XpPointsSearchVo queryPointByName(PointByNameQuery query) {
        XpPointsSearchVo searchVo = new XpPointsSearchVo();
        List<XpPointsListVo> list = this.baseMapper.queryPointByName(query);
@@ -432,4 +510,168 @@
        return pageInfo;
    }
    private Map<Long, XpElectronicFenceArea> buildAreaMap(List<Long> areaIds) {
        if (CollUtil.isEmpty(areaIds)) {
            return Collections.emptyMap();
        }
        return xpElectronicFenceAreaService.listByIds(areaIds).stream()
                .collect(Collectors.toMap(XpElectronicFenceArea::getId, area -> area, (a, b) -> a));
    }
    /**
     * 提前拉取电子围栏,避免逐点位重复查库。
     */
    private Map<Long, XpElectronicFence> buildFenceMap(List<Long> fenceIds) {
        if (CollUtil.isEmpty(fenceIds)) {
            return Collections.emptyMap();
        }
        return xpElectronicFenceService.listByIds(fenceIds).stream()
                .collect(Collectors.toMap(XpElectronicFence::getId, fence -> fence, (a, b) -> a));
    }
    /**
     * 场站可展示判断:
     * 1. 状态必须为启用中;
     * 2. 与运营区同步时,依赖运营区和围栏的当前有效性;
     * 3. 自定义时段时,依赖场站自身时段配置。
     */
    private boolean isStationAvailable(XpStation station) {
        if (station == null || !Integer.valueOf(6).equals(station.getStatus())) {
            return false;
        }
        if (Integer.valueOf(1).equals(station.getOperationType())) {
            if (station.getElectronicAreaId() == null) {
                return false;
            }
            XpElectronicFenceArea area = xpElectronicFenceAreaService.getById(station.getElectronicAreaId());
            if (area == null || !Integer.valueOf(1).equals(area.getAreaStatus()) || area.getElectronicId() == null) {
                return false;
            }
            XpElectronicFence fence = xpElectronicFenceService.getById(area.getElectronicId());
            return isFenceAvailable(fence);
        }
        if (Integer.valueOf(2).equals(station.getOperationType())
                && station.getEffectivePeriod() != null
                && StringUtils.isNotBlank(station.getEffectiveTimePeriod())) {
            return getInTime(LocalTime.now(), station.getEffectivePeriod(), station.getEffectiveTimePeriod());
        }
        return false;
    }
    /**
     * 子点位是否可展示与场站卡片中。
     * 规则与现有点位有效性保持一致,避免地图页和其他点位查询出现口径偏差。
     */
    private boolean isPointAvailable(XpPoint point, Map<Long, XpElectronicFenceArea> areaMap, Map<Long, XpElectronicFence> fenceMap) {
        if (point == null || !Integer.valueOf(2).equals(point.getOperationStatus())) {
            return false;
        }
        if (Integer.valueOf(1).equals(point.getOperationType())) {
            if (point.getElectronicAreaId() == null) {
                return false;
            }
            XpElectronicFenceArea area = areaMap.get(point.getElectronicAreaId());
            if (area == null || !Integer.valueOf(1).equals(area.getAreaStatus()) || area.getElectronicId() == null) {
                return false;
            }
            return isFenceAvailable(fenceMap.get(area.getElectronicId()));
        }
        if (Integer.valueOf(2).equals(point.getOperationType())
                && point.getEffectivePeriod() != null
                && StringUtils.isNotBlank(point.getEffectiveTimePeriod())) {
            return getInTime(LocalTime.now(), point.getEffectivePeriod(), point.getEffectiveTimePeriod());
        }
        return false;
    }
    /**
     * 围栏生效判断:
     * 启用中,且当前时间需要落在生效时间与失效时间窗口内。
     */
    private boolean isFenceAvailable(XpElectronicFence fence) {
        if (fence == null || !Integer.valueOf(2).equals(fence.getStatus())) {
            return false;
        }
        LocalDateTime now = LocalDateTime.now();
        if (fence.getEffectTime() != null && now.isBefore(fence.getEffectTime())) {
            return false;
        }
        return !Integer.valueOf(2).equals(fence.getFailureType()) || fence.getFailureTime() == null || !now.isAfter(fence.getFailureTime());
    }
    /**
     * 组装单个场站子点位返回对象,并在有默认点位配置时打标。
     */
    private StationPointItemVo toStationPointItem(XpPoint point, Long defaultPointId, Double latitude, Double longitude, Set<Long> collectPointIds) {
        StationPointItemVo itemVo = new StationPointItemVo();
        itemVo.setPointId(point.getId());
        itemVo.setPointName(point.getPointName());
        itemVo.setPointAddress(point.getPointAddress());
        itemVo.setLatitude(point.getPointLat());
        itemVo.setLongitude(point.getPointLon());
        itemVo.setStationSortNo(point.getStationSortNo());
        if (collectPointIds.contains(point.getId())) {
            itemVo.setIsCollect(1);
        }
        if (defaultPointId != null && defaultPointId.equals(point.getId())) {
            itemVo.setIsDefault(1);
        }
        if (latitude != null && longitude != null && point.getPointLat() != null && point.getPointLon() != null) {
            double distance = FastDistanceCalculator.distanceFast(latitude, longitude, point.getPointLat(), point.getPointLon());
            itemVo.setDistance(BigDecimal.valueOf(distance).setScale(2, RoundingMode.HALF_UP).doubleValue());
        }
        return itemVo;
    }
    /**
     * 排序规则:
     * 默认点位优先,其次按场站内排序,再按与当前定位距离排序。
     */
    private Comparator<StationPointItemVo> buildStationPointComparator() {
        return Comparator
                .comparing((StationPointItemVo item) -> item.getIsDefault() == null ? 2 : item.getIsDefault())
                .thenComparing(item -> item.getStationSortNo() == null ? Integer.MAX_VALUE : item.getStationSortNo())
                .thenComparing(item -> item.getDistance() == null ? Double.MAX_VALUE : item.getDistance());
    }
    /**
     * 查询用户收藏的场站ID。
     * 收藏服务异常时降级为空集合,避免影响场站详情主流程。
     */
    private Set<Long> getCollectStationIds(String uid) {
        if (!StringUtils.hasText(uid)) {
            return Collections.emptySet();
        }
        try {
            R<List<Long>> result = xpAppUserCollectAddrClient.getStationIdsByUserId(Long.valueOf(uid));
            if (result == null || CollUtil.isEmpty(result.getData())) {
                return Collections.emptySet();
            }
            return new HashSet<>(result.getData());
        } catch (Exception e) {
            log.warn("查询用户收藏场站失败 uid={}", uid, e);
            return Collections.emptySet();
        }
    }
    /**
     * 查询用户收藏的点位ID。
     * 子点位收藏态只用于回显,不影响场站筛选和排序主逻辑。
     */
    private Set<Long> getCollectPointIds(String uid) {
        if (!StringUtils.hasText(uid)) {
            return Collections.emptySet();
        }
        try {
            R<List<Long>> result = xpAppUserCollectAddrClient.getPointIdsByUserId(Long.valueOf(uid));
            if (result == null || CollUtil.isEmpty(result.getData())) {
                return Collections.emptySet();
            }
            return new HashSet<>(result.getData());
        } catch (Exception e) {
            log.warn("查询用户收藏点位失败 uid={}", uid, e);
            return Collections.emptySet();
        }
    }
}