kong
2026-04-12 7f3081da3c0597cfbfc8f0fc4e342c4ceb901f6d
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/service/impl/XpPointServiceImpl.java
@@ -5,10 +5,13 @@
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
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.account.XpAppUserCollectAddr;
import com.xiaopeng.api.model.operation.*;
import com.xiaopeng.api.query.operation.PointListQuery;
import com.xiaopeng.api.query.operation.PointPageListListQuery;
@@ -17,6 +20,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.web.page.PageInfo;
import com.xiaopeng.common.redis.service.RedisService;
import com.xiaopeng.evi.oss.service.AliOssService;
@@ -73,11 +77,17 @@
    private IXpElectronicFenceService xpElectronicFenceService;
    @Resource
    private XpPointMapper baseMapper;
    @Resource
    private XpAppUserCollectAddrClient xpAppUserCollectAddrClient;
    @Resource
    private AliOssService aliOssService;
    @Override
    public NearestVo nearest(NearestDto dto) {
    public NearestVo nearest(NearestDto dto,String uid) {
        // 走缓存
        Object orderDistanceObj = redisService.getCacheObject(CacheConstants.NEAR_POINT_DISTANCE);
        Integer orderDistance;
@@ -117,6 +127,12 @@
        if (CollUtil.isNotEmpty(fenceIds)) {
            xpElectronicFences = xpElectronicFenceService.listByIds(fenceIds);
        }
        R<List<Long>> pointIdsByUserId = null;
        // 获取用户收藏的地址
        if(uid!=null){
            pointIdsByUserId = xpAppUserCollectAddrClient.getPointIdsByUserId(Long.valueOf(uid));
        }
        //判断当前点位是否在运营时间范围内
        for (XpPoint xpPoint : list) {
@@ -164,6 +180,12 @@
                nearestNextVo.setTime(xpPoint.getParkingTimeLimit());
                nearestNextVo.setElectronicAreaId(xpPoint.getElectronicAreaId());
                // 判断是否收藏
                if(pointIdsByUserId!=null && !pointIdsByUserId.getData().isEmpty()){
                    if(pointIdsByUserId.getData().contains(xpPoint.getId())){
                        nearestNextVo.setIsCollect(1);
                    }
                }
                nearestNextVos.add(nearestNextVo);
            }
@@ -297,9 +319,11 @@
            xpElectronicFences = xpElectronicFenceService.listByIds(fenceIds);
        }
        // 获取用户收藏的地址
        R<List<Long>> pointIdsByUserId = xpAppUserCollectAddrClient.getPointIdsByUserId(historyStartPointsDto.getUserId());
        for (XpPoint xpPoint : xpPoints) {
            if(xpPoint.getOperationStatus()!=2){
                continue;
            }
@@ -348,8 +372,14 @@
            xpHistoryStartPointsVo.setDistance(distanced);
            xpHistoryStartPointsVo.setTime(xpPoint.getParkingTimeLimit());
            xpHistoryStartPointsVo.setElectronicAreaId(xpPoint.getElectronicAreaId());
            resultList.add(xpHistoryStartPointsVo);
            // 判断是否收藏
            if(pointIdsByUserId!=null && !pointIdsByUserId.getData().isEmpty()){
                if(pointIdsByUserId.getData().contains(xpPoint.getId())){
                    xpHistoryStartPointsVo.setIsCollect(1);
                }
            }
            resultList.add(xpHistoryStartPointsVo);
        }
        return resultList;
@@ -371,6 +401,9 @@
        }
        // 获取用户收藏的地址
        R<List<Long>> pointIdsByUserId = xpAppUserCollectAddrClient.getPointIdsByUserId(historyEndPointsDto.getUserId());
        for (XpPoint xpPoint : xpPoints) {
@@ -421,6 +454,13 @@
            xpHistoryEndPointsVo.setDistance(distanced);
            xpHistoryEndPointsVo.setTime(xpPoint.getParkingTimeLimit());
            xpHistoryEndPointsVo.setElectronicAreaId(xpPoint.getElectronicAreaId());
            // 判断是否收藏
            if(pointIdsByUserId!=null && !pointIdsByUserId.getData().isEmpty()){
                if(pointIdsByUserId.getData().contains(xpPoint.getId())){
                    xpHistoryEndPointsVo.setIsCollect(1);
                }
            }
            resultList.add(xpHistoryEndPointsVo);
        }
@@ -428,70 +468,244 @@
    }
    @Override
    public XpPointsSearchVo queryPointByName(PointByNameQuery query) {
    public XpPointsSearchVo queryPointByName(PointByNameQuery query,String uid) {
        XpPointsSearchVo searchVo = new XpPointsSearchVo();
        List<XpPointsListVo> list = this.baseMapper.queryPointByName(query);
        if (list.isEmpty()) {
        List<XpPointsListVo> stationList = this.baseMapper.queryStationByName(query);
        List<Long> stationIds = stationList.stream()
                .map(this::getSearchResultStationId)
                .filter(Objects::nonNull)
                .distinct()
                .collect(Collectors.toList());
        // 先把命中的场站id传给点位查询,避免同一场站下的点位和场站父胶囊重复返回。
        List<XpPointsListVo> pointList = this.baseMapper.queryPointByName(query, stationIds);
        List<XpPointsListVo> stationChildren = CollUtil.isEmpty(stationIds) ? Collections.emptyList() : this.baseMapper.queryStationChildren(stationIds);
        if (CollUtil.isEmpty(stationList) && CollUtil.isEmpty(pointList)) {
            return searchVo;
        }
        LocalTime now = LocalTime.now();
        List<Long> areaIds = list.stream().map(XpPointsListVo::getElectronicAreaId).collect(Collectors.toList());
        List<XpElectronicFenceArea> xpElectronicFenceAreas = xpElectronicFenceAreaService.listByIds(areaIds);
        List<Long> fenceIds = xpElectronicFenceAreas.stream().map(XpElectronicFenceArea::getElectronicId).collect(Collectors.toList());
        // 搜索页需要拆分两套收藏态:
        // 1. 站点胶囊按 pointId 判定。
        // 2. 场站胶囊按 stationId 判定。
        Set<Long> collectedPointIds = getCollectedPointIds(uid);
        Set<Long> collectedStationIds = getCollectedStationIds(uid);
        List<XpPointsListVo> allRows = new ArrayList<>(stationList.size() + pointList.size() + stationChildren.size());
        allRows.addAll(stationList);
        allRows.addAll(pointList);
        allRows.addAll(stationChildren);
        List<XpElectronicFence> xpElectronicFences = new ArrayList<>();
        if (CollUtil.isNotEmpty(fenceIds)) {
            xpElectronicFences = xpElectronicFenceService.listByIds(fenceIds);
        }
        for (XpPointsListVo xpPointsListVo : list) {
            Integer operationType = xpPointsListVo.getOperationType();
            if(1 == operationType){
                //与运营区同步
                XpElectronicFenceArea xpElectronicFenceArea = xpElectronicFenceAreas.stream().filter(s -> s.getId().equals(xpPointsListVo.getElectronicAreaId())).findFirst().orElse(null);
                if(null == xpElectronicFenceArea){
                    // 不在运营段
                    xpPointsListVo.setState(0);
                }
                XpElectronicFence xpElectronicFence = xpElectronicFences.stream().filter(s -> s.getId().equals(xpElectronicFenceArea.getElectronicId())).findFirst().orElse(null);
                if(null == xpElectronicFence){
                    // 不在运营段
                    xpPointsListVo.setState(0);
                }
                //失效类型 1=永不失效 2=定时失效
                Integer failureType = xpElectronicFence.getFailureType();
                //永不失效,但是还未到开始时间
                if(1 == failureType && LocalDateTime.now().isBefore(xpElectronicFence.getEffectTime())){
                    xpPointsListVo.setState(0);
                }else if(2 == failureType && (LocalDateTime.now().isBefore(xpElectronicFence.getEffectTime()) || LocalDateTime.now().isAfter(xpElectronicFence.getFailureTime()))){
                    //定时失效,但是当前时间不在时间范围内
                    xpPointsListVo.setState(0);
                }
            }else{
                //自定义时间段
                // 判断是否在时间段内
                boolean intime = getInTime(now, xpPointsListVo.getEffectivePeriod(), xpPointsListVo.getEffectiveTimePeriod());
                if (!intime) {
                    // 不在运营段
                    xpPointsListVo.setState(0);
                }
            }
        }
        // 组装数据 并排序 剔除不在运营段的 数据
        List<XpPointsListVo> resultList = list.stream()
                .peek(vo -> vo.setDistance(BigDecimal.valueOf(FastDistanceCalculator.distanceFast(query.getLat(), query.getLon(), vo.getLatitude(), vo.getLongitude())).setScale(2, RoundingMode.HALF_UP).doubleValue()))
                .filter(vo -> vo.getState() == 1).collect(Collectors.toList());
        Map<Long, XpElectronicFenceArea> electronicFenceAreaMap = buildElectronicFenceAreaMap(allRows);
        Map<Long, XpElectronicFence> electronicFenceMap = buildElectronicFenceMap(electronicFenceAreaMap);
        markSearchRows(allRows, collectedPointIds, collectedStationIds, electronicFenceAreaMap, electronicFenceMap);
        // 可用点位
        List<XpPointsListVo> availablePoints = resultList.stream().filter(e -> query.getElectronicAreaId() != null && e.getElectronicAreaId().equals(query.getElectronicAreaId())).sorted(Comparator.comparing(XpPointsListVo::getDistance)).collect(Collectors.toList());//
        List<XpPointsListVo> resultList = new ArrayList<>();
        List<XpPointsListVo> stationResults = buildStationSearchResults(stationList, stationChildren, query);
        Set<Long> displayedStationIds = stationResults.stream()
                .map(this::getSearchResultStationId)
                .filter(Objects::nonNull)
                .collect(Collectors.toSet());
        resultList.addAll(stationResults);
        resultList.addAll(pointList.stream()
                .filter(vo -> vo.getState() == 1)
                .filter(vo -> vo.getStationId() == null || !displayedStationIds.contains(vo.getStationId()))
                .peek(vo -> fillDistance(vo, query))
                .collect(Collectors.toList()));
        // 不可用点位
        List<XpPointsListVo> unavailablePoints = resultList.stream().filter(e -> query.getElectronicAreaId() == null || !e.getElectronicAreaId().equals(query.getElectronicAreaId())).sorted(Comparator.comparing(XpPointsListVo::getDistance)).collect(Collectors.toList());
        List<XpPointsListVo> availablePoints = resultList.stream()
                .filter(e -> query.getElectronicAreaId() != null && e.getElectronicAreaId().equals(query.getElectronicAreaId()))
                .sorted(Comparator.comparing(this::distanceOrMax))
                .collect(Collectors.toList());
        List<XpPointsListVo> unavailablePoints = resultList.stream()
                .filter(e -> query.getElectronicAreaId() == null || !e.getElectronicAreaId().equals(query.getElectronicAreaId()))
                .sorted(Comparator.comparing(this::distanceOrMax))
                .collect(Collectors.toList());
        searchVo.setAvailablePoints(availablePoints);
        searchVo.setUnavailablePoints(unavailablePoints);
        return searchVo;
    }
    private List<XpPointsListVo> buildStationSearchResults(List<XpPointsListVo> stationList,
                                                           List<XpPointsListVo> stationChildren,
                                                           PointByNameQuery query) {
        if (CollUtil.isEmpty(stationList)) {
            return Collections.emptyList();
        }
        Map<Long, List<XpPointsListVo>> childrenMap = stationChildren.stream()
                .filter(vo -> vo.getState() == 1)
                .peek(vo -> fillDistance(vo, query))
                .collect(Collectors.groupingBy(this::getSearchResultStationId, LinkedHashMap::new, Collectors.toList()));
        List<XpPointsListVo> result = new ArrayList<>();
        for (XpPointsListVo station : stationList) {
            if (station.getState() != 1) {
                continue;
            }
            Long stationId = getSearchResultStationId(station);
            List<XpPointsListVo> children = childrenMap.getOrDefault(stationId, Collections.emptyList());
            if (CollUtil.isEmpty(children)) {
                continue;
            }
            XpPointsListVo firstChild = children.get(0);
            if (station.getCollectPointId() == null) {
                station.setCollectPointId(firstChild.getId());
            }
            if (station.getLongitude() == null || station.getLatitude() == null) {
                station.setLongitude(firstChild.getLongitude());
                station.setLatitude(firstChild.getLatitude());
            }
            if (!StringUtils.hasLength(station.getAddress())) {
                station.setAddress(firstChild.getAddress());
            }
            fillDistance(station, query);
            station.setChildren(children);
            result.add(station);
        }
        return result;
    }
    /**
     * 仅查询“站点收藏”。
     * nearest / 历史上下车点等老接口仍然只对 pointId 打星,所以这里不能混入场站收藏。
     */
    private Set<Long> getCollectedPointIds(String uid) {
        if (uid == null) {
            return Collections.emptySet();
        }
        R<List<Long>> pointIdsByUserId = xpAppUserCollectAddrClient.getPointIdsByUserId(Long.valueOf(uid));
        if (pointIdsByUserId == null || CollUtil.isEmpty(pointIdsByUserId.getData())) {
            return Collections.emptySet();
        }
        return new HashSet<>(pointIdsByUserId.getData());
    }
    /**
     * 场站收藏需要单独查询。
     * 搜索结果里的场站父级行应按 stationId 判断收藏态,不能继续复用 pointId 集合。
     */
    private Set<Long> getCollectedStationIds(String uid) {
        if (uid == null) {
            return Collections.emptySet();
        }
        R<List<Long>> stationIdsByUserId = xpAppUserCollectAddrClient.getStationIdsByUserId(Long.valueOf(uid));
        if (stationIdsByUserId == null || CollUtil.isEmpty(stationIdsByUserId.getData())) {
            return Collections.emptySet();
        }
        return new HashSet<>(stationIdsByUserId.getData());
    }
    private Map<Long, XpElectronicFenceArea> buildElectronicFenceAreaMap(List<XpPointsListVo> rows) {
        List<Long> areaIds = rows.stream()
                .map(XpPointsListVo::getElectronicAreaId)
                .filter(Objects::nonNull)
                .distinct()
                .collect(Collectors.toList());
        if (CollUtil.isEmpty(areaIds)) {
            return Collections.emptyMap();
        }
        return xpElectronicFenceAreaService.listByIds(areaIds).stream()
                .collect(Collectors.toMap(XpElectronicFenceArea::getId, area -> area, (left, right) -> left));
    }
    private Map<Long, XpElectronicFence> buildElectronicFenceMap(Map<Long, XpElectronicFenceArea> electronicFenceAreaMap) {
        if (electronicFenceAreaMap.isEmpty()) {
            return Collections.emptyMap();
        }
        List<Long> fenceIds = electronicFenceAreaMap.values().stream()
                .map(XpElectronicFenceArea::getElectronicId)
                .filter(Objects::nonNull)
                .distinct()
                .collect(Collectors.toList());
        if (CollUtil.isEmpty(fenceIds)) {
            return Collections.emptyMap();
        }
        return xpElectronicFenceService.listByIds(fenceIds).stream()
                .collect(Collectors.toMap(XpElectronicFence::getId, fence -> fence, (left, right) -> left));
    }
    private void markSearchRows(List<XpPointsListVo> rows,
                                Set<Long> collectedPointIds,
                                Set<Long> collectedStationIds,
                                Map<Long, XpElectronicFenceArea> electronicFenceAreaMap,
                                Map<Long, XpElectronicFence> electronicFenceMap) {
        LocalTime now = LocalTime.now();
        for (XpPointsListVo row : rows) {
            if (isCollectedSearchRow(row, collectedPointIds, collectedStationIds)) {
                row.setIsCollect(1);
            }
            if (!isSearchRowAvailable(row, electronicFenceAreaMap, electronicFenceMap, now)) {
                row.setState(0);
            }
        }
    }
    /**
     * 搜索结果收藏态判定规则:
     * 1. 站点行按 pointId / collectPointId 判定。
     * 2. 场站行按 stationId 判定。
     */
    private boolean isCollectedSearchRow(XpPointsListVo row,
                                         Set<Long> collectedPointIds,
                                         Set<Long> collectedStationIds) {
        if (row.getItemType() != null && row.getItemType() == 2) {
            Long stationId = getSearchResultStationId(row);
            return stationId != null && collectedStationIds.contains(stationId);
        }
        Long collectPointId = row.getCollectPointId() != null ? row.getCollectPointId() : row.getId();
        return collectPointId != null && collectedPointIds.contains(collectPointId);
    }
    private boolean isSearchRowAvailable(XpPointsListVo row,
                                         Map<Long, XpElectronicFenceArea> electronicFenceAreaMap,
                                         Map<Long, XpElectronicFence> electronicFenceMap,
                                         LocalTime now) {
        Integer operationType = row.getOperationType();
        if (operationType == null) {
            return false;
        }
        if (operationType == 1) {
            Long electronicAreaId = row.getElectronicAreaId();
            if (electronicAreaId == null) {
                return false;
            }
            XpElectronicFenceArea electronicFenceArea = electronicFenceAreaMap.get(electronicAreaId);
            if (electronicFenceArea == null || electronicFenceArea.getElectronicId() == null) {
                return false;
            }
            XpElectronicFence electronicFence = electronicFenceMap.get(electronicFenceArea.getElectronicId());
            if (electronicFence == null) {
                return false;
            }
            Integer failureType = electronicFence.getFailureType();
            if (failureType == null) {
                return true;
            }
            if (failureType == 1 && LocalDateTime.now().isBefore(electronicFence.getEffectTime())) {
                return false;
            }
            return failureType != 2 || (!LocalDateTime.now().isBefore(electronicFence.getEffectTime())
                    && !LocalDateTime.now().isAfter(electronicFence.getFailureTime()));
        }
        if (row.getEffectivePeriod() == null || !StringUtils.hasLength(row.getEffectiveTimePeriod())) {
            return true;
        }
        return getInTime(now, row.getEffectivePeriod(), row.getEffectiveTimePeriod());
    }
    private void fillDistance(XpPointsListVo row, PointByNameQuery query) {
        if (row.getLatitude() == null || row.getLongitude() == null) {
            return;
        }
        row.setDistance(BigDecimal.valueOf(FastDistanceCalculator.distanceFast(query.getLat(), query.getLon(), row.getLatitude(), row.getLongitude()))
                .setScale(2, RoundingMode.HALF_UP)
                .doubleValue());
    }
    private Long getSearchResultStationId(XpPointsListVo row) {
        return row.getStationId() != null ? row.getStationId() : row.getId();
    }
    private Double distanceOrMax(XpPointsListVo row) {
        return row.getDistance() == null ? Double.MAX_VALUE : row.getDistance();
    }
    @Override
    public OrderDistanceVo getOrderDistance() {
        OrderDistanceVo orderDistanceVo = new OrderDistanceVo();