package com.xiaopeng.operation.service.impl;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
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.operation.LonLatDto;
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.model.account.XpAppUser;
import com.xiaopeng.api.model.operation.*;
import com.xiaopeng.api.model.order.XpOrder;
import com.xiaopeng.api.vo.account.UserCouponVo;
import com.xiaopeng.api.vo.operation.FareVo;
import com.xiaopeng.api.vo.operation.RouteAndFareVo;
import com.xiaopeng.api.vo.operation.RouteVo;
import com.xiaopeng.api.vo.order.XpPricingCardVo;
import com.xiaopeng.common.core.constant.CacheConstants;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.exception.ServiceException;
import com.xiaopeng.common.core.utils.DateUtils;
import com.xiaopeng.common.core.utils.uuid.UUID;
import com.xiaopeng.common.core.web.page.PageInfo;
import com.xiaopeng.common.redis.service.RedisService;
import com.xiaopeng.operation.constant.I18nKeyConstant;
import com.xiaopeng.operation.dto.PricingAddDTO;
import com.xiaopeng.operation.dto.PricingSetPriceDTO;
import com.xiaopeng.operation.dto.PricingSetRuleDTO;
import com.xiaopeng.operation.dto.XpOrderDto;
import com.xiaopeng.operation.mapper.*;
import com.xiaopeng.operation.query.PricingPageListQuery;
import com.xiaopeng.operation.service.IXpGeoQueryEntityService;
import com.xiaopeng.operation.service.XpPricingService;
import com.xiaopeng.operation.utils.XpPricingUtil;
import com.xiaopeng.operation.utils.holiday.HolidayUtil;
import com.xiaopeng.operation.utils.map.FastDistanceCalculator;
import com.xiaopeng.operation.vo.PricingPageListVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
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;
/**
*
* 定价管理 服务实现类
*
*
* @author liu
* @since 2026-02-03
*/
@Service
public class XpPricingServiceImpl extends ServiceImpl implements XpPricingService {
@Resource
private XpPricingUtil xpPricingUtil;
@Resource
private XpMapClient xpMapClient;
@Resource
private XpOpenCityMapper xpOpenCityMapper;
@Resource
private XpRoutePlanMapper xpRoutePlanMapper;
@Resource
private RedisService redisService;
@Resource
private XpSystemSettingMapper systemSettingMapper;
@Resource
private XpAppUserCouponClient xpAppUserCouponClient;
@Resource
private XpOrderClient xpOrderClient;
@Resource
private XpTradingStrategyMapper xpTradingStrategyMapper;
@Resource
private XpElectronicFenceAreaMapper xpElectronicFenceAreaMapper;
@Resource
private XpPointMapper xpPointMapper;
@Resource
private XpWhitelistMapper xpWhitelistMapper;
@Resource
private XpAppUserClient xpAppUserClient;
@Resource
private HolidayUtil holidayUtil;
@Autowired
private IXpGeoQueryEntityService geoQueryEntityService;
@Autowired
private XpElectronicFenceMapper xpElectronicFenceMapper;
// 自定义线程池用于并行执行任务
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().eq(XpRoutePlan::getAuditStatus, 2).eq(XpRoutePlan::getCityCode, cityCode));
if (xpRoutePlan != null) {
routeAndFareDto.setRoutePlanRule(xpRoutePlan.getRoutePlanRule());
}
XpPricing xpPricing = this.baseMapper.selectOne(new LambdaQueryWrapper().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> diagnosisAreaFuture = CompletableFuture.supplyAsync(
() -> xpMapClient.diagnosisArea(routeAndFareDto),
executorService
);
// // 算距离
// CompletableFuture> routeFuture = CompletableFuture.supplyAsync(
// () -> xpMapClient.getDistanceAndDuration(routeAndFareDto),
// executorService
// );
// 优惠券
CompletableFuture> couponFuture = CompletableFuture.supplyAsync(
() -> {
// 优惠券
return xpAppUserCouponClient.getUserCoupon(
// new GetUserCouponDto(Long.valueOf(userId), xpPricingCardVo.getTotalPrice(),routeAndFareDto.getOpenCityId())
new GetUserCouponDto(Long.valueOf(userId), 1d, routeAndFareDto.getOpenCityId())
);
},
executorService
);
// 4. 等待所有并行任务完成并获取结果
R diagnosisResult = diagnosisAreaFuture.join();
// R routeResult = routeFuture.join();
R 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);
routeAndFareVo.setXpPricingCard(xpPricingCardVo);
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 entities = geoQueryEntityService.findByLocationWithin500MetersAndLastWeekAndStatusGreater(
lon,
lat,
getCurrentTimeSlot()
);
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();
// 未完成接单的数量
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);
}else if(waitMinutes==0){
return 1;
}
return (int) waitMinutes;
}
} else {
return 1;
}
}
}
@Override
public Long 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) {
throw new ServiceException("当前区域未开通服务", I18nKeyConstant.NO_CURRENT_REGION);
}
Integer cancelCount = xpTradingStrategyMapper.selectOne(null).getCancleCount();
// 判断是否有进行的订单
R r = xpOrderClient.hasUnPaodOrder(uid);
if (r.getData() > 0) {
throw new RuntimeException("有正在进行的订单");
}
// 获取起点经纬度
XpPoint xpPoint = xpPointMapper.selectById(dto.getStartPointId());
// 终点
XpPoint endXpPoint = xpPointMapper.selectById(dto.getStartPointId());
// 判断当天取消的次数 后台配置 redis 查询
Object cancelOrder = redisService.getCacheObject("CANCEL_ORDER_" + uid);
// 静默处理
boolean isSilent = false;
if (cancelOrder != null) {
Integer num = Integer.valueOf(cancelOrder.toString());
if (num >= cancelCount) {
isSilent =true;
}
}
// 获取起步里程
// XpPricing xpPricing = this.baseMapper.selectOne(new LambdaQueryWrapper().eq(XpPricing::getCityId, dto.getOpenCityId()));
// Integer startMileage = XpPricingUtil.getStartMileage(xpPricing.getStartingFareJson());
// if (startMileage == null) {
// throw new ServiceException("当前区域未开通服务", I18nKeyConstant.NO_CURRENT_REGION);
// }
// 根据id获取手机号
Boolean isPriorityDispatch = false;
R xpAppUserR = xpAppUserClient.queryOperationsUserById(Long.valueOf(uid));
if (xpAppUserR.getCode() == R.SUCCESS) {
Long count = xpWhitelistMapper.selectCount(new LambdaQueryWrapper().eq(XpWhitelist::getAccount, xpAppUserR.getData().getPhoneNumber()).eq(XpWhitelist::getIsPriorityDispatch, 1));
if (count > 0) {
isPriorityDispatch = true;
}
}
// 生成订单数据
XpOrder xpOrder = new XpOrder();
xpOrder.setVehicleId(1l);
// xpOrder.setStartMileage(Double.valueOf(startMileage));
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());
// 计算距离 和 时间
RouteAndFareDto routeAndFareDto = new RouteAndFareDto();
LonLatDto lonLatDto = new LonLatDto();
lonLatDto.setLongitude(xpPoint.getPointLon());
lonLatDto.setLatitude(xpPoint.getPointLat());
routeAndFareDto.setOrigin(lonLatDto);
LonLatDto lonLatDto1 = new LonLatDto();
lonLatDto1.setLongitude(endXpPoint.getPointLon());
lonLatDto1.setLatitude(endXpPoint.getPointLat());
routeAndFareDto.setDestination(lonLatDto1);
XpOpenCity xpOpenCity = xpOpenCityMapper.selectById(dto.getOpenCityId());
XpRoutePlan xpRoutePlan = xpRoutePlanMapper.selectOne(new LambdaQueryWrapper().eq(XpRoutePlan::getAuditStatus, 2).eq(XpRoutePlan::getCityCode, xpOpenCity.getCityCode()));
if (xpRoutePlan != null) {
routeAndFareDto.setRoutePlanRule(xpRoutePlan.getRoutePlanRule());
}
R distanceAndDuration = xpMapClient.getDistanceAndDuration(routeAndFareDto);
Integer distance = 0;
Integer duration = 0;
if (distanceAndDuration.getCode() != 200) {
// throw new ServiceException("订单生成失败", I18nKeyConstant.ORDER_CREATE_FAIL);
}
// distance = distanceAndDuration.getData().getDistance();
// duration = distanceAndDuration.getData().getDuration();
// 产生订单
R addOrderR = xpOrderClient.addOrder(xpOrder);
if (addOrderR.getCode() != R.SUCCESS) {
throw new ServiceException("订单生成失败", I18nKeyConstant.ORDER_CREATE_FAIL);
}
Long orderId = addOrderR.getData();
// 静默处理 不推单
if(isSilent){
return orderId;
}
// 最大距离
Object maxPushDistanceObj = redisService.getCacheObject(CacheConstants.MAX_PUSH_DISTANCE);
Double maxPushDistance = 0.0;
if (maxPushDistanceObj != null) {
maxPushDistance = Double.valueOf(maxPushDistanceObj.toString());
} else {
maxPushDistance = xpTradingStrategyMapper.selectOne(null).getThreePushDistance();
}
// 找出现在所有的队列 通过计算距离 算出目前应该放在那个队列 如果没有符合的新增一个队列 符合后看是否白名单账号 是则直接放在队列前端
Set quoteMatchName = redisService.getQuoteMatchName(CacheConstants.QUEUE_POINT_ID);
for (String s : quoteMatchName) {
// QUEUE_POINT_ID-1-30.233,120.233
String lonLat = s.split("-")[2];
// 和上车点进行判断是否> vehicles = xpVehicleClient.listByFenceId(xpElectronicFenceArea.getElectronicId());
//
// 产生订单 存订单数据 // 下发指令 消息推送用户端
// return null;
}
private 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;
}
@Override
public PageInfo pageList(PricingPageListQuery query) {
PageInfo pageInfo = new PageInfo<>(query.getPageNum(), query.getPageSize());
Page page = this.baseMapper.pageList(pageInfo, query);
pageInfo.setRecords(page.getRecords());
pageInfo.setTotal(page.getTotal());
return pageInfo;
}
@Override
public void add(PricingAddDTO dto) {
XpOpenCity openCity = xpOpenCityMapper.selectById(dto.getCityId());
if (openCity == null) {
throw new ServiceException("开通城市不存在");
}
Long count = this.lambdaQuery()
.eq(XpPricing::getVehicleModel, dto.getVehicleModel())
.eq(XpPricing::getChannel, dto.getChannel())
.eq(XpPricing::getCityId, dto.getCityId().intValue())
.count();
if (count != null && count > 0) {
throw new ServiceException("该车型/渠道/城市的定价已存在", I18nKeyConstant.PRICING_EXISTS);
}
XpPricing pricing = new XpPricing();
pricing.setVehicleModel(dto.getVehicleModel());
pricing.setChannel(dto.getChannel());
pricing.setCityId(dto.getCityId().intValue());
pricing.setRemark(dto.getRemark());
pricing.setPricingType(dto.getPricingType());
pricing.setBasicPricingJson("{}");
pricing.setStartingFareJson("[]");
pricing.setDynamicPricingJson("{}");
pricing.setIsDynamicPrice(0);
this.save(pricing);
}
@Override
public void setPrice(PricingSetPriceDTO dto) {
XpPricing pricing = this.getById(dto.getId());
if (pricing == null) {
throw new ServiceException("定价不存在", I18nKeyConstant.PRICING_NOT_EXIST);
}
try {
JSONObject.parseObject(dto.getBasicPricingJson());
JSONArray.parseArray(dto.getStartingFareJson());
JSONObject.parseObject(dto.getDynamicPricingJson());
} catch (Exception e) {
throw new ServiceException("价格配置JSON格式不正确", I18nKeyConstant.PRICING_JSON_INVALID);
}
XpPricing update = new XpPricing();
update.setId(dto.getId());
update.setBasicPricingJson(dto.getBasicPricingJson());
update.setStartingFareJson(dto.getStartingFareJson());
update.setDynamicPricingJson(dto.getDynamicPricingJson());
this.updateById(update);
}
@Override
public void setRule(PricingSetRuleDTO dto) {
XpPricing pricing = this.getById(dto.getId());
if (pricing == null) {
throw new ServiceException("定价不存在", I18nKeyConstant.PRICING_NOT_EXIST);
}
XpPricing update = new XpPricing();
update.setId(dto.getId());
update.setPriceRule(dto.getPriceRule());
this.updateById(update);
}
}