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.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;
|
|
/**
|
* <p>
|
* 定价管理 服务实现类
|
* </p>
|
*
|
* @author liu
|
* @since 2026-02-03
|
*/
|
@Slf4j
|
@Service
|
public class XpPricingServiceImpl extends ServiceImpl<XpPricingMapper, XpPricing> 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
|
private XpTradingStrategyClient xpTradingStrategyClient;
|
|
@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<XpPricing> 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<RouteAndFareVo> getRouteAndFare(RouteAndFareDto routeAndFareDto, String userId) {
|
// // 判断是否5分钟内进行了查询 相同站点的起始点 并且是同时段
|
// String key = "xp-map-query-" + routeAndFareDto.getOpenCityId()
|
// + "-" + routeAndFareDto.getOrigin().getLongitude() + "-" + routeAndFareDto.getOrigin().getLatitude()
|
// + "-" + routeAndFareDto.getDestination().getLongitude() + "-" + routeAndFareDto.getDestination().getLatitude() + "-" + getCurrentTimeSlot() + "-" + userId+routeAndFareDto.getUserCouponId();
|
//
|
// if (redisService.hasKey(key)) {
|
// String jsonStr = Objects.requireNonNull(redisService.getCacheObject(key)).toString();
|
// RouteAndFareVo RouteAndFareVo = JSONObject.parseObject(jsonStr, RouteAndFareVo.class);
|
// return (RouteAndFareVo);
|
// }
|
ArrayList<RouteAndFareVo> routeAndFareVos = new ArrayList<>(3);
|
|
// 1. 基础数据查询
|
Map<String, String> 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> xpRoutePlan = xpRoutePlanMapper.selectList(new LambdaQueryWrapper<XpRoutePlan>().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<RouteVo> 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<UserCouponVo> coupon = null;
|
CompletableFuture<R<UserCouponVo>> 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<R<Boolean>> diagnosisAreaFuture = CompletableFuture.supplyAsync(
|
() -> xpMapClient.diagnosisArea(routeAndFareDto),
|
executorService
|
);
|
|
// 4. 等待所有并行任务完成并获取结果
|
R<Boolean> diagnosisResult = diagnosisAreaFuture.join();
|
R<UserCouponVo> 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<XpGeoQueryEntity> entities = geoQueryEntityService.findByLocationWithin500MetersAndLastWeekAndStatusGreater(
|
lon,
|
lat,
|
getCurrentTimeSlot()
|
);
|
|
log.info("等待时间查询结果:{}", entities.toString());
|
|
if (entities.isEmpty()) {
|
return 1;
|
} else {
|
// 已完成接单的数据
|
List<XpGeoQueryEntity> 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<XpGeoQueryEntity> entities = geoQueryEntityService.findByLocationWithin500MetersAndLastWeekAndStatusGreater(
|
lon,
|
lat,
|
timeSlot
|
);
|
|
|
if (entities.isEmpty()) {
|
return 1;
|
} else {
|
// 已完成接单的数据
|
List<XpGeoQueryEntity> 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<Long> 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);
|
}
|
// 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<XpPricing>().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<XpAppUser> xpAppUserR = xpAppUserClient.queryOperationsUserById(Long.valueOf(uid));
|
// if (xpAppUserR.getCode() == R.SUCCESS) {
|
// Long count = xpWhitelistMapper.selectCount(new LambdaQueryWrapper<XpWhitelist>().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<XpRoutePlan>().eq(XpRoutePlan::getAuditStatus, 2)
|
.eq(XpRoutePlan::getCityCode, xpOpenCity.getCityCode()).eq(XpRoutePlan::getRoutePlanRule, xpUserRoutePlanRule.getRoutePlanRule()));
|
xpRoutePlanMapper.selectById(xpUserRoutePlanRule.getRoutePlanRule());
|
}else{
|
xpRoutePlan = xpRoutePlanMapper.selectOne(new LambdaQueryWrapper<XpRoutePlan>().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<Long> 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<String> quoteMatchName = redisService.getQuoteMatchName(CacheConstants.QUEUE_POINT_ID);
|
|
int targetedVal = 0;
|
R<List<XpTargetedServiceVehicleVo>> targetedServiceUserR = xpTargetedServiceClient.findListByUserIdAndCityCode(xpOrder.getAppUserId(), xpOrder.getCityCode());
|
if(targetedServiceUserR.getCode() == R.SUCCESS && CollUtil.isNotEmpty(targetedServiceUserR.getData()) ){
|
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];
|
// 和上车点进行判断是否<maxPushDistance
|
double v = FastDistanceCalculator.distanceFast(Double.parseDouble(lonLat.split(",")[1]), Double.parseDouble(lonLat.split(",")[0]), xpPoint.getPointLat(), xpPoint.getPointLon());
|
if (v < maxPushDistance) {
|
// String value = orderId + "_" + xpPoint.getPointLon() + "," + xpPoint.getPointLat() + "_0" + "_" + uid + "_" + xpOrder.getOrderNumber() + "_" + distance + "_" + duration + "_" + planRuleForQueue;
|
|
if(targetedVal>0){ //todo yyy 这里添加数据需要优化 需要redis 加锁
|
List<String> cacheList = redisService.getCacheList(s);
|
cacheList.add(redis_value);
|
List<RedisQueuePointIdVo> 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<String> 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<UserCouponVo> coupon = null;
|
CompletableFuture<R<UserCouponVo>> 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<UserCouponVo> 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<String, String> 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<XpPricing>().eq(XpPricing::getCityId, routeAndFareDto.getOpenCityId()));
|
if (xpPricing == null) {
|
throw new ServiceException("当前区域未开通服务", I18nKeyConstant.NO_CURRENT_REGION);
|
}
|
R<Boolean> 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<RouteStrategyKind> kinds = planningConfig.getKinds();
|
Map<String, RouteVo> routeById = new LinkedHashMap<>();
|
Map<String, Set<RouteStrategyKind>> 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<RouteVo> 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<RouteOptionVo> options = new ArrayList<>();
|
for (Map.Entry<String, RouteVo> e : routeById.entrySet()) {
|
String fid = e.getKey();
|
RouteVo rv = e.getValue();
|
Set<RouteStrategyKind> 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<String> 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);
|
|
R<XpTradingStrategyDetailVO> 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);
|
|
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<RoutePreferenceItemVo> listRoutePreferences(Long openCityId, String userId) {
|
Set<String> enabledCodes = parseEnabledPreferenceCodes();
|
Optional<RouteStrategyKind> savedPref = resolveUserPreferredKind(userId);
|
Map<RouteStrategyKind, String> displayNames = buildStrategyKindsForPlanning(userId, openCityId).getDisplayNames();
|
List<RoutePreferenceItemVo> 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<String> parseEnabledPreferenceCodes() {
|
Set<String> 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<String> enabled = parseEnabledPreferenceCodes();
|
List<XpRoutePlan> xpRoutePlans = xpRoutePlanMapper.selectList(new LambdaQueryWrapper<XpRoutePlan>().eq(XpRoutePlan::getOpenCityId, openCityId));
|
Map<RouteStrategyKind, String> displayNames = new LinkedHashMap<>();
|
List<RouteStrategyKind> 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<RouteStrategyKind> all = new ArrayList<>();
|
for (RouteStrategyKind k : configuredKinds) {
|
if (enabled.contains(k.name())) {
|
addIfAbsent(all, k);
|
}
|
}
|
if (all.isEmpty()) {
|
all.add(resolveDefaultEnabledKind(enabled));
|
}
|
Optional<RouteStrategyKind> 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<RouteStrategyKind> 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<RouteStrategyKind, String> displayNames) {
|
String configuredName = displayNames.get(kind);
|
return StringUtils.isNotBlank(configuredName) ? configuredName : kind.getDisplayName();
|
}
|
|
private void addIfAbsent(List<RouteStrategyKind> kinds, RouteStrategyKind kind) {
|
if (!kinds.contains(kind)) {
|
kinds.add(kind);
|
}
|
}
|
|
private RouteStrategyKind resolveDefaultEnabledKind(Set<String> 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<RouteStrategyKind> kinds;
|
private final Map<RouteStrategyKind, String> displayNames;
|
|
private StrategyPlanningConfig(List<RouteStrategyKind> kinds, Map<RouteStrategyKind, String> displayNames) {
|
this.kinds = kinds;
|
this.displayNames = displayNames;
|
}
|
|
private List<RouteStrategyKind> getKinds() {
|
return kinds;
|
}
|
|
private Map<RouteStrategyKind, String> getDisplayNames() {
|
return displayNames;
|
}
|
}
|
|
private Optional<RouteStrategyKind> 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<String, Set<RouteStrategyKind>> strategiesById) {
|
Set<RouteStrategyKind> sa = strategiesById.getOrDefault(a.getPathFingerprint(), Collections.emptySet());
|
Set<RouteStrategyKind> 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<XpOrder> 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<String> 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;
|
}
|
|
}
|