package com.xiaopeng.itinerary.service.impl;
import cn.hutool.core.collection.CollUtil;
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.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xiaopeng.api.dto.account.GetUserCouponDto;
import com.xiaopeng.api.dto.account.UcUserInfo;
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.UserResourcesClient;
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.time.format.DateTimeFormatter;
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 XpTradingStrategyPushMapper xpTradingStrategyPushMapper;
@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
private XpTradingStrategyClient xpTradingStrategyClient;
@Resource
private UserResourcesClient userResourcesClient;
@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) {
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) {
//校验白名单用户
// UcUserInfo userInfo = userResourcesClient.getBaseUserByUid(uid);
// XpWhitelist xpWhitelist = xpWhitelistMapper.selectOne(new QueryWrapper().eq("account", userInfo.getMobile())
// .eq("status", 1).last(" and (DATE(expire_time) < DATE(NOW()) or expire_time is null)"));
// if(null == xpWhitelist){
// throw new ServiceException("未授权用户,不能使用该功能", I18nKeyConstant.NO_PERMISSION);
// }
//判断最大下单数
// if(null != xpWhitelist.getMaxOrderNum() && xpWhitelist.getMaxOrderNum()>0){
// Long count = xpOrderClient.getUserOrderNum(Long.valueOf(uid)).getData();
// if(xpWhitelist.getMaxOrderNum() <= count){
// throw new ServiceException("已达到最大下单数量,不能使用该功能", I18nKeyConstant.MAX_ORDER_NUM);
// }
// }
// 是否在运营区域时间内
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 ServiceException("有正在进行的订单");
}
// 获取起点经纬度
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);
// }
//判断是否在白名单且优先派单
boolean isPriorityDispatch = false;
UcUserInfo ucUserInfo = userResourcesClient.getBaseUserByUid(uid);
if (null != ucUserInfo) {
Long count = xpWhitelistMapper.selectCount(new LambdaQueryWrapper().eq(XpWhitelist::getAccount, ucUserInfo.getMobile()).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 {
XpTradingStrategy xpTradingStrategy = xpTradingStrategyMapper.selectOne(null);
List xpTradingStrategyPushes = xpTradingStrategyPushMapper.selectList(new QueryWrapper().eq("trading_strategy_id", xpTradingStrategy.getId()));
long now = Long.parseLong(LocalDateTime.now().format(DateTimeFormatter.ofPattern("HHmm")));
XpTradingStrategyPush xpTradingStrategyPush = xpTradingStrategyPushes.stream().filter(item -> {
long start = Long.parseLong(item.getStartTime().replace(":", ""));
long end = Long.parseLong(item.getEndTime().replace(":", ""));
return now >= start && now < end;
}).findFirst().orElse(null);
maxPushDistance = null != xpTradingStrategyPush ? xpTradingStrategyPush.getFivePushDistance() : xpTradingStrategy.getFivePushDistance();
}
//获取定向服务配置
R> targetedServiceUserR = xpTargetedServiceClient.findListByUserIdAndCityCode(xpOrder.getAppUserId(), xpOrder.getCityCode());
if(targetedServiceUserR.getCode() == R.SUCCESS && CollUtil.isNotEmpty(targetedServiceUserR.getData()) ){
XpTargetedServiceVehicleVo xpTargetedServiceVehicleVo = targetedServiceUserR.getData().get(0);
return addSpOrderPush(xpOrder, xpPoint, xpRoutePlan, maxPushDistance, xpTargetedServiceVehicleVo, orderId, currentTimeSlot, distance, duration, uid);
}else{
return addOrderPush(xpOrder, xpPoint, xpRoutePlan, maxPushDistance, isPriorityDispatch, orderId, currentTimeSlot, distance, duration, uid);
}
}
/**
* 构建普通订单推送数据
* @param xpOrder
* @param xpPoint
* @param xpRoutePlan
* @param maxPushDistance
* @param isPriorityDispatch
* @param orderId
* @param currentTimeSlot
* @param distance
* @param duration
* @param uid
* @return
*/
private String addOrderPush(XpOrder xpOrder, XpPoint xpPoint, XpRoutePlan xpRoutePlan, double maxPushDistance,
Boolean isPriorityDispatch, Long orderId, String currentTimeSlot, Integer distance, Integer duration, String uid){
// 找出现在所有的队列 通过计算距离 算出目前应该放在那个队列 如果没有符合的新增一个队列 符合后看是否白名单账号 是则直接放在队列前端
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];
// 和上车点进行判断是否 quoteMatchName = redisService.getQuoteMatchName(CacheConstants.SP_QUEUE_POINT_ID);
log.info("当前队列:{}", quoteMatchName);
for (String s : quoteMatchName) {
// QUEUE_POINT_ID-1-30.233,120.233
String lonLat = s.split("-")[2];
// 和上车点进行判断是否 cacheList = redisService.getCacheList(s);
List ll = new ArrayList<>();
for (String string : cacheList) {
String[] split = string.split("_");
RedisQueuePointIdVo redisQueuePointIdVo = new RedisQueuePointIdVo();
redisQueuePointIdVo.setOrderId(split[0]);
redisQueuePointIdVo.setPointLonLat(split[1]);
redisQueuePointIdVo.setPushStatus(split[2]);
redisQueuePointIdVo.setUid(split[3]);
redisQueuePointIdVo.setOrderNumber(split[4]);
redisQueuePointIdVo.setDistance(split[5]);
redisQueuePointIdVo.setDuration(split[6]);
redisQueuePointIdVo.setPlanRuleForQueue(split[7]);
redisQueuePointIdVo.setTargetedVal(Integer.valueOf(split[8]));
ll.add(redisQueuePointIdVo);
}
RedisQueuePointIdVo redisQueuePointIdVo = new RedisQueuePointIdVo();
redisQueuePointIdVo.setOrderId(orderId.toString());
redisQueuePointIdVo.setPointLonLat(xpPoint.getPointLon() + "," + xpPoint.getPointLat());
redisQueuePointIdVo.setPushStatus("0");
redisQueuePointIdVo.setUid(uid);
redisQueuePointIdVo.setOrderNumber(xpOrder.getOrderNumber());
redisQueuePointIdVo.setDistance(distance.toString());
redisQueuePointIdVo.setDuration(duration.toString());
redisQueuePointIdVo.setPlanRuleForQueue(xpRoutePlan.getRoutePlanRule().toString());
redisQueuePointIdVo.setTargetedVal(xpTargetedServiceVehicleVo.getWeight());
ll.add(redisQueuePointIdVo);
//排序
ll.sort((o1, o2) -> o2.getTargetedVal() - o1.getTargetedVal());
List collect = ll.stream().map(item -> {
String value = item.getOrderId() + "_" + item.getPointLonLat() + "_" + item.getPushStatus() + "_" + item.getUid() + "_" + item.getOrderNumber()
+ "_" + item.getDistance() + "_" + item.getDuration() + "_" + item.getPlanRuleForQueue() + "_" + item.getTargetedVal();
return value;
}).collect(Collectors.toList());
redisService.setCacheList(s, cacheList);
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();
}
}
String key = CacheConstants.SP_QUEUE_POINT_ID + "-" + xpPoint.getId() + "-" + xpPoint.getPointLon() + "," + xpPoint.getPointLat();
if(xpRoutePlan==null){
redisService.setCacheList(key, Arrays.asList(orderId + "_" + xpPoint.getPointLon() + "," + xpPoint.getPointLat() + "_0" + "_" + uid + "_" + xpOrder.getOrderNumber() + "_" + distance + "_" + duration + "_" + null + "_" + xpTargetedServiceVehicleVo.getWeight()));
}else {
redisService.setCacheList(key, Arrays.asList(orderId + "_" + xpPoint.getPointLon() + "," + xpPoint.getPointLat() + "_0" + "_" + uid + "_" + xpOrder.getOrderNumber() + "_" + distance + "_" + duration + "_" + xpRoutePlan.getRoutePlanRule() + "_" + xpTargetedServiceVehicleVo.getWeight()));
}
log.info("订单推单:{} {}", key, redisService.getCacheList(key));
xpOrder.setQueueName(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());
// routeAndFareVo.setTrafficLights();//交通信号灯 todo 20260508 红绿灯功能还没有
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 = this.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.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);
}
XpSystemSetting xpSystemSetting = systemSettingMapper.selectOne(null);
Integer operateFree = 0 ;
if(null != xpSystemSetting) {
operateFree = xpSystemSetting.getOperateFree();
}
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.setPrimaryRoutePlanName(primary.getDisplayName());
opt.setRoute(part.getRoute());
opt.setFareEstimate(part.getFareEstimate());
opt.setXpPricingCard(part.getXpPricingCard());
if (part.getFareEstimate() != null && part.getFareEstimate().getCouponId() == null) {
opt.setCouponMessage("当前路线不满足优惠券使用条件");
}
opt.setOperateFree(operateFree);
options.add(opt);
}
// 根据 pathFingerprint 去重,保留第一次出现的路线
// options = new ArrayList<>(options.stream()
// .collect(Collectors.toMap(
// RouteOptionVo::getPathFingerprint,
// option -> option,
// (existing, replacement) -> existing,
// LinkedHashMap::new
// )).values());
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);
R strategyDetailVOR = xpTradingStrategyClient.queryById();
XpTradingStrategyDetailVO strategyDetailVO = strategyDetailVOR.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);
vo.setOperateFree(operateFree);
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 routePlans = findRoutePlans(openCityId);
List list = new ArrayList<>();
if(CollUtil.isNotEmpty(routePlans)){
int i = 0;
for (XpRoutePlan routePlan : routePlans) {
RoutePreferenceItemVo v = new RoutePreferenceItemVo();
RouteStrategyKind strategy = RouteStrategyKind.getStrategy(routePlan.getRoutePlanRule());
if(strategy != null){
v.setCode(strategy.name());
}
v.setName(routePlan.getPlanName());
v.setEnabled(true);
v.setSortOrder(i++);
v.setAmapStrategy(routePlan.getRoutePlanRule());
v.setSelected(savedPref.isPresent() && strategy == 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 List findRoutePlans (Long openCityId){
return xpRoutePlanMapper.selectList(new LambdaQueryWrapper().eq(XpRoutePlan::getOpenCityId, openCityId)
.eq(XpRoutePlan::getAuditStatus, 2));
}
/**
* 用户偏好策略优先请求,其余策略仍请求以便合并去重。
*/
private StrategyPlanningConfig buildStrategyKindsForPlanning(String userId, Long openCityId) {
List xpRoutePlans = xpRoutePlanMapper.selectList(new LambdaQueryWrapper().eq(XpRoutePlan::getOpenCityId, openCityId)
.eq(XpRoutePlan::getAuditStatus, 2));
Map displayNames = new LinkedHashMap<>();
List configuredKinds = new ArrayList<>();
if (xpRoutePlans.isEmpty()) {
throw new ServiceException("未配置有效的导航策略", I18nKeyConstant.PATH_NOT_FOUND);
} 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());
}
});
}
}
return new StrategyPlanningConfig(configuredKinds, 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;
}
}