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 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); } 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 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); } } } } }