package com.xiaopeng.operation.service.impl;
|
|
|
import com.alibaba.fastjson.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.feignClient.account.XpAppUserClient;
|
import com.xiaopeng.api.feignClient.account.XpAppUserCouponClient;
|
import com.xiaopeng.api.feignClient.map.XpMapClient;
|
import com.xiaopeng.api.feignClient.order.XpOrderClient;
|
import com.xiaopeng.api.feignClient.vehicleConvoy.XpVehicleClient;
|
import com.xiaopeng.api.model.account.XpAppUser;
|
import com.xiaopeng.api.model.operation.*;
|
import com.xiaopeng.api.model.order.XpOrder;
|
import com.xiaopeng.api.model.vehicleConvoy.XpVehicle;
|
import com.xiaopeng.api.vo.account.UserCouponVo;
|
import com.xiaopeng.api.vo.operation.FareVo;
|
import com.xiaopeng.api.vo.operation.RouteAndFareVo;
|
import com.xiaopeng.api.vo.operation.RouteVo;
|
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.core.utils.uuid.UUID;
|
import com.xiaopeng.common.redis.service.RedisService;
|
import com.xiaopeng.operation.constant.I18nKeyConstant;
|
import com.xiaopeng.operation.dto.XpOrderDto;
|
import com.xiaopeng.operation.entity.XpGeoQueryEntity;
|
import com.xiaopeng.operation.mapper.*;
|
import com.xiaopeng.operation.service.IXpGeoQueryEntityService;
|
import com.xiaopeng.operation.service.XpPricingService;
|
import com.xiaopeng.operation.utils.XpPricingUtil;
|
import com.xiaopeng.operation.utils.map.FastDistanceCalculator;
|
import com.xiaopeng.operation.vo.XpPricingCardVo;
|
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.stereotype.Service;
|
|
import javax.annotation.PreDestroy;
|
import javax.annotation.Resource;
|
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
|
*/
|
@Service
|
public class XpPricingServiceImpl extends ServiceImpl<XpPricingMapper, XpPricing> implements XpPricingService {
|
|
|
@Resource
|
private XpPricingUtil xpPricingUtil;
|
|
@Resource
|
private XpMapClient xpMapClient;
|
|
@Resource
|
private XpOpenCityMapper xpOpenCityMapper;
|
|
@Resource
|
private XpRoutePlanMapper xpRoutePlanMapper;
|
|
@Resource
|
private RedisService redisService;
|
|
@Resource
|
private XpSystemSettingMapper systemSettingMapper;
|
|
@Resource
|
private XpAppUserCouponClient xpAppUserCouponClient;
|
|
@Resource
|
private XpOrderClient xpOrderClient;
|
|
@Resource
|
private XpTradingStrategyMapper xpTradingStrategyMapper;
|
|
@Resource
|
private XpElectronicFenceAreaMapper xpElectronicFenceAreaMapper;
|
|
@Resource
|
private XpVehicleClient xpVehicleClient;
|
|
@Resource
|
private XpPointMapper xpPointMapper;
|
|
@Resource
|
private XpWhitelistMapper xpWhitelistMapper;
|
|
@Resource
|
private XpAppUserClient xpAppUserClient;
|
|
|
@Autowired
|
private IXpGeoQueryEntityService geoQueryEntityService;
|
|
|
|
// 自定义线程池用于并行执行任务
|
private final ExecutorService executorService;
|
|
// 初始化自定义线程池
|
public XpPricingServiceImpl() {
|
this.executorService = new ThreadPoolExecutor(
|
40, // 核心线程数
|
100, // 最大线程数
|
60, // 线程空闲时间(秒)
|
TimeUnit.SECONDS, // 时间单位
|
new LinkedBlockingQueue<>(400), // 工作队列
|
new ThreadFactory() {
|
private final AtomicInteger threadNumber = new AtomicInteger(1);
|
|
@Override
|
public Thread newThread(Runnable r) {
|
Thread thread = new Thread(r);
|
thread.setName("pricing-service-thread-" + threadNumber.getAndIncrement());
|
thread.setDaemon(false);
|
return thread;
|
}
|
}, // 线程工厂
|
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
|
);
|
}
|
|
// 关闭线程池
|
@PreDestroy
|
public void destroy() {
|
if (executorService != null && !executorService.isShutdown()) {
|
executorService.shutdown();
|
try {
|
if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) {
|
executorService.shutdownNow();
|
}
|
} catch (InterruptedException e) {
|
executorService.shutdownNow();
|
Thread.currentThread().interrupt();
|
}
|
}
|
}
|
|
/**
|
* 根据当前时间获取时段
|
* 例如:15:50 → 15-16
|
* @return 时段字符串,格式:HH-HH
|
*/
|
private String getCurrentTimeSlot() {
|
Calendar calendar = Calendar.getInstance();
|
int hour = calendar.get(Calendar.HOUR_OF_DAY);
|
return hour + "-" + (hour + 1);
|
}
|
|
|
@Override
|
public RouteAndFareVo getRouteAndFare(RouteAndFareDto routeAndFareDto, String userId) {
|
|
|
// 判断是否5分钟内进行了查询 相同站点的起始点 并且是同时段
|
String key = "xp-map-query-" + routeAndFareDto.getOpenCityId()
|
+ "-" + routeAndFareDto.getOrigin().getLongitude() + "-" + routeAndFareDto.getOrigin().getLatitude()
|
+ "-" + routeAndFareDto.getDestination().getLongitude() + "-" + routeAndFareDto.getDestination().getLatitude() + "-" + getCurrentTimeSlot() + "-" + userId;
|
|
if (redisService.hasKey(key)) {
|
String jsonStr = Objects.requireNonNull(redisService.getCacheObject( key)).toString();
|
RouteAndFareVo RouteAndFareVo = JSONObject.parseObject(jsonStr, RouteAndFareVo.class);
|
return (RouteAndFareVo);
|
}
|
|
// 1. 基础数据查询
|
XpOpenCity xpOpenCity = xpOpenCityMapper.selectById(routeAndFareDto.getOpenCityId());
|
if (xpOpenCity == null) {
|
throw new ServiceException("未找到步行路线", I18nKeyConstant.CITY_INFORMATION_DOES_NOT_EXIST);
|
}
|
String cityCode = xpOpenCity.getCityCode();
|
|
XpRoutePlan xpRoutePlan = xpRoutePlanMapper.selectOne(new LambdaQueryWrapper<XpRoutePlan>().eq(XpRoutePlan::getAuditStatus, 2).eq(XpRoutePlan::getCityCode, cityCode));
|
if (xpRoutePlan != null) {
|
routeAndFareDto.setRoutePlanRule(xpRoutePlan.getRoutePlanRule());
|
}
|
|
// XpPricing xpPricing = this.baseMapper.selectOne(new LambdaQueryWrapper<XpPricing>().eq(XpPricing::getCityId, routeAndFareDto.getOpenCityId()));
|
// if (xpPricing == null) {
|
// throw new ServiceException("当前区域未开通服务", I18nKeyConstant.NO_CURRENT_REGION);
|
// }
|
|
// 2. 计算价格(需要在并行任务前完成,因为优惠券查询需要价格)
|
// XpPricingCardVo xpPricingCardVo = xpPricingUtil.calculationPrice(
|
// JSON.parseObject(xpPricing.getBasicPricingJson()),
|
// JSON.parseArray(xpPricing.getStartingFareJson()),
|
// 1, 1, 0, 0.0
|
// );
|
XpPricingCardVo xpPricingCardVo = new XpPricingCardVo();
|
|
// 3. 并行执行远程服务调用 判断是否同一经营区域
|
CompletableFuture<R<Boolean>> diagnosisAreaFuture = CompletableFuture.supplyAsync(
|
() -> xpMapClient.diagnosisArea(routeAndFareDto),
|
executorService
|
);
|
|
// // 算距离
|
// CompletableFuture<R<RouteVo>> routeFuture = CompletableFuture.supplyAsync(
|
// () -> xpMapClient.getDistanceAndDuration(routeAndFareDto),
|
// executorService
|
// );
|
|
// 优惠券
|
CompletableFuture<R<UserCouponVo>> couponFuture = CompletableFuture.supplyAsync(
|
() -> {
|
// 优惠券
|
return xpAppUserCouponClient.getUserCoupon(
|
// new GetUserCouponDto(Long.valueOf(userId), xpPricingCardVo.getTotalPrice())
|
new GetUserCouponDto(Long.valueOf(userId), 1d)
|
);
|
},
|
executorService
|
);
|
|
// 4. 等待所有并行任务完成并获取结果
|
R<Boolean> diagnosisResult = diagnosisAreaFuture.join();
|
// R<RouteVo> routeResult = routeFuture.join();
|
R<UserCouponVo> 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) {
|
xpPricingCardVo.setCoupon(String.valueOf(userCouponVo.getMoney()));
|
}
|
|
// 8. 构建响应对象
|
RouteAndFareVo routeAndFareVo = new RouteAndFareVo();
|
// routeAndFareVo.setRoute(routeVo);
|
|
FareVo fareVo = new FareVo();
|
// fareVo.setEstimatedDistanceKm(routeVo.getDistance().doubleValue() / 1000);
|
// fareVo.setEstimatedDurationMin(routeVo.getDuration() / 60);
|
|
// 9. 设置费用信息
|
fareVo.setOriginalFare(xpPricingCardVo.getTotalPrice());
|
if (userCouponVo != null) {
|
fareVo.setDiscountAmount(userCouponVo.getMoney());
|
fareVo.setFinalFare(fareVo.getOriginalFare() - fareVo.getDiscountAmount());
|
fareVo.setCouponId(userCouponVo.getId());
|
} else {
|
fareVo.setDiscountAmount(0.0);
|
fareVo.setFinalFare(fareVo.getOriginalFare());
|
}
|
|
// 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. 设置费用信息
|
routeAndFareVo.setFareEstimate(fareVo);
|
|
|
String voJsonStr = JSONObject.toJSONString(routeAndFareVo);
|
// 进行缓存
|
redisService.setCacheObject(key, voJsonStr, 5l, TimeUnit.MINUTES);
|
|
return routeAndFareVo;
|
}
|
|
|
/**
|
* 获取等待时间
|
* @param lon
|
* @param lat
|
* @return
|
*/
|
@Override
|
public Integer getWaitTime(double lon,double lat) {
|
List<XpGeoQueryEntity> entities = geoQueryEntityService.findByLocationWithin500MetersAndLastWeekAndStatusGreater(
|
lon,
|
lat,
|
getCurrentTimeSlot()
|
);
|
|
|
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();
|
// 未完成接单的数量
|
int count = entities.size() - validEntities.size();
|
if (count == 0) {
|
return 1;
|
} else {
|
double waitMinutes = (average * count) / 60;
|
if (waitMinutes > 3) {
|
throw new ServiceException("当前区域正在等待中,请耐心等待", I18nKeyConstant.WAITING_TIME);
|
}
|
return (int) waitMinutes;
|
}
|
} else {
|
return 1;
|
}
|
}
|
}
|
|
|
|
@Override
|
public Long saveOrder(XpOrderDto dto, String uid) {
|
Integer cancelCount = xpTradingStrategyMapper.selectOne(null).getCancleCount();
|
// 判断是否有进行的订单
|
R<Long> r = xpOrderClient.hasUnPaodOrder(uid);
|
if(r.getData()>0){
|
throw new RuntimeException("有正在进行的订单");
|
}
|
|
// 获取起点经纬度
|
XpPoint xpPoint = xpPointMapper.selectById(dto.getStartPointId());
|
|
// 终点
|
XpPoint endXpPoint = xpPointMapper.selectById(dto.getStartPointId());
|
|
// 判断当天取消的次数 后台配置 redis 查询
|
Object cancelOrder = redisService.getCacheObject("CANCEL_ORDER_" + uid);
|
if(cancelOrder!=null){
|
Integer num = Integer.valueOf(cancelOrder.toString());
|
if(num>=cancelCount){
|
return 0l;
|
}
|
}
|
|
|
|
// 根据id获取手机号
|
|
Boolean isPriorityDispatch=false;
|
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.setAppUserId(Long.valueOf(uid));
|
xpOrder.setOrderNumber("XP"+System.currentTimeMillis());
|
xpOrder.setPlacementLon(dto.getPlacementLon());
|
xpOrder.setPlacementLat(dto.getPlacementLat());
|
xpOrder.setPlacementAddress(dto.getPlacementAddress());
|
|
xpOrder.setCityId(dto.getOpenCityId());
|
|
xpOrder.setStartLon(xpPoint.getPointLon());
|
xpOrder.setStartLat(xpPoint.getPointLat());
|
xpOrder.setBoardingLon(xpPoint.getPointLon());
|
xpOrder.setBoardingLat(xpPoint.getPointLat());
|
xpOrder.setBoardingAddress(xpPoint.getPointAddress());
|
|
xpOrder.setEndLon(endXpPoint.getPointLon());
|
xpOrder.setEndLat(endXpPoint.getPointLat());
|
|
xpOrder.setStartAddress(xpPoint.getPointAddress());
|
xpOrder.setEndAddress(endXpPoint.getPointAddress());
|
|
xpOrder.setXpElectronicFenceAreaId(dto.getXpElectronicFenceAreaId());
|
xpOrder.setStartPointId(dto.getStartPointId());
|
xpOrder.setEndPointId(dto.getEndPointId());
|
xpOrder.setStartPointName(xpPoint.getPointName());
|
xpOrder.setEndPointName(endXpPoint.getPointName());
|
|
// 当前时段
|
String currentTimeSlot = DateUtils.getCurrentTimeSlot();
|
xpOrder.setTimeSlot(currentTimeSlot);
|
xpOrder.setCreateTime(LocalDateTime.now());
|
|
// 产生订单
|
R<Long> addOrderR = xpOrderClient.addOrder(xpOrder);
|
if(addOrderR.getCode()!=R.SUCCESS){
|
throw new ServiceException("订单生成失败", I18nKeyConstant.ORDER_CREATE_FAIL);
|
}
|
Long orderId = addOrderR.getData();
|
// 最大距离
|
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);
|
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){
|
if(isPriorityDispatch){
|
// 头部 value 订单_经纬度_状态_用户id_订单编号
|
redisService.addToTail(s,orderId+"_"+xpPoint.getPointLon()+","+xpPoint.getPointLat()+"_0"+"_"+uid+"_"+xpOrder.getOrderNumber());
|
}else {
|
// 尾部
|
redisService.addToTail(s,orderId+"_"+xpPoint.getPointLon()+","+xpPoint.getPointLat()+"_0"+"_"+uid+"_"+xpOrder.getOrderNumber());
|
}
|
return orderId;
|
}
|
}
|
|
String key = CacheConstants.QUEUE_POINT_ID+"-"+ xpPoint.getId()+"-"+xpPoint.getPointLon()+","+xpPoint.getPointLat();
|
redisService.setCacheList(key,Arrays.asList(orderId+"_"+xpPoint.getPointLon()+","+xpPoint.getPointLat()+"_0"+"_"+uid+"_"+xpOrder.getOrderNumber()));
|
return orderId;
|
|
//
|
// XpElectronicFenceArea xpElectronicFenceArea = xpElectronicFenceAreaMapper.selectById(dto.getXpElectronicFenceAreaId());
|
// // 获取电子围栏的车辆
|
// R<List<XpVehicle>> vehicles = xpVehicleClient.listByFenceId(xpElectronicFenceArea.getElectronicId());
|
//
|
|
|
|
|
// 产生订单 存订单数据 // 下发指令 消息推送用户端
|
// return null;
|
|
}
|
|
|
}
|