package com.xiaopeng.map.service.serviceimpl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xiaopeng.api.dto.operation.PointAddDto;
import com.xiaopeng.api.dto.operation.PointMapQuery;
import com.xiaopeng.api.dto.operation.PointUpdateDto;
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.*;
import com.xiaopeng.api.query.operation.FenceAndStationVo;
import com.xiaopeng.api.query.operation.PointListQuery;
import com.xiaopeng.api.query.operation.PointPageListListQuery;
import com.xiaopeng.api.query.operation.XpStationQuery;
import com.xiaopeng.api.vo.account.LoginUser;
import com.xiaopeng.api.vo.order.XpHistoryEndPointsVo;
import com.xiaopeng.api.vo.order.XpHistoryStartPointsVo;
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;
import com.xiaopeng.common.redis.service.RedisService;
import com.xiaopeng.common.security.service.TokenService;
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.XpElectronicFenceMapper;
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.XpElectronicFenceAreaService;
import com.xiaopeng.map.service.XpElectronicFenceService;
import com.xiaopeng.map.service.XpPointService;
import com.xiaopeng.map.service.XpStationService;
import com.xiaopeng.map.utils.holiday.HolidayUtil;
import com.xiaopeng.map.utils.map.map.FastDistanceCalculator;
import com.xiaopeng.map.vo.*;
import lombok.extern.slf4j.Slf4j;
import net.sourceforge.pinyin4j.PinyinHelper;
import org.apache.shardingsphere.transaction.annotation.ShardingSphereTransactionType;
import org.apache.shardingsphere.transaction.core.TransactionType;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
*
* 点位管理 服务实现类
*
*
* @author liu
* @author WuGuanFengYue
* @since 2026-02-03
*/
@Service
@Slf4j
public class XpPointServiceImpl extends ServiceImpl implements XpPointService {
private static final String POINT_BIZ_TYPE = "PUD";
private static final int MAX_POINTS_PER_FENCE = 15;
private static final int SERIAL_WIDTH = 6;
private static final int LOCK_WAIT_SECONDS = 5;
private static final int LOCK_LEASE_SECONDS = 30;
@Resource
private XpSystemSettingMapper xpSystemSettingMapper;
@Resource
private RedisService redisService;
@Resource
private HolidayUtil holidayUtil;
@Resource
private XpElectronicFenceAreaService xpElectronicFenceAreaService;
@Resource
private XpElectronicFenceMapper xpElectronicFenceMapper;
@Resource
@Lazy
private XpStationService xpStationService;
@Resource
private XpAppUserCollectAddrClient xpAppUserCollectAddrClient;
@Resource
private XpRegionMapper xpRegionMapper;
@Autowired
private RedissonClient redissonClient;
@Autowired
private AliOssService aliOssService;
@Lazy
@Autowired
private BootCommonService bootCommonService;
@Lazy
@Resource
private XpElectronicFenceService xpElectronicFenceService;
@Override
public void validateAdd(PointAddDto dto) {
XpElectronicFenceArea electronicFenceArea = xpElectronicFenceAreaService.getById(dto.getElectronicAreaId());
if(electronicFenceArea == null){
throw new ServiceException("所属运营区id错误");
}
XpElectronicFence electronicFence = xpElectronicFenceService.getById(electronicFenceArea.getElectronicId());
//场站的自定义时间必须在 围栏运营时间内
if(dto.getOperationType() != null && dto.getOperationType() == 2){
// ["06:00-12:00", "13:00-21:00"]
if(StrUtil.isBlank(dto.getEffectiveTimePeriod())){
throw new ServiceException("自定义时间段不能为空");
}
boolean stationTimeFenceOperationStrategy = bootCommonService.getStationTime_fenceOperationStrategy(dto.getEffectivePeriod(), dto.getEffectiveTimePeriod(), electronicFence.getOperationStrategy());
if(!stationTimeFenceOperationStrategy){
throw new ServiceException("自定义时间必须在围栏运营时间内");
}
}
}
@Override
public void validateUpdate(PointUpdateDto dto) {
XpElectronicFenceArea electronicFenceArea = xpElectronicFenceAreaService.getById(dto.getElectronicAreaId());
if(electronicFenceArea == null){
throw new ServiceException("所属运营区id错误");
}
XpElectronicFence electronicFence = xpElectronicFenceService.getById(electronicFenceArea.getElectronicId());
//场站的自定义时间必须在 围栏运营时间内
if(dto.getOperationType() != null && dto.getOperationType() == 2){
// ["06:00-12:00", "13:00-21:00"]
if(StrUtil.isBlank(dto.getEffectiveTimePeriod())){
throw new ServiceException("自定义时间段不能为空");
}
boolean stationTimeFenceOperationStrategy = bootCommonService.getStationTime_fenceOperationStrategy(dto.getEffectivePeriod(), dto.getEffectiveTimePeriod(), electronicFence.getOperationStrategy());
if(!stationTimeFenceOperationStrategy){
throw new ServiceException("自定义时间必须在围栏运营时间内");
}
}
}
@Override
@Transactional
@ShardingSphereTransactionType(TransactionType.BASE)
public XpPoint addPointWithGeneratedNumber(PointAddDto pointAddDto) {
if (pointAddDto == null) {
throw new ServiceException("参数不能为空", I18nKeyConstant.SYSTEM_EXCEPTION);
}
if (pointAddDto.getElectronicAreaId() == null) {
throw new ServiceException("运营区域ID不能为空", I18nKeyConstant.POINT_ELECTRONIC_AREA_ID_EMPTY);
}
validatePointNameUnique(pointAddDto.getPointName(), null);
String pointNumber = getPointNumber(pointAddDto.getElectronicAreaId());
XpPoint xpPoint = new XpPoint();
BeanUtils.copyProperties(pointAddDto, xpPoint);
xpPoint.setFpElectronicAreaId(pointAddDto.getElectronicAreaId());
xpPoint.setPointNumber(pointNumber);
xpPoint.setStationSortNo(0);
this.save(xpPoint);
//场站点位排序 后添加的点位 排第一个。
this.setStationSortNo(xpPoint.getStationId(),0);
return xpPoint;
}
/**
* 场站 点位排序
* @param stationId
* @param stationSortNo
*/
@Transactional
@Override
public void setStationSortNo(Long stationId, Integer stationSortNo){
if(null != stationId && stationSortNo != null && stationSortNo ==0){
// 查询该场站下的所有点位,按 stationSortNo 排序
List points = this.lambdaQuery()
.eq(XpPoint::getStationId, stationId)
.orderByAsc(XpPoint::getStationSortNo)
.list();
if (CollUtil.isNotEmpty(points)) {
int newSortNo = 1;
for (XpPoint point : points) {
point.setStationSortNo(newSortNo++);
}
this.updateBatchById(points);
}
}
}
@Override
public String getPointNumber(Long electronicAreaId) {
String cityPrefix = buildCityPrefix(electronicAreaId);
String lockKey = buildPointNumberLockKey(cityPrefix, POINT_BIZ_TYPE);
RLock lock = redissonClient.getLock(lockKey);
boolean locked = false;
String pointNumber;
try {
locked = lock.tryLock(LOCK_WAIT_SECONDS, LOCK_LEASE_SECONDS, TimeUnit.SECONDS);
if (!locked) {
throw new ServiceException("点位编号生成繁忙,请稍后重试", I18nKeyConstant.POINT_NUMBER_LOCK_FAIL);
}
validateFencePointCountLimit(electronicAreaId);
String maxPointNumber = this.baseMapper.selectMaxPointNumber(cityPrefix, POINT_BIZ_TYPE);
int nextSerial = 1;
if (StringUtils.hasText(maxPointNumber)) {
nextSerial = parseSerialOrThrow(maxPointNumber) + 1;
}
pointNumber = cityPrefix + "-" + POINT_BIZ_TYPE + "-" + String.format("%0" + SERIAL_WIDTH + "d", nextSerial);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new ServiceException("点位编号生成繁忙,请稍后重试", I18nKeyConstant.POINT_NUMBER_LOCK_FAIL);
} finally {
try {
if (locked && lock.isHeldByCurrentThread()) {
lock.unlock();
}
} catch (Exception e) {
log.warn("释放点位编号生成锁失败 lockKey={}", lockKey, e);
}
}
return pointNumber;
}
@Override
public void validatePointNameUnique(String pointName, Long excludeId) {
if (!StringUtils.hasText(pointName)) {
return;
}
long count = this.lambdaQuery()
.eq(XpPoint::getPointName, pointName)
.ne(excludeId != null, XpPoint::getId, excludeId)
.count();
if (count > 0) {
throw new ServiceException("点位名称已存在", I18nKeyConstant.POINT_NAME_EXISTS);
}
}
@Override
public NearestVo nearest(NearestDto dto) {
// 走缓存
Object orderDistanceObj = redisService.getCacheObject(CacheConstants.NEAR_POINT_DISTANCE);
Integer orderDistance;
if (orderDistanceObj != null) {
orderDistance = Integer.valueOf(orderDistanceObj.toString());
} else {
XpSystemSetting xpSystemSetting = xpSystemSettingMapper.selectOne(null);
orderDistance = xpSystemSetting.getNearPointDistance();
}
// 获取所有的站点信息
PointPageListListQuery query = new PointPageListListQuery();
query.setOperationStatus(2);
query.setPointType(1);
query.setElectronicAreaIdNotNull(true);
query.setLongitude(dto.getLongitude());
query.setLatitude(dto.getLatitude());
query.setOrderDistance(orderDistance);
List list = baseMapper.pageList(query);
// 添加距离计算字段
// List list = list(new LambdaQueryWrapper().eq(XpPoint::getOperationStatus, 2).eq(XpPoint::getPointType,1).isNotNull(XpPoint::getElectronicAreaId)
// .apply("ST_Distance_Sphere(point({0}, {1}), point(point_lon, point_lat)) <= {2}",
// dto.getLongitude(), dto.getLatitude(), orderDistance));
NearestVo nearestVo = new NearestVo();
List nearestNextVos = new ArrayList<>();
LocalTime now = LocalTime.now();
for (XpPoint xpPoint : list) {
// 判断是否在时间段内
boolean intime = bootCommonService.getPointOperation(now, xpPoint);
if (!intime) {
continue;
}
// 计算距离 小于的站点
double v = FastDistanceCalculator.distanceFast(dto.getLatitude(), dto.getLongitude(), xpPoint.getPointLat(), xpPoint.getPointLon());
if (v < orderDistance) {
NearestNextVo nearestNextVo = new NearestNextVo();
nearestNextVo.setId(xpPoint.getId());
nearestNextVo.setName(xpPoint.getPointName());
nearestNextVo.setLatitude(xpPoint.getPointLat());
nearestNextVo.setLongitude(xpPoint.getPointLon());
nearestNextVo.setDistance(v);
nearestNextVo.setAddress(xpPoint.getPointAddress());
nearestNextVo.setTime(xpPoint.getParkingTimeLimit());
nearestNextVo.setElectronicAreaId(xpPoint.getElectronicAreaId());
nearestNextVos.add(nearestNextVo);
}
}
if (!nearestNextVos.isEmpty()) {
nearestNextVos = nearestNextVos.stream().sorted(Comparator.comparing(NearestNextVo::getDistance)).collect(Collectors.toList());
}
nearestVo.setPoints(nearestNextVos);
nearestVo.setTotal(nearestNextVos.size());
return nearestVo;
}
private String buildCityPrefix(Long electronicAreaId) {
//所有围栏范围之外,测试点位
if(0 == electronicAreaId){
return "TEST";
}
XpElectronicFenceArea area = xpElectronicFenceAreaService.getById(electronicAreaId);
if (area == null) {
throw new ServiceException("运营区域不存在", I18nKeyConstant.POINT_ELECTRONIC_AREA_NOT_EXIST);
}
if (!StringUtils.hasText(area.getCityCode())) {
throw new ServiceException("城市信息不存在", I18nKeyConstant.POINT_CITY_NOT_EXIST);
}
XpRegion region = xpRegionMapper.selectOne(new LambdaQueryWrapper()
.eq(XpRegion::getCode, area.getCityCode()));
if (region == null || !StringUtils.hasText(region.getName())) {
throw new ServiceException("城市信息不存在", I18nKeyConstant.POINT_CITY_NOT_EXIST);
}
String name = region.getName();
String prefix = name.length() <= 2 ? name : name.substring(0, 2);
return getPinyinInitials(prefix).toUpperCase();
}
private void validateFencePointCountLimit(Long electronicAreaId) {
if (electronicAreaId == null || electronicAreaId == 0L) {
return;
}
XpElectronicFenceArea currentArea = xpElectronicFenceAreaService.getById(electronicAreaId);
if (currentArea == null) {
throw new ServiceException("运营区域不存在", I18nKeyConstant.POINT_ELECTRONIC_AREA_NOT_EXIST);
}
Long fenceId = currentArea.getElectronicId();
if (fenceId == null) {
throw new ServiceException("运营区域不存在", I18nKeyConstant.POINT_ELECTRONIC_AREA_NOT_EXIST);
}
List areaIds = xpElectronicFenceAreaService.lambdaQuery()
.eq(XpElectronicFenceArea::getElectronicId, fenceId)
.list()
.stream()
.map(XpElectronicFenceArea::getId)
.filter(Objects::nonNull)
.collect(Collectors.toList());
if (areaIds.isEmpty()) {
return;
}
long pointCount = this.lambdaQuery()
.in(XpPoint::getElectronicAreaId, areaIds)
.count();
if (pointCount >= MAX_POINTS_PER_FENCE) {
throw new ServiceException("当前围栏最多只能添加15个点位", I18nKeyConstant.POINT_FENCE_POINT_LIMIT_EXCEEDED);
}
}
private static String buildPointNumberLockKey(String cityPrefix, String bizType) {
return "xp:point:number:" + cityPrefix + ":" + bizType;
}
private static String getPinyinInitials(String chinese) {
StringBuilder pinyinInitials = new StringBuilder();
for (char c : chinese.toCharArray()) {
String[] pinyinArray = PinyinHelper.toHanyuPinyinStringArray(c);
if (pinyinArray != null && pinyinArray.length > 0) {
pinyinInitials.append(pinyinArray[0].charAt(0));
}
}
return pinyinInitials.toString();
}
private static int parseSerialOrThrow(String pointNumber) {
String[] parts = pointNumber.split("-");
if (parts.length < 3) {
throw new ServiceException("点位编号生成失败(历史编号格式异常)", I18nKeyConstant.POINT_NUMBER_PARSE_FAIL);
}
String serialPart = parts[parts.length - 1];
if (serialPart.length() != SERIAL_WIDTH) {
throw new ServiceException("点位编号生成失败(历史编号格式异常)", I18nKeyConstant.POINT_NUMBER_PARSE_FAIL);
}
try {
return Integer.parseInt(serialPart);
} catch (NumberFormatException e) {
throw new ServiceException("点位编号生成失败(历史编号格式异常)", I18nKeyConstant.POINT_NUMBER_PARSE_FAIL);
}
}
@Override
public List pointMap(PointMapQuery query) {
List list = this.baseMapper.pointMap(query);
return list;
}
@Override
public List findListFenceAndStation(String cityCode){
return baseMapper.findListFenceAndStation(cityCode);
}
@Override
public List findList(PointListQuery query) {
return this.baseMapper.listAll(query);
}
@Override
public List listAll(PointListQuery query) {
List list = this.baseMapper.listAll(query);
for (XpPoint xpPoint : list) {
String pointPictures = xpPoint.getPointPictures();
log.info("pointPictures:{}", pointPictures);
if (StringUtils.isNotBlank(pointPictures)) {
log.info("pointPictures进入处理:{}", pointPictures);
JSONArray jsonArray = JSONArray.parseArray(pointPictures);
for (int i = 0; i < jsonArray.size(); i++) {
JSONObject jsonObject = jsonArray.getJSONObject(i);
if (Objects.isNull(jsonObject)) {
continue;
}
String downloadUrl ="";
// try { todo 20260422 yyy暂时注释
// downloadUrl = aliOssService.getDownloadUrl(jsonObject.getString("id"), jsonObject.getString("name"), true);
// log.info("downloadUrl:{}", downloadUrl);
// } catch (UnsupportedEncodingException e) {
// log.error("获取图片下载地址失败", e);
// }
jsonObject.put("url", downloadUrl);
}
log.info("jsonArray:{}", jsonArray);
xpPoint.setPointPictures(JSONArray.toJSONString(jsonArray));
log.info("xpPoint:{}", xpPoint.getPointPictures());
}
}
return list;
}
@Override
public List getHistoryStartPoints(HistoryStartPointsDto historyStartPointsDto) {
List xpPoints = this.baseMapper.selectBatchIds(historyStartPointsDto.getPointIds());
ArrayList resultList = new ArrayList<>(10);
for (XpPoint xpPoint : xpPoints) {
XpHistoryStartPointsVo xpHistoryStartPointsVo = new XpHistoryStartPointsVo();
xpHistoryStartPointsVo.setName(xpPoint.getPointName());
xpHistoryStartPointsVo.setAddress(xpPoint.getPointAddress());
xpHistoryStartPointsVo.setLatitude(xpPoint.getPointLat());
xpHistoryStartPointsVo.setLongitude(xpPoint.getPointLon());
xpHistoryStartPointsVo.setId(xpPoint.getId());
double distanced = FastDistanceCalculator.distanceFastKm(historyStartPointsDto.getLat(), historyStartPointsDto.getLon(), xpPoint.getPointLat(), xpPoint.getPointLon());
xpHistoryStartPointsVo.setDistance(distanced);
xpHistoryStartPointsVo.setTime(xpPoint.getParkingTimeLimit());
xpHistoryStartPointsVo.setElectronicAreaId(xpPoint.getElectronicAreaId());
resultList.add(xpHistoryStartPointsVo);
}
return resultList;
}
@Override
public List getHistoryEndPoints(HistoryEndPointsDto historyEndPointsDto) {
List xpPoints = this.baseMapper.selectBatchIds(historyEndPointsDto.getPointIds());
ArrayList resultList = new ArrayList<>(10);
for (XpPoint xpPoint : xpPoints) {
XpHistoryEndPointsVo xpHistoryEndPointsVo = new XpHistoryEndPointsVo();
xpHistoryEndPointsVo.setName(xpPoint.getPointName());
xpHistoryEndPointsVo.setAddress(xpPoint.getPointAddress());
xpHistoryEndPointsVo.setLatitude(xpPoint.getPointLat());
xpHistoryEndPointsVo.setLongitude(xpPoint.getPointLon());
xpHistoryEndPointsVo.setId(xpPoint.getId());
double distanced = FastDistanceCalculator.distanceFastKm(historyEndPointsDto.getLat(), historyEndPointsDto.getLon(), xpPoint.getPointLat(), xpPoint.getPointLon());
xpHistoryEndPointsVo.setDistance(distanced);
xpHistoryEndPointsVo.setTime(xpPoint.getParkingTimeLimit());
xpHistoryEndPointsVo.setElectronicAreaId(xpPoint.getElectronicAreaId());
resultList.add(xpHistoryEndPointsVo);
}
return resultList;
}
@Override
public StationDetailVo queryStationDetail(StationDetailQueryDto dto, String uid) {
StationDetailVo stationDetailVo = new StationDetailVo();
if (dto == null || dto.getStationId() == null) {
return stationDetailVo;
}
XpStationQuery query = new XpStationQuery();
query.setId(dto.getStationId());
query.setStatus(2);
List list = xpStationService.findList(query);
if(CollUtil.isEmpty(list)){
return stationDetailVo;
}
XpStation station = list.get(0);
LocalTime localTime = LocalTime.now();
boolean stationOperation = bootCommonService.getStationOperation(localTime, station.getOperationType(), station.getEffectivePeriod(), station.getEffectiveTimePeriod(), station.getFenceOperationStrategy());
if(!stationOperation){
return stationDetailVo;
}
// 仅查询场站下运营点位,最终结果还会叠加时段/围栏有效性过滤。
PointPageListListQuery queryPoint = new PointPageListListQuery();
queryPoint.setStationId( station.getId());
queryPoint.setPointType(1);
queryPoint.setOperationStatus(2);
List stationPoints = this.baseMapper.pageList(queryPoint);
if (CollUtil.isEmpty(stationPoints)) {
return stationDetailVo;
}
// Map areaMap = buildAreaMap(stationPoints.stream()
// .map(XpPoint::getElectronicAreaId)
// .filter(Objects::nonNull)
// .distinct()
// .collect(Collectors.toList()));
// Map fenceMap = buildFenceMap(areaMap.values().stream()
// .map(XpElectronicFenceArea::getElectronicId)
// .filter(Objects::nonNull)
// .distinct()
// .collect(Collectors.toList()));
Set collectStationIds = getCollectStationIds(uid);
Set collectPointIds = getCollectPointIds(uid);
// 默认点位优先置顶,其余点位再按场站内排序和距离排序。
List points = stationPoints.stream()
.filter(point -> isPointAvailable(localTime, point))
.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 list = this.baseMapper.queryPointByName(query);
if (list.isEmpty()) {
return searchVo;
}
LocalTime now = LocalTime.now();
for (XpPointsListVo xpPointsListVo : list) {
boolean inTime = bootCommonService.getPointOperation_vo(now, xpPointsListVo);
if (!inTime) {
// 不在运营段
xpPointsListVo.setState(0);
}
}
// 组装数据 并排序 剔除不在运营段的 数据
List resultList = list.stream().peek(vo -> vo.setDistance(FastDistanceCalculator.distanceFast(query.getLat(), query.getLon(), vo.getLatitude(), vo.getLongitude()))).filter(vo -> vo.getState() == 1).collect(Collectors.toList());
// 可用点位
List availablePoints = resultList.stream().filter(e -> query.getElectronicAreaId() != null && e.getElectronicAreaId().equals(query.getElectronicAreaId())).sorted(Comparator.comparing(XpPointsListVo::getDistance)).collect(Collectors.toList());//
// 不可用点位
List unavailablePoints = resultList.stream().filter(e -> query.getElectronicAreaId() == null || !e.getElectronicAreaId().equals(query.getElectronicAreaId())).sorted(Comparator.comparing(XpPointsListVo::getDistance)).collect(Collectors.toList());
searchVo.setAvailablePoints(availablePoints);
searchVo.setUnavailablePoints(unavailablePoints);
return searchVo;
}
@Override
public OrderDistanceVo getOrderDistance() {
OrderDistanceVo orderDistanceVo = new OrderDistanceVo();
XpSystemSetting xpSystemSetting = xpSystemSettingMapper.selectOne(null);
if (xpSystemSetting != null) {
orderDistanceVo.setMaxDistance(xpSystemSetting.getOrderDistance());
orderDistanceVo.setMinDistance(xpSystemSetting.getMinOrderDistance());
orderDistanceVo.setAdsorptionDistance(xpSystemSetting.getAdsorptionDistance());
orderDistanceVo.setNearPointDistance(xpSystemSetting.getNearPointDistance());
return orderDistanceVo;
}
return orderDistanceVo;
}
@Override
public List pageList(PageInfo pageInfo, PointPageListListQuery query) {
List list = null;
if (pageInfo == null) {
list = this.baseMapper.pageList(query);
}else{
list = this.baseMapper.pageList(pageInfo,query);
}
if(CollUtil.isNotEmpty(list)){
/**
* hover在状态标签时,展示信息
* - 已停用:展示停用原因;比如“因所属场站 [白云机场] 已关闭,该点位同步失效”
* - 测试不通过:展示不通过原因
* - 待生效:展示生效时间
*/
for (XpPoint point : list) {
String hoverStatusDes = "";
if(point.getVerificationStatus()==4){//验证状态 1=待验证、2=验证中、3=已验证 4验证失败 5待生效
hoverStatusDes = (point.getRemarks() == null ?"":point.getRemarks());
}
if(point.getOperationStatus() == 3){//运营状态 1=待启用 2=启用中 3=已停用 4=已失效
hoverStatusDes = (point.getRemarks()==null?"":point.getRemarks());
}
if(point.getVerificationStatus()==5 && point.getOperationStatus() == 1){
if(point.getOperationType() == 1){//运营类型 1=与运营区同步 2=自定义时间段
;
//[{"name": "策略组1", "time": ["06:00-12:00", "13:00-22:59"], "type": 0, "typeStr": []},
// {"name": "策略组2", "time": ["05:00-12:00", "12:00-16:00"], "type": 2, "typeStr": ""},
// {"name": "策略组3", "time": ["09:00-21:59"], "type": 1, "typeStr": ""}]
JSONArray electronicOperationStrategys = JSONArray.parseArray(point.getFenceOperationStrategy());
if(!electronicOperationStrategys.isEmpty()){
StringJoiner joiner = new StringJoiner(";");
for (Object electronicOperationStrategy : electronicOperationStrategys) {
JSONObject obj = (JSONObject) electronicOperationStrategy;
List times = obj.getList("time",String.class);
joiner.add((String.join(";", times)));
}
hoverStatusDes = joiner.toString() ;
}
}else{
if(StrUtil.isNotBlank(point.getEffectiveTimePeriod()) ){
List effectiveTimePeriods = JSONArray.parseArray(point.getEffectiveTimePeriod(), String.class);
hoverStatusDes = String.join(";", effectiveTimePeriods);
}
}
}
point.setHoverStatusDes(hoverStatusDes);
}
}
return list;
}
@Override
public PageInfo pageList(PointPageListListQuery query) {
if(CollUtil.isNotEmpty(query.getFenceAndStationList())){
List fenceAreaIds = new ArrayList<>();
List stationIds = new ArrayList<>();
for (FenceAndStationVo bean : query.getFenceAndStationList()) {
if(bean.getType()==1){
fenceAreaIds.addAll(StrUtil.split(bean.getIds(),",") );
}else{
stationIds.addAll(StrUtil.split(bean.getIds(),",") );
}
}
query.setFenceAreaIds(fenceAreaIds);
query.setStationIds(stationIds);
}
PageInfo pageInfo = new PageInfo<>(query.getPageNum(),query.getPageSize());
if (StringUtils.isNotBlank(query.getPointLonAndLat())) {
List lonAndLat = query.getPointLonAndLat().trim().contains(",") ? Arrays.asList(query.getPointLonAndLat().split(",")) :
query.getPointLonAndLat().trim().contains(" ") ? Arrays.asList(query.getPointLonAndLat().split(" ")) :
Collections.emptyList();
if (CollUtil.isNotEmpty(lonAndLat)) {
query.setPointLon(lonAndLat.get(0));
query.setPointLat(lonAndLat.get(1));
query.setPointLonAndLat(null);
}
}
List list = this.pageList(pageInfo,query);
pageInfo.setRecords(list);
return pageInfo;
}
private Map buildAreaMap(List 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 buildFenceMap(List fenceIds) {
if (CollUtil.isEmpty(fenceIds)) {
return Collections.emptyMap();
}
return xpElectronicFenceMapper.selectBatchIds(fenceIds).stream()
.collect(Collectors.toMap(XpElectronicFence::getId, fence -> fence, (a, b) -> a));
}
/**
* 子点位是否可展示与场站卡片中。
* 规则与现有点位有效性保持一致,避免地图页和其他点位查询出现口径偏差。
*/
private boolean isPointAvailable(LocalTime localTime, XpPoint point) {
return bootCommonService.getPointOperation(localTime, point);
}
/**
* 组装单个场站子点位返回对象,并在有默认点位配置时打标。
*/
private StationPointItemVo toStationPointItem(XpPoint point, Long defaultPointId, Double latitude, Double longitude, Set 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 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。
* 收藏服务异常时降级为空集合,避免影响场站详情主流程。
*/
@Override
public Set getCollectStationIds(String uid) {
if (!StringUtils.hasText(uid)) {
return Collections.emptySet();
}
try {
R> 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();
}
}
@Override
public List listByStationId(Long stationId) {
if (stationId == null) {
return Collections.emptyList();
}
return this.lambdaQuery()
.eq(XpPoint::getStationId, stationId)
.list();
}
@Transactional(rollbackFor = Exception.class)
@Override
public void clearStationInfoByStationId(Long stationId) {
if (stationId == null) {
return;
}
this.lambdaUpdate()
.eq(XpPoint::getStationId, stationId)
.set(XpPoint::getStationId, null)
.set(XpPoint::getStationSortNo, 0)
.update();
}
/**
* 查询用户收藏的点位ID。
* 子点位收藏态只用于回显,不影响场站筛选和排序主逻辑。
*/
private Set getCollectPointIds(String uid) {
if (!StringUtils.hasText(uid)) {
return Collections.emptySet();
}
try {
R> 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();
}
}
/**
* 场站停用
* - 操作项展示为 [停用]。点击后弹出二次确认框,“当前场站包含12个点位,确认一起停用?
* - 是:确认后场站以及子点位状态流转至“已停用”。
* - 否:子点位维持当前状态,但自动退回为“独立点位”。
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void statusStopByStationId(Long stationId, Boolean pointStop) {
if (stationId == null) {
return;
}
if(pointStop){
this.lambdaUpdate()
.eq(XpPoint::getStationId, stationId)
.set(XpPoint::getOperationStatus, 3)//运营状态 1=待启用 2=启用中 3=已停用 4=已失效
.update();
}else{
clearStationInfoByStationId(stationId);
}
}
@Transactional(rollbackFor = Exception.class)
@Override
public void statusEnableByStationId(Long stationId) {
if (stationId == null) {
return;
}
this.lambdaUpdate()
.eq(XpPoint::getStationId, stationId)
.set(XpPoint::getOperationStatus, 2)//运营状态 1=待启用 2=启用中 3=已停用 4=已失效
.update();
}
/**
* 场站删除
* - 删除拦截逻辑:
* - 测试阶段(待测试、测试中等):点击直接删除。
* - 历史运营阶段(已停用、失效):点击弹出二次确认框“当前场站包含12个点位,确认一起删除?”
* - 是:点位统一一起删除
* - 否:不自动删除点位,点位自动退回为[待测试]的“独立点位”,并自动继承父级运营区的运营时段
*/
@Transactional(rollbackFor = Exception.class)
@Override
public void pointDeleteByStationId(Long stationId, Boolean pointDelete) {
if (stationId == null) {
return;
}
if(pointDelete){
this.lambdaUpdate()
.eq(XpPoint::getStationId, stationId)
.set(XpPoint::getDeleted, 1)
.update();
}else{
clearStationInfoByStationId(stationId);
}
}
@Resource
private TokenService tokenService;
/**
* 场站 失效-围栏失效 场站失效
* @param electronicAreaIds
*/
@Override
public void setStatusFailure(List electronicAreaIds){
if(CollUtil.isNotEmpty(electronicAreaIds)){
LambdaQueryWrapper query = new LambdaQueryWrapper<>();
query.in(XpPoint::getElectronicAreaId, electronicAreaIds);
query.eq(XpPoint::getDeleted, 0);
List list = super.list(query);
if(!list.isEmpty()){
LoginUser loginUser = tokenService.getLoginUser();
for (XpPoint bean : list) {
bean.setUpdateBy(loginUser.getUsername());
bean.setUpdateTime(LocalDateTime.now());
bean.setOperationStatus(4);//场站状态 0=草稿 1=待启用 2=启用中 3=已停用 4=失效
boolean b = updateById(bean);
}
}
}
}
}