package com.xiaopeng.itinerary.service.impl; import cn.hutool.core.collection.ListUtil; import cn.hutool.core.lang.Snowflake; 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.RouteAndFareDto; import com.xiaopeng.api.dto.map.RouteStaleCheckDto; import com.xiaopeng.api.dto.map.SelectNavigationRouteDto; import com.xiaopeng.api.enums.RouteStrategyKind; import com.xiaopeng.api.feignClient.account.UserRoutePlanRuleClient; 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.operation.XpTargetedServiceClient; import com.xiaopeng.api.feignClient.operation.XpTradingStrategyClient; import com.xiaopeng.api.feignClient.order.XpOrderClient; import com.xiaopeng.api.feignClient.pushMessage.XpPushMessageClient; import com.xiaopeng.api.feignClient.vehicleConvoy.XpVehicleClient; import com.xiaopeng.api.model.account.XpUserRoutePlanRule; 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.*; 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.mapper.*; import com.xiaopeng.itinerary.navigation.MultiRouteSnapshotPayload; import com.xiaopeng.itinerary.service.BootCommonService; import com.xiaopeng.itinerary.service.IXpGeoQueryEntityService; import com.xiaopeng.itinerary.service.XpPointService; import com.xiaopeng.itinerary.service.XpPricingService; 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 com.xiaopeng.itinerary.vo.QueueStatusVo; import com.xiaopeng.itinerary.vo.RedisQueuePointIdVo; 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 BootCommonService bootCommonService; @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 XpVehicleClient xpVehicleClient; @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; @Resource private UserRoutePlanRuleClient userRoutePlanRuleClient; @Resource private XpTargetedServiceClient xpTargetedServiceClient; @Resource(name = "hutoolSnowflake") private Snowflake snowflake; @Value("${navigation.route.stale-seconds:60}") private int navigationStaleSeconds; @Value("${navigation.route.price-wave-percent:5}") private double navigationPriceWavePercent; /** * 后台可配置的偏好项编码,逗号分隔,如 SMART,TIME,PRICE,AVOID_CONGESTION */ @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); } public XpPricing getInfo(Long cityId,String vehicleModel){ LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(XpPricing::getDeleted, 0); queryWrapper.eq(XpPricing::getCityId, cityId); if(vehicleModel != null){ queryWrapper.eq(XpPricing::getVehicleModel, vehicleModel); } return super.getOne(queryWrapper); } @Override public List 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); // } ArrayList routeAndFareVos = new ArrayList<>(3); // 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); } routeAndFareDto.setOpenCityId(openCities.getId()); //根据订单获取车型 获取定价策略 XpPricing xpPricing = bootCommonService.getXpPricing(routeAndFareDto.getOpenCityId(), routeAndFareDto.getOrderId(), null); if (xpPricing == null) { throw new ServiceException("当前区域未开通服务", I18nKeyConstant.NO_CURRENT_REGION); } routeAndFareDto.setOpenCityId(openCities.getId()); List xpRoutePlan = xpRoutePlanMapper.selectList(new LambdaQueryWrapper().eq(XpRoutePlan::getAuditStatus, 2).eq(XpRoutePlan::getOpenCityId, cityCode)); // 找出用户偏好 Object cacheObject = redisService.getCacheObject(CacheConstants.USER_ROUTE_PREFERENCE + userId); for (XpRoutePlan routePlan : xpRoutePlan) { if (xpRoutePlan != null) { routeAndFareDto.setRoutePlanRule(routePlan.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 // } // } // 算距离 R distanceAndDuration = xpMapClient.getDistanceAndDuration(routeAndFareDto); if (distanceAndDuration.getCode() != 200) { // throw new ServiceException("获取导肮信息失败", I18nKeyConstant.PATH_NOT_FOUND); continue; } 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); if(cacheObject!=null){ if (cacheObject.toString().equals(routePlan.getRoutePlanRule().toString())) { routeAndFareVo.setPreference(1); } } routeAndFareVo.setRoutePlanName(routePlan.getPlanName()); routeAndFareVo.setAmapStrategy(routePlan.getRoutePlanRule()); routeAndFareVos.add(routeAndFareVo); } if(routeAndFareVos.isEmpty()){ throw new ServiceException("获取导肮信息失败", I18nKeyConstant.PATH_NOT_FOUND); } XpSystemSetting xpSystemSetting = systemSettingMapper.selectOne(null); if(null != xpSystemSetting){ routeAndFareVos.forEach(routeAndFareVo -> { routeAndFareVo.setOperateFree(xpSystemSetting.getOperateFree()); }); } return routeAndFareVos; } /** * 获取等待时间 * @param lon * @param lat * @return */ @Override public Integer getWaitTime(double lon, double lat) { List entities = geoQueryEntityService.findByLocationWithin500MetersAndLastWeekAndStatusGreater( lon, lat, getCurrentTimeSlot() ); log.info("等待时间查询结果:{}", entities.toString()); 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); } // todo 需要判断用户是否白名单,且白名单当天最大订单量 // 判断当天取消的次数 后台配置 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.setXpElectronicFenceAreaId(dto.getXpElectronicFenceAreaId()); xpOrder.setStartPointId(dto.getStartPointId()); xpOrder.setStartLon(xpPoint.getPointLon()); xpOrder.setStartLat(xpPoint.getPointLat()); xpOrder.setBoardingLon(xpPoint.getPointLon()); xpOrder.setBoardingLat(xpPoint.getPointLat()); xpOrder.setBoardingAddress(xpPoint.getPointAddress()); xpOrder.setStartAddress(xpPoint.getPointAddress()); xpOrder.setStartPointName(xpPoint.getPointName()); xpOrder.setEndPointId(dto.getEndPointId()); xpOrder.setEndLon(endXpPoint.getPointLon()); xpOrder.setEndLat(endXpPoint.getPointLat()); xpOrder.setEndAddress(endXpPoint.getPointAddress()); xpOrder.setEndPointName(endXpPoint.getPointName()); xpOrder.setXpuStartTaskId(snowflake.nextId() + ""); xpOrder.setXpuEndTaskId(snowflake.nextId() + ""); // 当前时段 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()); XpUserRoutePlanRule xpUserRoutePlanRule = userRoutePlanRuleClient.getXpUserRoutePlanRule(uid).getData(); XpRoutePlan xpRoutePlan = null; if(null != xpUserRoutePlanRule){ xpRoutePlan = xpRoutePlanMapper.selectOne(new LambdaQueryWrapper().eq(XpRoutePlan::getAuditStatus, 2) .eq(XpRoutePlan::getCityCode, xpOpenCity.getCityCode()).eq(XpRoutePlan::getRoutePlanRule, xpUserRoutePlanRule.getRoutePlanRule())); xpRoutePlanMapper.selectById(xpUserRoutePlanRule.getRoutePlanRule()); }else{ xpRoutePlan = xpRoutePlanMapper.selectOne(new LambdaQueryWrapper().eq(XpRoutePlan::getAuditStatus, 2) .eq(XpRoutePlan::getCityCode, xpOpenCity.getCityCode()).last(" limit 1")); } 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); int targetedVal = 0; R> targetedServiceUserR = xpTargetedServiceClient.findListByUserIdAndCityCode(xpOrder.getAppUserId(), xpOrder.getCityCode()); if(targetedServiceUserR.getCode() == R.SUCCESS && !targetedServiceUserR.getData().isEmpty()){ targetedVal = targetedServiceUserR.getData().get(0).getWeight(); } //String key = CacheConstants.QUEUE_POINT_ID + "-" + xpPoint.getId() + "-" + xpPoint.getPointLon() + "," + xpPoint.getPointLat(); String redis_key = redisService.redisKey_QUEUE_POINT_ID(xpPoint.getId(), xpPoint.getPointLon(), xpPoint.getPointLat()); //orderId + "_" + xpPoint.getPointLon() + "," + xpPoint.getPointLat() + "_0" + "_" + uid + "_" + xpOrder.getOrderNumber() + "_" + distance + "_" + duration + "_" + planRuleForQueue+"_" + targetedVal; String redis_value = redisService.redisValue_QUEUE_POINT_ID(orderId.toString(), xpPoint.getPointLon().toString(), xpPoint.getPointLat().toString(), uid, xpOrder.getOrderNumber(), distance.toString(), duration.toString(), planRuleForQueue == null ? null : planRuleForQueue.toString(),targetedVal); log.info("当前队列:{}", quoteMatchName); RedisQueuePointIdVo redisQueuePointIdVo = null; for (String s : quoteMatchName) { // QUEUE_POINT_ID-1-30.233,120.233 String lonLat = s.split("-")[2]; // 和上车点进行判断是否0){ //todo yyy 这里添加数据需要优化 需要redis 加锁 List cacheList = redisService.getCacheList(s); cacheList.add(redis_value); List ll = new ArrayList<>(); for (String string : cacheList) { String[] split = string.split("_"); redisQueuePointIdVo = new RedisQueuePointIdVo(); redisQueuePointIdVo.setOrderId(split[0]); redisQueuePointIdVo.setPointLon(split[1]); redisQueuePointIdVo.setPointLat(split[2]); redisQueuePointIdVo.setUid(split[3]); redisQueuePointIdVo.setOrderNumber(split[4]); redisQueuePointIdVo.setDistance(split[5]); redisQueuePointIdVo.setDuration(split[6]); redisQueuePointIdVo.setPlanRuleForQueue(split[7]); if(split.length>8){ redisQueuePointIdVo.setTargetedVal(Integer.valueOf(split[8])); }else{ redisQueuePointIdVo.setTargetedVal(0); } ll.add(redisQueuePointIdVo); } ll.sort((o1, o2) -> o2.getTargetedVal() - o1.getTargetedVal()); List redis_values = new ArrayList<>(); for (RedisQueuePointIdVo vo : ll) { String redis_value2 = redisService.redisValue_QUEUE_POINT_ID(vo.getOrderId(), vo.getPointLon(), vo.getPointLat(), vo.getUid(), vo.getOrderNumber(), vo.getDistance(), vo.getDuration(), vo.getPlanRuleForQueue(),vo.getTargetedVal()); redis_values.add(redis_value2); } redisService.setCacheList(s, redis_values); }else{ if (isPriorityDispatch) { // 头部 value 订单_经纬度_状态_用户id_订单编号_距离_时间 redisService.addToHead(s, redis_value); log.info("添加到队列头部:{} {}", s, redis_value); } else { // 尾部 redisService.addToTail(s, redis_value); log.info("添加到队列尾部:{} {}", s, redis_value); } } xpOrder.setQueueName(s); xpOrderClient.updateOrder(xpOrder); // 存储mongodb 数据 XpGeoQueryEntity xpGeoQueryEntity = new XpGeoQueryEntity(); xpGeoQueryEntity.setOrderId(orderId.toString()); xpGeoQueryEntity.setLocation(new GeoJsonPoint(xpPoint.getPointLon(), xpPoint.getPointLat())); // 计算2个时间的秒数 xpGeoQueryEntity.setTime(currentTimeSlot); xpGeoQueryEntity.setStatus(0); xpGeoQueryEntity.setTimestamp(System.currentTimeMillis()); geoQueryEntityService.save(xpGeoQueryEntity); return orderId.toString(); } } redisService.setCacheList(redis_key, ListUtil.toList(redis_value) ); log.info("订单推单:{} {}", redis_key, redisService.getCacheList(redis_key)); xpOrder.setQueueName(redis_key); xpOrderClient.updateOrder(xpOrder); // 存储mongodb 数据 XpGeoQueryEntity xpGeoQueryEntity = new XpGeoQueryEntity(); xpGeoQueryEntity.setOrderId(orderId.toString()); xpGeoQueryEntity.setLocation(new GeoJsonPoint(xpPoint.getPointLon(), xpPoint.getPointLat())); // 计算2个时间的秒数 xpGeoQueryEntity.setTime(currentTimeSlot); xpGeoQueryEntity.setStatus(0); xpGeoQueryEntity.setTimestamp(System.currentTimeMillis()); geoQueryEntityService.save(xpGeoQueryEntity); return orderId.toString(); } @Override public Boolean isInOperationTime(String operationStrategy) { JSONArray jsonArray = JSONArray.parseArray(operationStrategy); Integer holidayToday = holidayUtil.isHolidayToday(); for (Object o : jsonArray) { JSONObject jsonObject = JSONObject.parseObject(o.toString()); String type = jsonObject.getString("type"); if (type.equals(holidayToday.toString())) { JSONArray time = jsonObject.getJSONArray("time"); // 判断当前时间 是否在time时间段中 for (Object object : time) { String[] timeArray = object.toString().split("-"); String startTime = timeArray[0]; String endTime = timeArray[1]; boolean currentTimeInRange = DateUtils.isCurrentTimeInRange(startTime, endTime); if (currentTimeInRange) { return true; } } } else if (type.equals("3")) { // 判断是什么类型 0每天 1-7 想起 String typeStr = jsonObject.getString("typeStr"); // 查看今天周几 Integer week = DateUtils.getWeek(); // 包含每天 或者 包含当天 if (typeStr.contains("0") || typeStr.contains(week.toString())) { JSONArray time = jsonObject.getJSONArray("time"); // 判断当前时间 是否在time时间段中 for (Object object : time) { String[] timeArray = object.toString().split("-"); String startTime = timeArray[0]; String endTime = timeArray[1]; boolean currentTimeInRange = DateUtils.isCurrentTimeInRange(startTime, endTime); if (currentTimeInRange) { return true; } } } } } return false; } /** * 单次路径计价(不含区域诊断、不写导航缓存),供多路线合并调用。 */ private RouteAndFareVo pricingForRoute(RouteAndFareDto routeAndFareDto, RouteVo routeVo, XpPricing xpPricing, String userId) { 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() ); } 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), 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); } StrategyPlanningConfig planningConfig = buildStrategyKindsForPlanning(userId, routeAndFareDto.getOpenCityId()); List kinds = planningConfig.getKinds(); 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 = RouteStrategyKind.orderedOf(kindsOnRoute).stream() .map(kind -> resolveStrategyDisplayName(kind, planningConfig.getDisplayNames())) .collect(Collectors.toList()); opt.setStrategyLabels(labels); opt.setPrimaryStrategyLabel(resolveStrategyDisplayName(primary, planningConfig.getDisplayNames())); 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); XpTradingStrategyDetailVO strategyDetailVO = xpTradingStrategyClient.queryById().getData(); redisService.setCacheObject(CacheConstants.ORDER_MULTI_ROUTE_SNAPSHOT + userId + ":" + snapshotId, JSON.toJSONString(payload), Long.valueOf(strategyDetailVO.getMaxStandingTiming()), TimeUnit.SECONDS); 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; } XpTradingStrategyClient xpTradingStrategyClient; @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); Map displayNames = buildStrategyKindsForPlanning(userId, openCityId).getDisplayNames(); List list = new ArrayList<>(); int i = 0; for (RouteStrategyKind k : RouteStrategyKind.orderedValues()) { if (!enabledCodes.contains(k.name())) { continue; } RoutePreferenceItemVo v = new RoutePreferenceItemVo(); v.setCode(k.name()); v.setName(resolveStrategyDisplayName(k, displayNames)); 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()) { for (RouteStrategyKind kind : RouteStrategyKind.defaultPreferenceKinds()) { set.add(kind.name()); } } return set; } /** * 用户偏好策略优先请求,其余策略仍请求以便合并去重。 */ private StrategyPlanningConfig buildStrategyKindsForPlanning(String userId, Long openCityId) { Set enabled = parseEnabledPreferenceCodes(); List xpRoutePlans = xpRoutePlanMapper.selectList(new LambdaQueryWrapper().eq(XpRoutePlan::getOpenCityId, openCityId)); Map displayNames = new LinkedHashMap<>(); List configuredKinds = new ArrayList<>(); if (xpRoutePlans.isEmpty()) { configuredKinds.addAll(RouteStrategyKind.defaultPreferenceKinds()); } else { for (XpRoutePlan xpRoutePlan : xpRoutePlans) { Integer strategy = xpRoutePlan.getRoutePlanRule(); if (strategy == null) { continue; } RouteStrategyKind.fromAmapStrategy(strategy).ifPresent(kind -> { configuredKinds.add(kind); if (StringUtils.isNotBlank(xpRoutePlan.getPlanName())) { displayNames.putIfAbsent(kind, xpRoutePlan.getPlanName().trim()); } }); } } List all = new ArrayList<>(); for (RouteStrategyKind k : configuredKinds) { if (enabled.contains(k.name())) { addIfAbsent(all, k); } } if (all.isEmpty()) { all.add(resolveDefaultEnabledKind(enabled)); } Optional pref = resolveUserPreferredKind(userId); if (!pref.isPresent()) { return new StrategyPlanningConfig(all, displayNames); } RouteStrategyKind p = pref.get(); if (!all.contains(p)) { return new StrategyPlanningConfig(all, displayNames); } List ordered = new ArrayList<>(); ordered.add(p); for (RouteStrategyKind k : all) { if (k != p) { ordered.add(k); } } return new StrategyPlanningConfig(ordered, displayNames); } private String resolveStrategyDisplayName(RouteStrategyKind kind, Map displayNames) { String configuredName = displayNames.get(kind); return StringUtils.isNotBlank(configuredName) ? configuredName : kind.getDisplayName(); } private void addIfAbsent(List kinds, RouteStrategyKind kind) { if (!kinds.contains(kind)) { kinds.add(kind); } } private RouteStrategyKind resolveDefaultEnabledKind(Set enabled) { for (RouteStrategyKind kind : RouteStrategyKind.defaultPreferenceKinds()) { if (enabled.contains(kind.name())) { return kind; } } for (RouteStrategyKind kind : RouteStrategyKind.orderedValues()) { if (enabled.contains(kind.name())) { return kind; } } return RouteStrategyKind.SMART; } private static final class StrategyPlanningConfig { private final List kinds; private final Map displayNames; private StrategyPlanningConfig(List kinds, Map displayNames) { this.kinds = kinds; this.displayNames = displayNames; } private List getKinds() { return kinds; } private Map getDisplayNames() { return displayNames; } } 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; } }