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;
|
|
/**
|
* <p>
|
* 点位管理 服务实现类
|
* </p>
|
*
|
* @author liu
|
* @author WuGuanFengYue
|
* @since 2026-02-03
|
*/
|
@Service
|
@Slf4j
|
public class XpPointServiceImpl extends ServiceImpl<XpPointMapper, XpPoint> 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<XpPoint> 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<XpPoint> list = baseMapper.pageList(query);
|
// 添加距离计算字段
|
// List<XpPoint> list = list(new LambdaQueryWrapper<XpPoint>().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<NearestNextVo> nearestNextVos = new ArrayList<>();
|
LocalTime now = LocalTime.now();
|
|
|
for (XpPoint xpPoint : list) {
|
// 判断是否在时间段内
|
boolean intime = bootCommonService.getPointOperation(now, xpPoint);
|
if (!intime) {
|
continue;
|
}
|
Integer parkingTimeLimit = xpPoint.getParkingTimeLimit();
|
//如果点位位于场站内,则使用场站的限停时间
|
if(null != xpPoint.getStationId()){
|
XpStation xpStation = xpStationService.getById(xpPoint.getStationId());
|
parkingTimeLimit = xpStation.getParkingTimeLimit();
|
}
|
// 计算距离 小于的站点
|
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(parkingTimeLimit);
|
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<XpRegion>()
|
.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<Long> 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<XpPoint> pointMap(PointMapQuery query) {
|
List<XpPoint> list = this.baseMapper.pointMap(query);
|
return list;
|
}
|
|
@Override
|
public List<FenceAndStationVo> findListFenceAndStation(String cityCode){
|
return baseMapper.findListFenceAndStation(cityCode);
|
}
|
|
@Override
|
public List<XpPoint> findList(PointListQuery query) {
|
return this.baseMapper.listAll(query);
|
}
|
|
@Override
|
public List<XpPoint> listAll(PointListQuery query) {
|
return this.baseMapper.listAll(query);
|
}
|
|
@Override
|
public List<XpHistoryStartPointsVo> getHistoryStartPoints(HistoryStartPointsDto historyStartPointsDto) {
|
List<XpPoint> xpPoints = this.baseMapper.selectBatchIds(historyStartPointsDto.getPointIds());
|
ArrayList<XpHistoryStartPointsVo> resultList = new ArrayList<>(10);
|
for (XpPoint xpPoint : xpPoints) {
|
Integer parkingTimeLimit = xpPoint.getParkingTimeLimit();
|
//如果点位位于场站内,则使用场站的限停时间
|
if(null != xpPoint.getStationId()){
|
XpStation xpStation = xpStationService.getById(xpPoint.getStationId());
|
parkingTimeLimit = xpStation.getParkingTimeLimit();
|
}
|
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(parkingTimeLimit);
|
xpHistoryStartPointsVo.setElectronicAreaId(xpPoint.getElectronicAreaId());
|
resultList.add(xpHistoryStartPointsVo);
|
|
}
|
|
return resultList;
|
}
|
|
@Override
|
public List<XpHistoryEndPointsVo> getHistoryEndPoints(HistoryEndPointsDto historyEndPointsDto) {
|
List<XpPoint> xpPoints = this.baseMapper.selectBatchIds(historyEndPointsDto.getPointIds());
|
ArrayList<XpHistoryEndPointsVo> resultList = new ArrayList<>(10);
|
for (XpPoint xpPoint : xpPoints) {
|
Integer parkingTimeLimit = xpPoint.getParkingTimeLimit();
|
//如果点位位于场站内,则使用场站的限停时间
|
if(null != xpPoint.getStationId()){
|
XpStation xpStation = xpStationService.getById(xpPoint.getStationId());
|
parkingTimeLimit = xpStation.getParkingTimeLimit();
|
}
|
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(parkingTimeLimit);
|
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<XpStation> 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<XpPoint> stationPoints = this.baseMapper.pageList(queryPoint);
|
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(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<XpPointsListVo> 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<XpPointsListVo> 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<XpPointsListVo> availablePoints = resultList.stream().filter(e -> query.getElectronicAreaId() != null && e.getElectronicAreaId().equals(query.getElectronicAreaId())).sorted(Comparator.comparing(XpPointsListVo::getDistance)).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());
|
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<XpPoint> pageList(PageInfo<XpPoint> pageInfo, PointPageListListQuery query) {
|
List<XpPoint> 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<String> times = obj.getList("time",String.class);
|
joiner.add((String.join(";", times)));
|
}
|
hoverStatusDes = joiner.toString() ;
|
}
|
}else{
|
if(StrUtil.isNotBlank(point.getEffectiveTimePeriod()) ){
|
List<String> effectiveTimePeriods = JSONArray.parseArray(point.getEffectiveTimePeriod(), String.class);
|
hoverStatusDes = String.join(";", effectiveTimePeriods);
|
}
|
}
|
}
|
point.setHoverStatusDes(hoverStatusDes);
|
}
|
}
|
return list;
|
}
|
|
@Override
|
public PageInfo<XpPoint> pageList(PointPageListListQuery query) {
|
if(CollUtil.isNotEmpty(query.getFenceAndStationList())){
|
List<String> fenceAreaIds = new ArrayList<>();
|
List<String> 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<XpPoint> pageInfo = new PageInfo<>(query.getPageNum(),query.getPageSize());
|
if (StringUtils.isNotBlank(query.getPointLonAndLat())) {
|
List<String> 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<XpPoint> list = this.baseMapper.pageList(pageInfo,query);
|
list.forEach(s->s.setStrId(s.getId().toString()));
|
pageInfo.setRecords(list);
|
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 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<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。
|
* 收藏服务异常时降级为空集合,避免影响场站详情主流程。
|
*/
|
@Override
|
public 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();
|
}
|
}
|
|
@Override
|
public List<XpPoint> 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<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();
|
}
|
}
|
|
|
|
/**
|
* 场站停用
|
* - 操作项展示为 [停用]。点击后弹出二次确认框,“当前场站包含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<Long> electronicAreaIds){
|
if(CollUtil.isNotEmpty(electronicAreaIds)){
|
LambdaQueryWrapper<XpPoint> query = new LambdaQueryWrapper<>();
|
query.in(XpPoint::getElectronicAreaId, electronicAreaIds);
|
query.eq(XpPoint::getDeleted, 0);
|
|
List<XpPoint> 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);
|
}
|
}
|
}
|
}
|
}
|