package com.xiaopeng.itinerary.service.impl; import com.alibaba.fastjson2.JSON; 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.account.GetUserCouponDto; import com.xiaopeng.api.dto.map.OrderRouteAndFareDto; import com.xiaopeng.api.dto.map.RouteAndFareDto; import com.xiaopeng.api.dto.map.RouteStaleCheckDto; import com.xiaopeng.api.dto.map.SelectNavigationRouteDto; import com.xiaopeng.api.dto.operation.LonLatDto; import com.xiaopeng.api.enums.RouteStrategyKind; import com.xiaopeng.api.dto.pushMessage.SendMessageDTO; import com.xiaopeng.api.feignClient.account.XpAppUserClient; import com.xiaopeng.api.feignClient.account.XpAppUserCouponClient; import com.xiaopeng.api.feignClient.map.XpMapClient; import com.xiaopeng.api.feignClient.operation.XpPointClient; import com.xiaopeng.api.feignClient.order.XpOrderClient; import com.xiaopeng.api.feignClient.pushMessage.XpPushMessageClient; import com.xiaopeng.api.model.operation.*; import com.xiaopeng.api.model.order.XpOrder; import com.xiaopeng.api.vo.account.UserCouponVo; import com.xiaopeng.api.vo.operation.FareVo; import com.xiaopeng.api.vo.operation.MultiRouteAndFareVo; import com.xiaopeng.api.vo.operation.RouteAndFareVo; import com.xiaopeng.api.vo.operation.RouteOptionVo; import com.xiaopeng.api.vo.operation.RoutePreferenceItemVo; import com.xiaopeng.api.vo.operation.RouteStaleCheckVo; import com.xiaopeng.api.vo.operation.RouteVo; import com.xiaopeng.api.vo.order.XpPricingCardVo; 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.DateUtils; import com.xiaopeng.common.redis.service.RedisService; import com.xiaopeng.itinerary.constant.I18nKeyConstant; import com.xiaopeng.itinerary.dto.XpOrderDto; import com.xiaopeng.itinerary.navigation.MultiRouteSnapshotPayload; import com.xiaopeng.itinerary.mapper.*; import com.xiaopeng.itinerary.service.IXpGeoQueryEntityService; import com.xiaopeng.itinerary.service.XpPointService; import com.xiaopeng.itinerary.service.XpPricingService; import com.xiaopeng.itinerary.vo.QueueStatusVo; import com.xiaopeng.itinerary.utils.XpPricingUtil; import com.xiaopeng.itinerary.utils.holiday.HolidayUtil; import com.xiaopeng.itinerary.utils.map.FastDistanceCalculator; import com.xiaopeng.itinerary.utils.map.GDMapGeocodingUtil; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.mongodb.core.geo.GeoJsonPoint; import org.springframework.stereotype.Service; import javax.annotation.PreDestroy; import javax.annotation.Resource; import java.math.BigDecimal; import java.math.RoundingMode; import java.time.LocalDateTime; import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; /** *

* 定价管理 服务实现类 *

* * @author liu * @since 2026-02-03 */ @Slf4j @Service public class XpPricingServiceImpl extends ServiceImpl implements XpPricingService { @Resource private XpPricingUtil xpPricingUtil; @Resource private XpMapClient xpMapClient; @Resource private XpOpenCityMapper xpOpenCityMapper; @Resource private XpRoutePlanMapper xpRoutePlanMapper; @Resource private RedisService redisService; @Resource private XpSystemSettingMapper systemSettingMapper; @Resource private XpAppUserCouponClient xpAppUserCouponClient; @Resource private XpOrderClient xpOrderClient; @Resource private XpTradingStrategyMapper xpTradingStrategyMapper; @Resource private XpElectronicFenceAreaMapper xpElectronicFenceAreaMapper; @Resource private XpWhitelistMapper xpWhitelistMapper; @Resource private XpAppUserClient xpAppUserClient; @Resource private HolidayUtil holidayUtil; @Autowired private IXpGeoQueryEntityService geoQueryEntityService; @Autowired private XpElectronicFenceMapper xpElectronicFenceMapper; @Autowired private XpPointService xpPointService; @Resource private XpPushMessageClient xpPushMessageClient; @Resource private GDMapGeocodingUtil gdMapGeocodingUtil; @Resource private XpPointClient xpPointClient; @Value("${navigation.route.stale-seconds:60}") private int navigationStaleSeconds; @Value("${navigation.route.price-wave-percent:5}") private double navigationPriceWavePercent; /** * 后台可配置的偏好项编码,逗号分隔,如 SMART,TIME,PRICE */ @Value("${navigation.route.preference.enabled-codes:SMART,TIME,PRICE}") private String navigationPreferenceEnabledCodes; // 自定义线程池用于并行执行任务 private final ExecutorService executorService; // 初始化自定义线程池 public XpPricingServiceImpl() { this.executorService = new ThreadPoolExecutor( 40, // 核心线程数 100, // 最大线程数 60, // 线程空闲时间(秒) TimeUnit.SECONDS, // 时间单位 new LinkedBlockingQueue<>(400), // 工作队列 new ThreadFactory() { private final AtomicInteger threadNumber = new AtomicInteger(1); @Override public Thread newThread(Runnable r) { Thread thread = new Thread(r); thread.setName("pricing-service-thread-" + threadNumber.getAndIncrement()); thread.setDaemon(false); return thread; } }, // 线程工厂 new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略 ); } // 关闭线程池 @PreDestroy public void destroy() { if (executorService != null && !executorService.isShutdown()) { executorService.shutdown(); try { if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) { executorService.shutdownNow(); } } catch (InterruptedException e) { executorService.shutdownNow(); Thread.currentThread().interrupt(); } } } /** * 根据当前时间获取时段 * 例如:15:50 → 15-16 * @return 时段字符串,格式:HH-HH */ private String getCurrentTimeSlot() { Calendar calendar = Calendar.getInstance(); int hour = calendar.get(Calendar.HOUR_OF_DAY); return hour + "-" + (hour + 1); } @Override public RouteAndFareVo getRouteAndFare(RouteAndFareDto routeAndFareDto, String userId) { // // 判断是否5分钟内进行了查询 相同站点的起始点 并且是同时段 // String key = "xp-map-query-" + routeAndFareDto.getOpenCityId() // + "-" + routeAndFareDto.getOrigin().getLongitude() + "-" + routeAndFareDto.getOrigin().getLatitude() // + "-" + routeAndFareDto.getDestination().getLongitude() + "-" + routeAndFareDto.getDestination().getLatitude() + "-" + getCurrentTimeSlot() + "-" + userId+routeAndFareDto.getUserCouponId(); // // if (redisService.hasKey(key)) { // String jsonStr = Objects.requireNonNull(redisService.getCacheObject(key)).toString(); // RouteAndFareVo RouteAndFareVo = JSONObject.parseObject(jsonStr, RouteAndFareVo.class); // return (RouteAndFareVo); // } // 1. 基础数据查询 Map geocode = gdMapGeocodingUtil.geocode(routeAndFareDto.getOrigin().getLongitude().toString(), routeAndFareDto.getOrigin().getLatitude().toString()); if(null == geocode){ throw new ServiceException("获取城市信息失败", I18nKeyConstant.CITY_NOT_FOUND); } Long cityCode = routeAndFareDto.getOpenCityId(); XpOpenCity openCities = xpOpenCityMapper.selectById(cityCode); if (openCities == null ) { throw new ServiceException("获取城市信息失败", I18nKeyConstant.CITY_NOT_FOUND); } XpOpenCity xpOpenCity = openCities; routeAndFareDto.setOpenCityId(xpOpenCity.getId()); XpRoutePlan xpRoutePlan = xpRoutePlanMapper.selectOne(new LambdaQueryWrapper().eq(XpRoutePlan::getAuditStatus, 2).eq(XpRoutePlan::getCityCode, cityCode)); if (xpRoutePlan != null) { routeAndFareDto.setRoutePlanRule(xpRoutePlan.getRoutePlanRule()); } Object userPref = redisService.getCacheObject(CacheConstants.USER_ROUTE_PREFERENCE + userId); if (userPref != null) { try { routeAndFareDto.setRoutePlanRule(Integer.valueOf(userPref.toString())); } catch (Exception ignored) { // keep city default } } XpPricing xpPricing = this.baseMapper.selectOne(new LambdaQueryWrapper().eq(XpPricing::getCityId, routeAndFareDto.getOpenCityId())); if (xpPricing == null) { throw new ServiceException("当前区域未开通服务", I18nKeyConstant.NO_CURRENT_REGION); } // 算距离 R distanceAndDuration = xpMapClient.getDistanceAndDuration(routeAndFareDto); if (distanceAndDuration.getCode() != 200) { throw new ServiceException("获取导肮信息失败", I18nKeyConstant.PATH_NOT_FOUND); } RouteVo routeVo = distanceAndDuration.getData(); // 2. 计算价格(需要在并行任务前完成,因为优惠券查询需要价格) XpPricingCardVo xpPricingCardVo; if (xpPricing.getPricingType() == 1) { xpPricingCardVo = xpPricingUtil.calculationPrice(userId, JSON.parseObject(xpPricing.getBasicPricingJson()), JSON.parseArray(xpPricing.getStartingFareJson()), routeVo.getDistance(), routeVo.getDuration(), 0, 0.0, xpPricing.getIsDynamicPrice(), routeAndFareDto.getOpenCityId(), xpPricing.getDynamicPricingJson() ); } else { xpPricingCardVo = xpPricingUtil.calculationFixedPrice(userId, JSON.parseObject(xpPricing.getBasicPricingJson()), JSON.parseArray(xpPricing.getStartingFareJson()), routeVo.getDistance(), routeVo.getDuration(), 0, 0.0, xpPricing.getIsDynamicPrice(), routeAndFareDto.getOpenCityId(), xpPricing.getDynamicPricingJson() ); } // 优惠券:null=自动最佳;0=不使用;>0=指定券 XpPricingCardVo finalXpPricingCardVo = xpPricingCardVo; R coupon = null; CompletableFuture> couponFuture=null; Long ucId = routeAndFareDto.getUserCouponId(); if (Long.valueOf(0L).equals(ucId)) { coupon = R.ok(new UserCouponVo()); } else if (ucId != null && ucId > 0L) { coupon = xpAppUserCouponClient.getUserCoupon(new GetUserCouponDto(Long.valueOf(userId), finalXpPricingCardVo.getTotalPrice(), routeAndFareDto.getOpenCityId(), ucId)); if(coupon.getCode()==200 && coupon.getData().getStatus()==2){ throw new ServiceException("优惠券不满足条件", I18nKeyConstant.COUPON_NOT_SATISFY); } } else { couponFuture= CompletableFuture.supplyAsync( () -> xpAppUserCouponClient.getUserCoupon( new GetUserCouponDto(Long.valueOf(userId), finalXpPricingCardVo.getTotalPrice(), routeAndFareDto.getOpenCityId(),null) ), executorService ); } // 3. 并行执行远程服务调用 判断是否同一经营区域 CompletableFuture> diagnosisAreaFuture = CompletableFuture.supplyAsync( () -> xpMapClient.diagnosisArea(routeAndFareDto), executorService ); // 4. 等待所有并行任务完成并获取结果 R diagnosisResult = diagnosisAreaFuture.join(); R couponResult =null; if (Long.valueOf(0L).equals(ucId) || (ucId != null && ucId > 0L)) { couponResult = coupon; } else { couponResult = couponFuture.join(); } // 5. 处理诊断结果 if (diagnosisResult.getCode() != R.SUCCESS) { throw new RuntimeException(diagnosisResult.getMsg()); } if (!diagnosisResult.getData()) { throw new ServiceException("当前区域未开通服务", I18nKeyConstant.NO_CURRENT_REGION); } // // 6. 处理路线结果 // if (routeResult.getCode() != 200) { // throw new RuntimeException(routeResult.getMsg()); // } // RouteVo routeVo = routeResult.getData(); // 起终点最小距离 走缓存 Integer minOrderDistance; Object minOrderDistanceObj = redisService.getCacheObject(CacheConstants.MIN_ORDER_DISTANCE); if (minOrderDistanceObj != null) { minOrderDistance = Integer.valueOf(minOrderDistanceObj.toString()); } else { minOrderDistance = systemSettingMapper.selectOne(null).getMinOrderDistance(); } if (routeVo.getDistance() < Integer.valueOf(minOrderDistance)) { throw new ServiceException("当前上下车点距离过近,建议步行前往", I18nKeyConstant.TO_WORK); } // 7. 处理优惠券结果 if (couponResult.getCode() != R.SUCCESS) { throw new RuntimeException(couponResult.getMsg()); } UserCouponVo userCouponVo = couponResult.getData(); if (userCouponVo != null) { Double money = userCouponVo.getMoney(); money = new BigDecimal(money).setScale(2, RoundingMode.HALF_UP).doubleValue(); xpPricingCardVo.setCoupon(String.valueOf(money)); if (xpPricingCardVo.getTotalPrice() > money) { xpPricingCardVo.setSystemDiscount(xpPricingCardVo.getTotalPrice() - money); } } // 8. 构建响应对象 RouteAndFareVo routeAndFareVo = new RouteAndFareVo(); routeAndFareVo.setRoute(routeVo); // 转2位小数处理 xpPricingCardVo.setTotalPriceStr(new BigDecimal(xpPricingCardVo.getTotalPrice()).setScale(2, RoundingMode.HALF_UP).toString()); xpPricingCardVo.setStartPriceStr(new BigDecimal(xpPricingCardVo.getStartPrice()).setScale(2, RoundingMode.HALF_UP).toString()); xpPricingCardVo.setDistancePriceStr(new BigDecimal(xpPricingCardVo.getDistancePrice()).setScale(2, RoundingMode.HALF_UP).toString()); xpPricingCardVo.setDurationPriceStr(new BigDecimal(xpPricingCardVo.getDurationPrice()).setScale(2, RoundingMode.HALF_UP).toString()); xpPricingCardVo.setSystemDiscountStr(new BigDecimal(xpPricingCardVo.getSystemDiscount()).setScale(2, RoundingMode.HALF_UP).toString()); routeAndFareVo.setXpPricingCard(xpPricingCardVo); FareVo fareVo = new FareVo(); fareVo.setEstimatedDistanceKm(routeVo.getDistance().doubleValue() / 1000); fareVo.setEstimatedDurationMin(routeVo.getDuration() / 60); // 9. 设置费用信息 fareVo.setOriginalFare(xpPricingCardVo.getTotalPrice()); fareVo.setOriginalFareStr(new BigDecimal(fareVo.getOriginalFare()).setScale(2, RoundingMode.HALF_UP).toString()); if (userCouponVo != null) { if(userCouponVo.getMoney()>fareVo.getOriginalFare()){ fareVo.setDiscountAmount(fareVo.getOriginalFare()); fareVo.setFinalFare(0.00); }else { fareVo.setDiscountAmount(userCouponVo.getMoney()); fareVo.setFinalFare(fareVo.getOriginalFare() - fareVo.getDiscountAmount()); } if(fareVo.getFinalFare()>0){ fareVo.setSystemDiscount(fareVo.getFinalFare()); fareVo.setFinalFare(0.00); } fareVo.setCouponId(userCouponVo.getId()); } else { fareVo.setDiscountAmount(0.00); fareVo.setFinalFare(fareVo.getOriginalFare()); fareVo.setSystemDiscount(fareVo.getFinalFare()); fareVo.setFinalFare(0.00); } // 10. 检查等待时间 Integer waitTime = getWaitTime(routeAndFareDto.getOrigin().getLongitude(), routeAndFareDto.getOrigin().getLatitude()); // if (waitTime > 3) { // throw new ServiceException("当前区域正在等待中,请耐心等待", I18nKeyConstant.WAITING_TIME); // } fareVo.setWaitingTime(waitTime); // 11. 生成订单ID并缓存 String id = CacheConstants.ORD + System.currentTimeMillis() + "_" + UUID.randomUUID().toString().substring(0, 8); redisService.setCacheObject(CacheConstants.ORD + id + userId, xpPricingCardVo, 1l, TimeUnit.DAYS); fareVo.setId(id); // 12. 设置费用信息 fareVo.setFinalFareStr(new BigDecimal(fareVo.getFinalFare()).setScale(2, RoundingMode.HALF_UP).toString()); fareVo.setOriginalFare(new BigDecimal(fareVo.getOriginalFare()).setScale(2,RoundingMode.HALF_UP).doubleValue()); fareVo.setSystemDiscount(new BigDecimal(fareVo.getSystemDiscount() == null ? 0 : fareVo.getSystemDiscount()).setScale(2,RoundingMode.HALF_UP).doubleValue()); routeAndFareVo.setFareEstimate(fareVo); routeAndFareVo.getXpPricingCard().setFinalFareStr(fareVo.getFinalFareStr()); // String voJsonStr = JSONObject.toJSONString(routeAndFareVo); // 进行缓存 // redisService.setCacheObject(key, voJsonStr, 5l, TimeUnit.MINUTES); //缓存导航数据,用于下单后推送导航给车端和乘客端 redisService.setCacheObject("ORDER_NAVIGATION_END_DATA:" + userId, JSON.toJSONString(routeVo), 24L, TimeUnit.HOURS); return routeAndFareVo; } /** * 获取等待时间 * @param lon * @param lat * @return */ @Override public Integer getWaitTime(double lon, double lat) { List entities = geoQueryEntityService.findByLocationWithin500MetersAndLastWeekAndStatusGreater( lon, lat, getCurrentTimeSlot() ); if (entities.isEmpty()) { return 1; } else { // 已完成接单的数据 List validEntities = entities.stream().filter(e -> e.getStatus() == 1).collect(Collectors.toList()); long waitNum = entities.stream().filter(e -> e.getStatus() == 0).count(); // 计算平均等待时间 OptionalDouble averageOptional = validEntities.stream().mapToDouble(XpGeoQueryEntity::getSeconds).average(); if (averageOptional.isPresent()) { double average = averageOptional.getAsDouble(); // 未完成接单的数量 int count = (int) waitNum; if (count == 0) { return 1; } else { double waitMinutes = (average * count) / 60; if (waitMinutes > 3) { // throw new ServiceException("当前区域正在等待中,请耐心等待", I18nKeyConstant.WAITING_TIME); } else if (waitMinutes == 0) { return 1; }else if(waitMinutes>0 && waitMinutes<1){ return 1; } return (int) waitMinutes; } } else { return 1; } } } /** * 获取等待时间 * @param lon * @param lat * @return */ @Override public Integer getWaitTimeTimeSlot(double lon, double lat,String timeSlot) { List entities = geoQueryEntityService.findByLocationWithin500MetersAndLastWeekAndStatusGreater( lon, lat, timeSlot ); if (entities.isEmpty()) { return 1; } else { // 已完成接单的数据 List validEntities = entities.stream().filter(e -> e.getStatus() == 1).collect(Collectors.toList()); // 计算平均等待时间 OptionalDouble averageOptional = validEntities.stream().mapToDouble(XpGeoQueryEntity::getSeconds).average(); if (averageOptional.isPresent()) { double average = averageOptional.getAsDouble(); return (int) average; } else { return 1; } } } @Override public String saveOrder(XpOrderDto dto, String uid) { // 是否在运营区域时间内 XpElectronicFenceArea xpElectronicFenceArea = xpElectronicFenceAreaMapper.selectById(dto.getXpElectronicFenceAreaId()); XpElectronicFence xpElectronicFence = xpElectronicFenceMapper.selectById(xpElectronicFenceArea.getElectronicId()); String operationStrategy = xpElectronicFence.getOperationStrategy(); Boolean inOperationTime = isInOperationTime(operationStrategy); if (!inOperationTime && xpElectronicFence.getFailureType()!=1) { log.info("当前区域暂未开通,下单失败 {}", uid); throw new ServiceException("当前区域未开通服务", I18nKeyConstant.NO_CURRENT_REGION); } Integer cancelCount = xpTradingStrategyMapper.selectOne(null).getCancleCount(); // 判断是否有进行的订单 R r = xpOrderClient.hasUnPaodOrder(uid); if (r.getData() > 0) { throw new RuntimeException("有正在进行的订单"); } // 获取起点经纬度 XpPoint xpPoint = xpPointService.getById(dto.getStartPointId()); if(xpPoint ==null || xpPoint.getOperationStatus()!=2){ return null; } // 终点 XpPoint endXpPoint = xpPointService.getById(dto.getEndPointId()); if(endXpPoint ==null || endXpPoint.getOperationStatus()!=2){ // throw new ServiceException("该站点已停止运营", 200,I18nKeyConstant.NO_CURRENT_POINT); return null; } // 判断点位是否在运营区域内 boolean inTime = xpPointClient.nowInTimePoint(dto.getStartPointId()).getData(); if (!inTime) { throw new ServiceException("当前点位未开通服务", I18nKeyConstant.NO_CURRENT_REGION); } inTime = xpPointClient.nowInTimePoint(dto.getEndPointId()).getData(); if (!inTime) { throw new ServiceException("当前点位未开通服务", I18nKeyConstant.NO_CURRENT_REGION); } // 判断当天取消的次数 后台配置 redis 查询 // 静默处理 boolean isSilent = false; Integer num = xpOrderClient.todayCancellationCount(Long.valueOf(uid)).getData(); if (null != num && num >= cancelCount) { isSilent = true; } // 获取起步里程 // XpPricing xpPricing = this.baseMapper.selectOne(new LambdaQueryWrapper().eq(XpPricing::getCityId, dto.getOpenCityId())); // Integer startMileage = XpPricingUtil.getStartMileage(xpPricing.getStartingFareJson()); // if (startMileage == null) { // throw new ServiceException("当前区域未开通服务", I18nKeyConstant.NO_CURRENT_REGION); // } // 根据id获取手机号 Boolean isPriorityDispatch = true; // R xpAppUserR = xpAppUserClient.queryOperationsUserById(Long.valueOf(uid)); // if (xpAppUserR.getCode() == R.SUCCESS) { // Long count = xpWhitelistMapper.selectCount(new LambdaQueryWrapper().eq(XpWhitelist::getAccount, xpAppUserR.getData().getPhoneNumber()).eq(XpWhitelist::getIsPriorityDispatch, 1)); // if (count > 0) { // isPriorityDispatch = true; // } // } // 生成订单数据 XpOrder xpOrder = new XpOrder(); xpOrder.setMileageKilometers(0.0); xpOrder.setAppUserId(Long.valueOf(uid)); xpOrder.setOrderNumber("XP" + System.currentTimeMillis()); xpOrder.setPlacementLon(dto.getPlacementLon()); xpOrder.setPlacementLat(dto.getPlacementLat()); xpOrder.setPlacementAddress(dto.getPlacementAddress()); xpOrder.setCityId(dto.getOpenCityId()); xpOrder.setStartLon(xpPoint.getPointLon()); xpOrder.setStartLat(xpPoint.getPointLat()); xpOrder.setBoardingLon(xpPoint.getPointLon()); xpOrder.setBoardingLat(xpPoint.getPointLat()); xpOrder.setBoardingAddress(xpPoint.getPointAddress()); xpOrder.setEndLon(endXpPoint.getPointLon()); xpOrder.setEndLat(endXpPoint.getPointLat()); xpOrder.setStartAddress(xpPoint.getPointAddress()); xpOrder.setEndAddress(endXpPoint.getPointAddress()); xpOrder.setXpElectronicFenceAreaId(dto.getXpElectronicFenceAreaId()); xpOrder.setStartPointId(dto.getStartPointId()); xpOrder.setEndPointId(dto.getEndPointId()); xpOrder.setStartPointName(xpPoint.getPointName()); xpOrder.setEndPointName(endXpPoint.getPointName()); // 当前时段 String currentTimeSlot = DateUtils.getCurrentTimeSlot(); xpOrder.setTimeSlot(currentTimeSlot); xpOrder.setCreateTime(LocalDateTime.now()); XpOpenCity openCities = xpOpenCityMapper.selectById(dto.getOpenCityId()); if (openCities == null) { throw new ServiceException("获取城市信息失败", I18nKeyConstant.CITY_NOT_FOUND); } XpOpenCity xpOpenCity = openCities; dto.setOpenCityId(xpOpenCity.getId()); xpOrder.setCityCode(xpOpenCity.getCityCode()); XpRoutePlan xpRoutePlan = xpRoutePlanMapper.selectOne(new LambdaQueryWrapper().eq(XpRoutePlan::getAuditStatus, 2).eq(XpRoutePlan::getCityCode, xpOpenCity.getCityCode())); Integer distance = 0; Integer duration = 0; if (dto.getNavigationSnapshotId() != null && dto.getSelectedRouteId() != null) { String snap = redisService.getCacheObject(CacheConstants.ORDER_MULTI_ROUTE_SNAPSHOT + uid + ":" + dto.getNavigationSnapshotId()); if (snap != null) { MultiRouteSnapshotPayload snapPayload = JSON.parseObject(snap, MultiRouteSnapshotPayload.class); RouteOptionVo opt = snapPayload.getRoutes().stream() .filter(r2 -> dto.getSelectedRouteId().equals(r2.getPathFingerprint())) .findFirst() .orElse(null); if (opt != null && opt.getRoute() != null) { distance = opt.getRoute().getDistance(); duration = opt.getRoute().getDuration(); redisService.setCacheObject("ORDER_NAVIGATION_END_DATA:" + uid, JSON.toJSONString(opt.getRoute()), 24L, TimeUnit.HOURS); if (dto.getSelectedRoutePlanRule() == null) { dto.setSelectedRoutePlanRule(opt.getPrimaryRoutePlanRule()); } } } } if (distance == 0 && duration == 0) { Object cacheObject = redisService.getCacheObject("ORDER_NAVIGATION_END_DATA:" + uid); if (cacheObject != null) { RouteVo routeVo = JSON.parseObject(cacheObject.toString(), RouteVo.class); distance = routeVo.getDistance(); duration = routeVo.getDuration(); } } final Integer planRuleForQueue = dto.getSelectedRoutePlanRule() != null ? dto.getSelectedRoutePlanRule() : (xpRoutePlan != null ? xpRoutePlan.getRoutePlanRule() : null); Long couponRef = dto.getUserCouponId(); xpOrder.setCouponId(couponRef != null && couponRef > 0L ? couponRef : null); // 产生订单 R addOrderR = xpOrderClient.addOrder(xpOrder); if (addOrderR.getCode() != R.SUCCESS) { throw new ServiceException("订单生成失败", I18nKeyConstant.ORDER_CREATE_FAIL); } Long orderId = addOrderR.getData(); xpOrder.setId(orderId); // 静默处理 不推单 if (isSilent) { log.info("订单取消次数达到上限,执行静默处理,不推单。"); return orderId.toString(); } // 最大距离 Object maxPushDistanceObj = redisService.getCacheObject(CacheConstants.MAX_PUSH_DISTANCE); Double maxPushDistance = 0.0; if (maxPushDistanceObj != null) { maxPushDistance = Double.valueOf(maxPushDistanceObj.toString()); } else { maxPushDistance = xpTradingStrategyMapper.selectOne(null).getThreePushDistance(); } // 找出现在所有的队列 通过计算距离 算出目前应该放在那个队列 如果没有符合的新增一个队列 符合后看是否白名单账号 是则直接放在队列前端 Set quoteMatchName = redisService.getQuoteMatchName(CacheConstants.QUEUE_POINT_ID); log.info("当前队列:{}", quoteMatchName); for (String s : quoteMatchName) { // QUEUE_POINT_ID-1-30.233,120.233 String lonLat = s.split("-")[2]; // 和上车点进行判断是否 coupon = null; CompletableFuture> couponFuture = null; Long ucId = routeAndFareDto.getUserCouponId(); if (Long.valueOf(0L).equals(ucId)) { coupon = R.ok(new UserCouponVo()); } else if (ucId != null && ucId > 0L) { coupon = xpAppUserCouponClient.getUserCoupon( new GetUserCouponDto(Long.valueOf(userId), xpPricingCardVo.getTotalPrice(), routeAndFareDto.getOpenCityId(), ucId)); if (coupon.getCode() == 200 && coupon.getData().getStatus() == 2) { throw new ServiceException("优惠券不满足条件", I18nKeyConstant.COUPON_NOT_SATISFY); } } else { couponFuture = CompletableFuture.supplyAsync( () -> xpAppUserCouponClient.getUserCoupon( new GetUserCouponDto(Long.valueOf(userId), xpPricingCardVo.getTotalPrice(), routeAndFareDto.getOpenCityId(), null)), executorService); } R couponResult; if (Long.valueOf(0L).equals(ucId) || (ucId != null && ucId > 0L)) { couponResult = coupon; } else { couponResult = couponFuture.join(); } Integer minOrderDistance; Object minOrderDistanceObj = redisService.getCacheObject(CacheConstants.MIN_ORDER_DISTANCE); if (minOrderDistanceObj != null) { minOrderDistance = Integer.valueOf(minOrderDistanceObj.toString()); } else { minOrderDistance = systemSettingMapper.selectOne(null).getMinOrderDistance(); } if (routeVo.getDistance() < minOrderDistance) { throw new ServiceException("当前上下车点距离过近,建议步行前往", I18nKeyConstant.TO_WORK); } if (couponResult.getCode() != R.SUCCESS) { throw new RuntimeException(couponResult.getMsg()); } UserCouponVo userCouponVo = couponResult.getData(); if (userCouponVo != null) { Double money = userCouponVo.getMoney(); money = new BigDecimal(money).setScale(2, RoundingMode.HALF_UP).doubleValue(); xpPricingCardVo.setCoupon(String.valueOf(money)); if (xpPricingCardVo.getTotalPrice() > money) { xpPricingCardVo.setSystemDiscount(xpPricingCardVo.getTotalPrice() - money); } } RouteAndFareVo routeAndFareVo = new RouteAndFareVo(); routeAndFareVo.setRoute(routeVo); xpPricingCardVo.setTotalPriceStr(new BigDecimal(xpPricingCardVo.getTotalPrice()).setScale(2, RoundingMode.HALF_UP).toString()); xpPricingCardVo.setStartPriceStr(new BigDecimal(xpPricingCardVo.getStartPrice()).setScale(2, RoundingMode.HALF_UP).toString()); xpPricingCardVo.setDistancePriceStr(new BigDecimal(xpPricingCardVo.getDistancePrice()).setScale(2, RoundingMode.HALF_UP).toString()); xpPricingCardVo.setDurationPriceStr(new BigDecimal(xpPricingCardVo.getDurationPrice()).setScale(2, RoundingMode.HALF_UP).toString()); xpPricingCardVo.setSystemDiscountStr(new BigDecimal(xpPricingCardVo.getSystemDiscount()).setScale(2, RoundingMode.HALF_UP).toString()); routeAndFareVo.setXpPricingCard(xpPricingCardVo); FareVo fareVo = new FareVo(); fareVo.setEstimatedDistanceKm(routeVo.getDistance().doubleValue() / 1000); fareVo.setEstimatedDurationMin(routeVo.getDuration() / 60); fareVo.setOriginalFare(xpPricingCardVo.getTotalPrice()); fareVo.setOriginalFareStr(new BigDecimal(fareVo.getOriginalFare()).setScale(2, RoundingMode.HALF_UP).toString()); if (userCouponVo != null) { if (userCouponVo.getMoney() > fareVo.getOriginalFare()) { fareVo.setDiscountAmount(fareVo.getOriginalFare()); fareVo.setFinalFare(0.00); } else { fareVo.setDiscountAmount(userCouponVo.getMoney()); fareVo.setFinalFare(fareVo.getOriginalFare() - fareVo.getDiscountAmount()); } if (fareVo.getFinalFare() > 0) { fareVo.setSystemDiscount(fareVo.getFinalFare()); fareVo.setFinalFare(0.00); } fareVo.setCouponId(userCouponVo.getId()); } else { fareVo.setDiscountAmount(0.00); fareVo.setFinalFare(fareVo.getOriginalFare()); fareVo.setSystemDiscount(fareVo.getFinalFare()); fareVo.setFinalFare(0.00); } Integer waitTime = getWaitTime(routeAndFareDto.getOrigin().getLongitude(), routeAndFareDto.getOrigin().getLatitude()); fareVo.setWaitingTime(waitTime); fareVo.setFinalFareStr(new BigDecimal(fareVo.getFinalFare()).setScale(2, RoundingMode.HALF_UP).toString()); fareVo.setOriginalFare(new BigDecimal(fareVo.getOriginalFare()).setScale(2, RoundingMode.HALF_UP).doubleValue()); fareVo.setSystemDiscount(new BigDecimal(fareVo.getSystemDiscount() == null ? 0 : fareVo.getSystemDiscount()).setScale(2, RoundingMode.HALF_UP).doubleValue()); routeAndFareVo.setFareEstimate(fareVo); routeAndFareVo.getXpPricingCard().setFinalFareStr(fareVo.getFinalFareStr()); fareVo.setId(null); return routeAndFareVo; } @Override public MultiRouteAndFareVo getMultiRouteAndFare(RouteAndFareDto routeAndFareDto, String userId) { Map geocode = gdMapGeocodingUtil.geocode(routeAndFareDto.getOrigin().getLongitude().toString(), routeAndFareDto.getOrigin().getLatitude().toString()); if (null == geocode) { throw new ServiceException("获取城市信息失败", I18nKeyConstant.CITY_NOT_FOUND); } Long cityCode = routeAndFareDto.getOpenCityId(); XpOpenCity openCities = xpOpenCityMapper.selectById(cityCode); if (openCities == null) { throw new ServiceException("获取城市信息失败", I18nKeyConstant.CITY_NOT_FOUND); } routeAndFareDto.setOpenCityId(openCities.getId()); XpPricing xpPricing = this.baseMapper.selectOne(new LambdaQueryWrapper().eq(XpPricing::getCityId, routeAndFareDto.getOpenCityId())); if (xpPricing == null) { throw new ServiceException("当前区域未开通服务", I18nKeyConstant.NO_CURRENT_REGION); } R diagnosisResult = xpMapClient.diagnosisArea(routeAndFareDto); if (diagnosisResult.getCode() != R.SUCCESS) { throw new RuntimeException(diagnosisResult.getMsg()); } if (!Boolean.TRUE.equals(diagnosisResult.getData())) { throw new ServiceException("当前区域未开通服务", I18nKeyConstant.NO_CURRENT_REGION); } List kinds = buildStrategyKindsForPlanning(userId); Map routeById = new LinkedHashMap<>(); Map> strategiesById = new LinkedHashMap<>(); for (RouteStrategyKind kind : kinds) { RouteAndFareDto copy = new RouteAndFareDto(); copy.setOrigin(routeAndFareDto.getOrigin()); copy.setDestination(routeAndFareDto.getDestination()); copy.setOpenCityId(routeAndFareDto.getOpenCityId()); copy.setUserCouponId(routeAndFareDto.getUserCouponId()); copy.setVehicleId(routeAndFareDto.getVehicleId()); copy.setRoutePlanRule(kind.getAmapStrategy()); R r = xpMapClient.getDistanceAndDuration(copy); if (r.getCode() != 200 || r.getData() == null || r.getData().getRouteID() == null) { continue; } String fid = r.getData().getRouteID(); routeById.putIfAbsent(fid, r.getData()); strategiesById.computeIfAbsent(fid, k -> new LinkedHashSet<>()).add(kind); } if (routeById.isEmpty()) { throw new ServiceException("获取导肮信息失败", I18nKeyConstant.PATH_NOT_FOUND); } List options = new ArrayList<>(); for (Map.Entry e : routeById.entrySet()) { String fid = e.getKey(); RouteVo rv = e.getValue(); Set kindsOnRoute = strategiesById.get(fid); RouteStrategyKind primary = RouteStrategyKind.primaryOf(kindsOnRoute); RouteAndFareDto pricingDto = new RouteAndFareDto(); pricingDto.setOrigin(routeAndFareDto.getOrigin()); pricingDto.setDestination(routeAndFareDto.getDestination()); pricingDto.setOpenCityId(routeAndFareDto.getOpenCityId()); pricingDto.setUserCouponId(routeAndFareDto.getUserCouponId()); pricingDto.setVehicleId(routeAndFareDto.getVehicleId()); pricingDto.setRoutePlanRule(primary.getAmapStrategy()); RouteAndFareVo part = pricingForRoute(pricingDto, rv, xpPricing, userId); RouteOptionVo opt = new RouteOptionVo(); opt.setPathFingerprint(fid); List labels = new ArrayList<>(); for (RouteStrategyKind k : Arrays.asList(RouteStrategyKind.SMART, RouteStrategyKind.TIME, RouteStrategyKind.PRICE)) { if (kindsOnRoute.contains(k)) { labels.add(k.getDisplayName()); } } opt.setStrategyLabels(labels); opt.setPrimaryStrategyLabel(primary.getDisplayName()); opt.setPrimaryRoutePlanRule(primary.getAmapStrategy()); opt.setRoute(part.getRoute()); opt.setFareEstimate(part.getFareEstimate()); opt.setXpPricingCard(part.getXpPricingCard()); if (part.getFareEstimate() != null && part.getFareEstimate().getCouponId() == null) { opt.setCouponMessage("当前路线不满足优惠券使用条件"); } options.add(opt); } RouteStrategyKind userPref = resolveUserPreferredKind(userId).orElse(RouteStrategyKind.SMART); options.sort((a, b) -> compareRouteOptionByUserPreference(a, b, userPref, strategiesById)); String snapshotId = UUID.randomUUID().toString().replace("-", ""); long now = System.currentTimeMillis(); MultiRouteSnapshotPayload payload = new MultiRouteSnapshotPayload(); payload.setSnapshotId(snapshotId); payload.setGeneratedAtMillis(now); payload.setUserId(userId); payload.setOpenCityId(routeAndFareDto.getOpenCityId()); payload.setOrigin(routeAndFareDto.getOrigin()); payload.setDestination(routeAndFareDto.getDestination()); payload.setUserCouponId(routeAndFareDto.getUserCouponId()); payload.setRoutes(options); redisService.setCacheObject(CacheConstants.ORDER_MULTI_ROUTE_SNAPSHOT + userId + ":" + snapshotId, JSON.toJSONString(payload), 24L, TimeUnit.HOURS); MultiRouteAndFareVo vo = new MultiRouteAndFareVo(); vo.setSnapshotId(snapshotId); vo.setGeneratedAtMillis(now); vo.setShowRouteSelector(options.size() >= 2); vo.setRoutes(options); vo.setRoutePreferences(listRoutePreferences(routeAndFareDto.getOpenCityId(), userId)); vo.setStaleCheckSeconds(navigationStaleSeconds); vo.setPriceWaveThresholdPercent(navigationPriceWavePercent); RouteOptionVo first = options.get(0); redisService.setCacheObject("ORDER_NAVIGATION_END_DATA:" + userId, JSON.toJSONString(first.getRoute()), 24L, TimeUnit.HOURS); return vo; } @Override public void selectNavigationRoute(SelectNavigationRouteDto dto, String userId) { String json = redisService.getCacheObject(CacheConstants.ORDER_MULTI_ROUTE_SNAPSHOT + userId + ":" + dto.getSnapshotId()); if (json == null) { throw new ServiceException("路线已失效,请重新规划", I18nKeyConstant.PATH_NOT_FOUND); } MultiRouteSnapshotPayload payload = JSON.parseObject(json, MultiRouteSnapshotPayload.class); RouteOptionVo selected = payload.getRoutes().stream() .filter(r -> dto.getPathFingerprint().equals(r.getPathFingerprint())) .findFirst() .orElseThrow(() -> new ServiceException("未找到对应路线", I18nKeyConstant.PATH_NOT_FOUND)); redisService.setCacheObject("ORDER_NAVIGATION_END_DATA:" + userId, JSON.toJSONString(selected.getRoute()), 24L, TimeUnit.HOURS); String id = CacheConstants.ORD + System.currentTimeMillis() + "_" + UUID.randomUUID().toString().substring(0, 8); if (selected.getXpPricingCard() != null) { redisService.setCacheObject(CacheConstants.ORD + id + userId, selected.getXpPricingCard(), 1L, TimeUnit.DAYS); } if (selected.getFareEstimate() != null) { selected.getFareEstimate().setId(id); } } @Override public RouteStaleCheckVo checkRouteStale(RouteStaleCheckDto dto, String userId) { RouteStaleCheckVo out = new RouteStaleCheckVo(); out.setNeedRefreshNotice(false); long elapsed = System.currentTimeMillis() - dto.getClientSnapshotMillis(); if (elapsed <= navigationStaleSeconds * 1000L) { return out; } String snapJson = redisService.getCacheObject(CacheConstants.ORDER_MULTI_ROUTE_SNAPSHOT + userId + ":" + dto.getSnapshotId()); if (snapJson == null) { out.setNeedRefreshNotice(true); out.setNoticeMessage("路况已变,价格已更新"); out.setReplacedSnapshot(true); return out; } MultiRouteSnapshotPayload oldSnap = JSON.parseObject(snapJson, MultiRouteSnapshotPayload.class); RouteAndFareDto refreshDto = new RouteAndFareDto(); refreshDto.setOrigin(oldSnap.getOrigin()); refreshDto.setDestination(oldSnap.getDestination()); refreshDto.setOpenCityId(oldSnap.getOpenCityId()); refreshDto.setUserCouponId(oldSnap.getUserCouponId()); MultiRouteAndFareVo fresh = getMultiRouteAndFare(refreshDto, userId); out.setLatestMultiRoute(fresh); if (dto.getClientFinalFare() == null || fresh.getRoutes() == null) { return out; } String fp = dto.getPathFingerprint(); RouteOptionVo newOpt = fresh.getRoutes().stream() .filter(r -> fp == null || fp.equals(r.getPathFingerprint())) .findFirst() .orElse(fresh.getRoutes().get(0)); Double nf = newOpt.getFareEstimate() != null ? newOpt.getFareEstimate().getFinalFare() : null; if (nf == null) { return out; } double diff = Math.abs(nf - dto.getClientFinalFare()) / Math.max(1e-6, dto.getClientFinalFare()) * 100.0; if (diff > navigationPriceWavePercent) { out.setNeedRefreshNotice(true); out.setNoticeMessage("路况已变,价格已更新"); out.setReplacedSnapshot(true); } return out; } @Override public List listRoutePreferences(Long openCityId, String userId) { Set enabledCodes = parseEnabledPreferenceCodes(); Optional savedPref = resolveUserPreferredKind(userId); List list = new ArrayList<>(); int i = 0; for (RouteStrategyKind k : Arrays.asList(RouteStrategyKind.SMART, RouteStrategyKind.TIME, RouteStrategyKind.PRICE)) { if (!enabledCodes.contains(k.name())) { continue; } RoutePreferenceItemVo v = new RoutePreferenceItemVo(); v.setCode(k.name()); v.setName(k.getDisplayName()); v.setEnabled(true); v.setSortOrder(i++); v.setAmapStrategy(k.getAmapStrategy()); v.setSelected(savedPref.isPresent() && k == savedPref.get()); list.add(v); } return list; } @Override public void saveUserRoutePreference(String code, String userId) { RouteStrategyKind k; try { k = RouteStrategyKind.valueOf(code.trim().toUpperCase(Locale.ROOT)); } catch (Exception e) { throw new ServiceException("无效的路线偏好编码", I18nKeyConstant.PATH_NOT_FOUND); } redisService.setCacheObject(CacheConstants.USER_ROUTE_PREFERENCE + userId, String.valueOf(k.getAmapStrategy()), 365L, TimeUnit.DAYS); } private Set parseEnabledPreferenceCodes() { Set set = new LinkedHashSet<>(); for (String part : navigationPreferenceEnabledCodes.split(",")) { String p = part.trim().toUpperCase(Locale.ROOT); if (!p.isEmpty()) { set.add(p); } } if (set.isEmpty()) { set.add(RouteStrategyKind.SMART.name()); set.add(RouteStrategyKind.TIME.name()); set.add(RouteStrategyKind.PRICE.name()); } return set; } /** * 用户偏好策略优先请求,其余策略仍请求以便合并去重。 */ private List buildStrategyKindsForPlanning(String userId) { List all = new ArrayList<>(); Set enabled = parseEnabledPreferenceCodes(); for (RouteStrategyKind k : Arrays.asList(RouteStrategyKind.SMART, RouteStrategyKind.TIME, RouteStrategyKind.PRICE)) { if (enabled.contains(k.name())) { all.add(k); } } if (all.isEmpty()) { all.add(RouteStrategyKind.SMART); } Optional pref = resolveUserPreferredKind(userId); if (!pref.isPresent()) { return all; } RouteStrategyKind p = pref.get(); if (!all.contains(p)) { return all; } List ordered = new ArrayList<>(); ordered.add(p); for (RouteStrategyKind k : all) { if (k != p) { ordered.add(k); } } return ordered; } private Optional resolveUserPreferredKind(String userId) { if (userId == null || userId.isEmpty()) { return Optional.empty(); } Object raw = redisService.getCacheObject(CacheConstants.USER_ROUTE_PREFERENCE + userId); if (raw == null) { return Optional.empty(); } try { int strategy = Integer.parseInt(raw.toString()); return RouteStrategyKind.fromAmapStrategy(strategy); } catch (Exception e) { return Optional.empty(); } } private int compareRouteOptionByUserPreference(RouteOptionVo a, RouteOptionVo b, RouteStrategyKind pref, Map> strategiesById) { Set sa = strategiesById.getOrDefault(a.getPathFingerprint(), Collections.emptySet()); Set sb = strategiesById.getOrDefault(b.getPathFingerprint(), Collections.emptySet()); boolean aHas = sa.contains(pref); boolean bHas = sb.contains(pref); if (aHas != bHas) { return bHas ? 1 : -1; } boolean aPrimary = a.getPrimaryRoutePlanRule() != null && a.getPrimaryRoutePlanRule() == pref.getAmapStrategy(); boolean bPrimary = b.getPrimaryRoutePlanRule() != null && b.getPrimaryRoutePlanRule() == pref.getAmapStrategy(); if (aPrimary != bPrimary) { return bPrimary ? 1 : -1; } int da = a.getRoute() != null && a.getRoute().getDuration() != null ? a.getRoute().getDuration() : Integer.MAX_VALUE; int db = b.getRoute() != null && b.getRoute().getDuration() != null ? b.getRoute().getDuration() : Integer.MAX_VALUE; return Integer.compare(da, db); } @Override public QueueStatusVo getQueueStatus(String orderId, String uid) { QueueStatusVo queueStatusVo = new QueueStatusVo(); try { // 从订单服务获取订单信息,获取queueName R orderR = xpOrderClient.queryOrderById(Long.valueOf(orderId)); if (orderR.getCode() != R.SUCCESS || orderR.getData() == null) { throw new ServiceException("订单不存在"); } XpOrder xpOrder = orderR.getData(); String queueName = xpOrder.getQueueName(); if (StringUtils.isBlank(queueName)) { // 订单不在排队中 queueStatusVo.setStatus(1); queueStatusVo.setCurrentPosition(0); queueStatusVo.setTotalCount(0); queueStatusVo.setEstimatedWaitTime(0); return queueStatusVo; } // 从Redis获取队列数据 List queueItems = redisService.getCacheList(queueName); if (queueItems == null || queueItems.isEmpty()) { // 队列为空,说明已分配车辆 queueStatusVo.setStatus(1); queueStatusVo.setCurrentPosition(0); queueStatusVo.setTotalCount(0); queueStatusVo.setEstimatedWaitTime(0); return queueStatusVo; } // 遍历队列,找到当前订单的位置 int currentPosition = 0; int totalCount = queueItems.size(); for (int i = 0; i < queueItems.size(); i++) { String item = queueItems.get(i); if (item.startsWith(orderId + "_")) { currentPosition = i + 1; // 位置从1开始 break; } } Integer waitTimeTimeSlot = getWaitTimeTimeSlot(xpOrder.getStartLon(), xpOrder.getStartLat(), xpOrder.getTimeSlot()); queueStatusVo.setStatus(0); // 0-排队中 queueStatusVo.setCurrentPosition(currentPosition); queueStatusVo.setTotalCount(totalCount); queueStatusVo.setEstimatedWaitTime((waitTimeTimeSlot*currentPosition)/60); } catch (Exception e) { log.error("查询排队状态失败: {}", e.getMessage(), e); // 异常情况下返回默认值 queueStatusVo.setStatus(0); queueStatusVo.setCurrentPosition(0); queueStatusVo.setTotalCount(0); queueStatusVo.setEstimatedWaitTime(0); } return queueStatusVo; } }