From a06900135aeca18447f99167bcf7749f5a3cf9ab Mon Sep 17 00:00:00 2001
From: “mitao” <“2763622819@qq.com”>
Date: 星期日, 12 四月 2026 18:02:02 +0800
Subject: [PATCH] feat:APP地图选点-场站
---
xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/service/serviceimpl/XpPointServiceImpl.java | 242 ++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 242 insertions(+), 0 deletions(-)
diff --git a/xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/service/serviceimpl/XpPointServiceImpl.java b/xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/service/serviceimpl/XpPointServiceImpl.java
index 933ec77..3abc61c 100644
--- a/xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/service/serviceimpl/XpPointServiceImpl.java
+++ b/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();
+ }
+ }
+
}
--
Gitblit v1.7.1