package com.xiaopeng.account.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.alibaba.fastjson2.JSON; import com.xiaopeng.account.mapper.XpAppUserCollectAddrMapper; import com.xiaopeng.account.model.dto.CollectAddrDTO; import com.xiaopeng.account.model.query.XpUserCollectAddrListQuery; import com.xiaopeng.account.model.vo.CollectAddrTargetVO; import com.xiaopeng.account.model.vo.XpAppUserCollectAddrVO; import com.xiaopeng.account.service.IXpAppUserCollectAddrService; import com.xiaopeng.api.dto.map.XpPointExportExcelQuery; import com.xiaopeng.api.feignClient.map.XpStationClient; import com.xiaopeng.api.feignClient.operation.XpPointClient; import com.xiaopeng.api.model.account.XpAppUserCollectAddr; import com.xiaopeng.api.model.operation.XpPoint; import com.xiaopeng.api.model.operation.XpStation; import com.xiaopeng.api.vo.order.XpPointsListVo; import com.xiaopeng.common.core.domain.R; import com.xiaopeng.common.core.exception.ServiceException; import com.xiaopeng.common.core.web.page.PageInfo; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; /** *

* 用户收藏地址 服务实现类 *

* * @author ck * @since 2026-04-10 */ @Service public class XpAppUserCollectAddrServiceImpl extends ServiceImpl implements IXpAppUserCollectAddrService { private static final int COLLECT_TYPE_ADD = 1; private static final int COLLECT_TYPE_CANCEL = 2; private static final int ITEM_TYPE_POINT = 1; private static final int ITEM_TYPE_STATION = 2; private static final int COLLECT_LIMIT = 50; @Resource private XpPointClient xpPointClient; @Resource private XpStationClient xpStationClient; /** * 收藏地址。 * 收藏表显式保存 item_type / station_id: * 1. 站点收藏:item_type=1,point_id 保存站点自身。 * 2. 场站收藏:item_type=2,station_id 保存场站id,point_id 保存代表点位id。 * 这样取消收藏、列表回显和后续扩展都不需要再做隐式推断。 */ @Override public Boolean collectAddr(CollectAddrDTO dto, String uid) { Long userId = Long.parseLong(uid); if(dto.getItemType() == 1){//收藏对象类型 1=站点 2=场站,不传时默认按站点处理 if(dto.getLatitude() == null || dto.getLongitude() == null){ throw new ServiceException("经度、纬度不能为空"); } XpPointExportExcelQuery xpPointExportExcelQuery = new XpPointExportExcelQuery(); xpPointExportExcelQuery.setPointIds(Arrays.asList(dto.getPointId())); List data = xpPointClient.getByIds(xpPointExportExcelQuery).getData(); if(null == data || data.size() == 0){ throw new ServiceException("站点不存在"); } } if(dto.getItemType() == 2){ XpStation data = xpStationClient.getXpStationById(dto.getStationId()).getData(); if(null == data){ throw new ServiceException("场站不存在"); } } if(1 == dto.getType()){ XpAppUserCollectAddr collectAddr = new XpAppUserCollectAddr(); collectAddr.setAppUserId(userId); collectAddr.setItemType(dto.getItemType()); collectAddr.setPointId(dto.getPointId()); collectAddr.setStationId(dto.getStationId()); collectAddr.setLongitude(dto.getLongitude()); collectAddr.setLatitude(dto.getLatitude()); return this.save(collectAddr); }else{ if(dto.getItemType() == 1){ return this.remove(new LambdaQueryWrapper().eq(XpAppUserCollectAddr::getAppUserId, userId) .eq(XpAppUserCollectAddr::getPointId, dto.getPointId())); } if(dto.getItemType() == 2){ return this.remove(new LambdaQueryWrapper().eq(XpAppUserCollectAddr::getAppUserId, userId) .eq(XpAppUserCollectAddr::getStationId, dto.getStationId())); } } return false; } /** * 收藏地址列表。 * 列表查询直接联表回显,场站/站点通过 item_type 直接区分。 */ @Override public Page userCollectAddrList(XpUserCollectAddrListQuery query, String uid) { Page page = new Page<>(query.getPageNum(), query.getPageSize()); Page pageData = this.baseMapper.userCollectAddrPageList(page, query, Long.parseLong(uid)); Set collectedPointIds = new HashSet<>(getPointIdsByUserId(Long.parseLong(uid))); pageData.getRecords().forEach(xpAppUserCollectAddrVO -> { if(xpAppUserCollectAddrVO.getItemType() == 1){ XpPointExportExcelQuery xpPointExportExcelQuery = new XpPointExportExcelQuery(); xpPointExportExcelQuery.setPointIds(Arrays.asList(xpAppUserCollectAddrVO.getPointId())); List data = xpPointClient.getByIds(xpPointExportExcelQuery).getData(); if(null != data && data.size() > 0){ fillPointInfo(xpAppUserCollectAddrVO, data.get(0), query); } } if(xpAppUserCollectAddrVO.getItemType() == 2){ fillCollectPointInfo(xpAppUserCollectAddrVO, query); XpStation data = xpStationClient.getXpStationById(xpAppUserCollectAddrVO.getStationId()).getData(); if(null != data){ xpAppUserCollectAddrVO.setPointName(data.getStationName()); } xpAppUserCollectAddrVO.setChildren(buildStationChildren(xpAppUserCollectAddrVO.getStationId(), query, collectedPointIds)); } }); page.setTotal(pageData.getTotal()); page.setRecords(pageData.getRecords()); return page; } private List buildStationChildren(Long stationId, XpUserCollectAddrListQuery query, Set collectedPointIds) { if (stationId == null) { return Collections.emptyList(); } R> result = xpPointClient.getStationChildren(stationId); List children = result == null ? Collections.emptyList() : result.getData(); if (children == null || children.isEmpty()) { return Collections.emptyList(); } return children.stream() .peek(child -> fillChildInfo(child, query, collectedPointIds)) .collect(Collectors.toList()); } private void fillChildInfo(XpPointsListVo child, XpUserCollectAddrListQuery query, Set collectedPointIds) { fillChildDistance(child, query); if (collectedPointIds.contains(child.getId())) { child.setIsCollect(1); } } private void fillChildDistance(XpPointsListVo child, XpUserCollectAddrListQuery query) { if (query.getLatitude() == null || query.getLongitude() == null || child.getLatitude() == null || child.getLongitude() == null) { return; } double distance = distanceFast(query.getLatitude(), query.getLongitude(), child.getLatitude(), child.getLongitude()); child.setDistance(BigDecimal.valueOf(distance).setScale(2, RoundingMode.HALF_UP).doubleValue()); } private void fillCollectPointInfo(XpAppUserCollectAddrVO collectAddrVO, XpUserCollectAddrListQuery query) { XpPointExportExcelQuery xpPointExportExcelQuery = new XpPointExportExcelQuery(); xpPointExportExcelQuery.setPointIds(Arrays.asList(collectAddrVO.getPointId())); List data = xpPointClient.getByIds(xpPointExportExcelQuery).getData(); if (data == null || data.isEmpty()) { return; } fillPointInfo(collectAddrVO, data.get(0), query); } private void fillPointInfo(XpAppUserCollectAddrVO collectAddrVO, XpPoint point, XpUserCollectAddrListQuery query) { collectAddrVO.setPointName(point.getPointName()); collectAddrVO.setPointAddress(point.getPointAddress()); collectAddrVO.setElectronicAreaId(point.getElectronicAreaId()); collectAddrVO.setTime(point.getParkingTimeLimit()); fillEffectiveTime(collectAddrVO, point.getEffectiveTimePeriod()); fillDistance(collectAddrVO, point, query); } private void fillEffectiveTime(XpAppUserCollectAddrVO collectAddrVO, String effectiveTimePeriod) { if (effectiveTimePeriod == null || effectiveTimePeriod.isEmpty()) { return; } List timeRanges; try { timeRanges = JSON.parseArray(effectiveTimePeriod, String.class); } catch (Exception e) { return; } if (timeRanges == null || timeRanges.isEmpty()) { return; } String[] timeRange = timeRanges.get(0).split("-"); if (timeRange.length != 2) { return; } collectAddrVO.setStartTime(timeRange[0].trim()); collectAddrVO.setEndTime(timeRange[1].trim()); } private void fillDistance(XpAppUserCollectAddrVO collectAddrVO, XpPoint point, XpUserCollectAddrListQuery query) { if (query.getLatitude() == null || query.getLongitude() == null || point.getPointLat() == null || point.getPointLon() == null) { return; } double distance = distanceFast(query.getLatitude(), query.getLongitude(), point.getPointLat(), point.getPointLon()); collectAddrVO.setDistance(BigDecimal.valueOf(distance).setScale(2, RoundingMode.HALF_UP).doubleValue()); } private double distanceFast(double lat1, double lon1, double lat2, double lon2) { double latDistance = Math.toRadians(lat2 - lat1); double lonDistance = Math.toRadians(lon2 - lon1); double value = Math.sin(latDistance / 2) * Math.sin(latDistance / 2) + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) * Math.sin(lonDistance / 2) * Math.sin(lonDistance / 2); return 6371000 * 2 * Math.atan2(Math.sqrt(value), Math.sqrt(1 - value)); } @Override public List getPointIdsByUserId(Long uid) { return this.list(buildUserCollectQuery(uid) .eq(XpAppUserCollectAddr::getItemType, ITEM_TYPE_POINT)) .stream() .map(XpAppUserCollectAddr::getPointId) .filter(Objects::nonNull) .distinct() .collect(Collectors.toList()); } @Override public List getStationIdsByUserId(Long uid) { return this.list(buildUserCollectQuery(uid) .eq(XpAppUserCollectAddr::getItemType, ITEM_TYPE_STATION)) .stream() .map(XpAppUserCollectAddr::getStationId) .filter(Objects::nonNull) .distinct() .collect(Collectors.toList()); } private LambdaQueryWrapper buildUserCollectQuery(Long uid) { return new LambdaQueryWrapper() .eq(XpAppUserCollectAddr::getAppUserId, uid) .eq(XpAppUserCollectAddr::getDeleted, 0); } }