package com.xiaopeng.order.service.impl; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollUtil; import cn.hutool.json.JSONUtil; 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.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.google.common.collect.ImmutableMap; import com.xiaopeng.api.dto.account.GetUserCouponDto; import com.xiaopeng.api.dto.account.UcUserInfo; import com.xiaopeng.api.dto.account.UseUserCouponDto; import com.xiaopeng.api.dto.automaticPilot.PushOrderDto; import com.xiaopeng.api.dto.map.RouteAndFareDto; import com.xiaopeng.api.dto.operation.LonLatDto; import com.xiaopeng.api.dto.order.GetOrderNumDto; import com.xiaopeng.api.dto.order.HistoryEndPointsDto; import com.xiaopeng.api.dto.order.HistoryStartPointsDto; import com.xiaopeng.api.dto.order.PopOrderDto; import com.xiaopeng.api.dto.pushMessage.SendMessageDTO; import com.xiaopeng.api.dto.statistics.DailyCountDTO; import com.xiaopeng.api.dto.statistics.OrderStatisticsDTO; import com.xiaopeng.api.dto.statistics.StatisticsQueryDTO; import com.xiaopeng.api.dto.vehicleConvoy.BaseVehicleUserDto; import com.xiaopeng.api.feignClient.account.UserResourcesClient; import com.xiaopeng.api.feignClient.account.XpAppUserCouponClient; import com.xiaopeng.api.feignClient.automaticPilot.OrderMissionClient; import com.xiaopeng.api.feignClient.maintenance.XpTaskRuleClient; import com.xiaopeng.api.feignClient.map.XpMapClient; import com.xiaopeng.api.feignClient.operation.*; import com.xiaopeng.api.feignClient.pushMessage.XpPushMessageClient; import com.xiaopeng.api.feignClient.vehicleConvoy.UserVehicleClient; import com.xiaopeng.api.feignClient.vehicleConvoy.XpFleetClient; import com.xiaopeng.api.feignClient.vehicleConvoy.XpFleetVehicleClient; import com.xiaopeng.api.feignClient.vehicleConvoy.XpVehicleClient; import com.xiaopeng.api.model.maintenance.XpTaskRule; import com.xiaopeng.api.model.operation.*; import com.xiaopeng.api.model.order.*; import com.xiaopeng.api.model.vehicleConvoy.XpFleet; import com.xiaopeng.api.model.vehicleConvoy.XpVehicle; import com.xiaopeng.api.vo.OrderType; import com.xiaopeng.api.vo.SystemEventType; import com.xiaopeng.api.vo.account.UserCouponVo; import com.xiaopeng.api.vo.automaticPilot.VehiclePosition; 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.*; import com.xiaopeng.api.vo.vehicleConvoy.AddTaxiPassengerVo; import com.xiaopeng.api.vo.vehicleConvoy.CancelTaxiPassengerVo; import com.xiaopeng.common.core.constant.CacheConstants; import com.xiaopeng.common.core.constant.Constants; import com.xiaopeng.common.core.constant.HttpStatus; import com.xiaopeng.common.core.domain.R; import com.xiaopeng.common.core.enums.OrderStatusEnum; import com.xiaopeng.common.core.enums.PushMessageTypeEnum; import com.xiaopeng.common.core.exception.ServiceException; import com.xiaopeng.common.core.utils.DateUtils; import com.xiaopeng.common.core.utils.GeodesyUtil; import com.xiaopeng.common.core.utils.StringUtils; import com.xiaopeng.common.core.web.domain.BaseModel; import com.xiaopeng.common.core.web.page.PageInfo; import com.xiaopeng.common.redis.service.RedisService; import com.xiaopeng.common.security.utils.SecurityUtils; import com.xiaopeng.order.constant.I18nKeyConstant; import com.xiaopeng.order.dto.MileageDto; import com.xiaopeng.order.job.base.QuartzManager; import com.xiaopeng.order.job.base.TimeJobType; import com.xiaopeng.order.job.jobs.AddCruiseMissionJob; import com.xiaopeng.order.job.jobs.EndTripJob; import com.xiaopeng.order.job.jobs.OrderCancelJob; import com.xiaopeng.order.mangodb.model.OrderTrajectory; import com.xiaopeng.order.mangodb.service.OrderTrajectoryService; import com.xiaopeng.order.mapper.*; import com.xiaopeng.order.model.dto.XpOrderCommentDto; import com.xiaopeng.order.model.dto.XpOrderPayDto; import com.xiaopeng.order.model.query.XpHistoryOrderListQuery; import com.xiaopeng.order.model.query.XpOrderExcelQuery; import com.xiaopeng.order.model.query.XpOrderPageQuery; import com.xiaopeng.order.model.query.XpTestOrderPageQuery; import com.xiaopeng.order.model.vo.*; import com.xiaopeng.order.service.*; import com.xiaopeng.order.utils.GDMapGeocodingUtil; import com.xiaopeng.order.utils.RegionCodeFilterUtil; import com.xiaopeng.order.utils.TaxiPricingDemoService; import com.xiaopeng.order.utils.XpPricingUtil; import com.xiaopeng.order.utils.holiday.HolidayUtil; import com.xiaopeng.order.vo.OrderDetailVo; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.shardingsphere.transaction.annotation.ShardingSphereTransactionType; import org.apache.shardingsphere.transaction.core.TransactionType; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Lazy; import org.springframework.data.domain.Sort; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.geo.GeoJsonPoint; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; import javax.annotation.Resource; import java.math.BigDecimal; import java.math.RoundingMode; import java.text.SimpleDateFormat; import java.time.*; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.function.Function; import java.util.stream.Collectors; /** *

* 订单 服务实现类 *

* * @author mitao * @since 2026-02-03 */ @Slf4j @Service @RequiredArgsConstructor(onConstructor_ = @Lazy) public class XpTestOrderServiceImpl extends ServiceImpl implements IXpTestOrderService { private final UserResourcesClient userResourcesClient; private final XpTestPointClient xpTestPointClient; private final XpVehicleClient xpVehicleClient; private final XpAppUserCouponClient xpAppUserCouponClient; private final XpFleetClient xpFleetClient; private final XpFleetVehicleClient xpFleetVehicleClient; private final UserVehicleClient userVehicleClient; private final IXpOrderEvaluateService xpOrderEvaluateService; private final IXpTestOrderCancelService xpTestOrderCancelService; private final IXpOrderReassignService xpOrderReassignService; private final RedisService redisService; private final XpPricingClient xpPricingClient; private final XpSystemSettingClient xpSystemSettingClient; private final XpPushMessageClient xpPushMessageClient; private final OrderMissionClient orderMissionClient; private final XpTestOrderDetailMapper xpTestOrderDetailMapper; private final XpCruiseOrderMapper xpCruiseOrderMapper; private final XpTaskRuleClient xpTaskRuleClient; private final XpCruisePlanClient xpCruisePlanClient; private final XpPricingUtil xpPricingUtil; private final GDMapGeocodingUtil gdMapGeocodingUtil; private final XpOpenCityClient openCityClient; private final XpMapClient xpMapClient; private final XpRoutePlanClient xpRoutePlanClient; private final XpTestPlanClient xpTestPlanClient; private final HolidayUtil holidayUtil; private final MongoTemplate mongoTemplate; private final XpElectronicFenceClient xpElectronicFenceClient; private final XpElectronicClient xpElectronicClient; /** * 添加测试单 * @param query */ @Override public void addTestOrder(AddTestOrderVo query) { XpTestPlanPoint xpTestPlanPoint = xpTestPlanClient.getXpTestPlanPointById(query.getTestPlanPointId()).getData(); Long startTestPointId = xpTestPlanPoint.getStartTestPointId(); Long endTestPointId = xpTestPlanPoint.getEndTestPointId(); XpTestPoint xpPoint = xpTestPlanClient.getXpTestPointById(startTestPointId).getData(); XpTestPoint endXpPoint = xpTestPlanClient.getXpTestPointById(endTestPointId).getData(); if(xpPoint ==null || xpPoint.getOperationStatus()!=2){ throw new ServiceException("起站点已停止运营", 200,I18nKeyConstant.NO_CURRENT_POINT); } if(endXpPoint ==null || endXpPoint.getOperationStatus()!=2){ throw new ServiceException("终站点已停止运营", 200,I18nKeyConstant.NO_CURRENT_POINT); } // 是否在运营区域时间内 XpElectronicFenceArea xpElectronicFenceArea = xpElectronicClient.queryElectronicFenceAreaById(xpPoint.getElectronicAreaId()).getData(); XpElectronicFence xpElectronicFence = xpElectronicFenceClient.selectById(xpElectronicFenceArea.getElectronicId().toString()).getData(); String operationStrategy = xpElectronicFence.getOperationStrategy(); Boolean inOperationTime = isInOperationTime(operationStrategy); if (!inOperationTime && xpElectronicFence.getFailureType()!=1) { throw new ServiceException("当前区域未开通服务", I18nKeyConstant.NO_CURRENT_REGION); } // 判断点位是否在运营区域内 boolean inTime = xpTestPointClient.nowInTimePoint(startTestPointId).getData(); if (!inTime) { throw new ServiceException("当前点位未开通服务", I18nKeyConstant.NO_CURRENT_REGION); } inTime = xpTestPointClient.nowInTimePoint(endTestPointId).getData(); if (!inTime) { throw new ServiceException("当前点位未开通服务", I18nKeyConstant.NO_CURRENT_REGION); } Map geocode = gdMapGeocodingUtil.geocode(xpPoint.getPointLon().toString(), xpPoint.getPointLat().toString()); String districtCode = geocode.get("districtCode"); String cityCode = geocode.get("cityCode"); String provinceCode = geocode.get("provinceCode"); XpOpenCity openCity = openCityClient.getXpOpenCity(provinceCode, cityCode, districtCode).getData(); if (openCity == null) { throw new ServiceException("当前区域未开通服务", I18nKeyConstant.NO_CURRENT_REGION); } XpPricing xpPricing = xpPricingClient.getPricingByCid(openCity.getId()).getData(); if (xpPricing == null) { throw new ServiceException("当前区域未开通服务", I18nKeyConstant.NO_CURRENT_REGION); } // 算距离 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); routeAndFareDto.setOpenCityId(openCity.getId()); XpRoutePlan xpRoutePlan = xpRoutePlanClient.getXpRoutePlanOpenCityId(openCity.getId()).getData(); if(xpRoutePlan!=null&& !"null".equals(xpRoutePlan.getRoutePlanRule())){ routeAndFareDto.setRoutePlanRule(Integer.valueOf(xpRoutePlan.getRoutePlanRule())); } // 调用地图 获取时间 R distanceAndDuration = xpMapClient.getDistanceAndDuration(routeAndFareDto); if (distanceAndDuration.getCode() != 200) { throw new ServiceException("获取导肮信息失败", I18nKeyConstant.PATH_NOT_FOUND); } RouteVo routeVo = distanceAndDuration.getData(); // 生成订单数据 XpTestOrder xpOrder = new XpTestOrder(); xpOrder.setOrderType(query.getOrderType()); xpOrder.setMileageKilometers(0.0); xpOrder.setOrderNumber("XP" + System.currentTimeMillis()); xpOrder.setCityId(openCity.getId()); 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(xpPoint.getElectronicAreaId()); xpOrder.setStartTestPointId(startTestPointId); xpOrder.setEndTestPointId(endTestPointId); xpOrder.setStartTestPointName(xpPoint.getPointName()); xpOrder.setEndTestPointName(endXpPoint.getPointName()); xpOrder.setStatus(1); xpOrder.setOrderSource(1); xpOrder.setOpenTrunk(2); // 当前时段 String currentTimeSlot = DateUtils.getCurrentTimeSlot(); xpOrder.setTimeSlot(currentTimeSlot); xpOrder.setCreateTime(LocalDateTime.now()); xpOrder.setCityCode(openCity.getCityCode()); List data = xpTestPlanClient.getXpTestPlanVehicleByTestPlanId(query.getTestPlanId()).getData(); for (XpTestPlanVehicle datum : data) { XpVehicle xpVehicle = xpVehicleClient.queryById(datum.getVehicleId()).getData(); xpOrder.setVehicleId(datum.getVehicleId()); xpOrder.setFleetId(xpVehicle.getFleetId()); this.save(xpOrder); //缓存导航数据,用于下单后推送导航给车端和乘客端 redisService.setCacheObject("TEST_ORDER_NAVIGATION_END_DATA:" + datum.getVehicleId(), JSON.toJSONString(routeVo), 24L, TimeUnit.HOURS); //下发XPU消息 PushOrderDto pushOrderDto = new PushOrderDto(); pushOrderDto.setVin(xpVehicle.getVehicleVin()); pushOrderDto.setOrderId("TEST" + xpOrder.getId()); pushOrderDto.setStartLatitude(xpOrder.getStartLat()); pushOrderDto.setStartLongitude(xpOrder.getStartLon()); pushOrderDto.setEndLatitude(xpOrder.getEndLat()); pushOrderDto.setEndLongitude(xpOrder.getEndLon()); pushOrderDto.setOrderType(OrderType.USER_ORDER); pushOrderDto.setStage(1); // 调用推送服务 orderId + carVin orderMissionClient.pushOrder(pushOrderDto); JSONObject jsonObject = new JSONObject(); jsonObject.put("event", SystemEventType.CREATE_ORDER); jsonObject.put("orderType", OrderType.TEST_ORDER); jsonObject.put("orderId", xpOrder.getId()); jsonObject.put("vin", xpVehicle.getVehicleVin()); redisService.setCacheObject("CREATE_ORDER:" + pushOrderDto.getOrderId(), jsonObject.toJSONString(),30L, TimeUnit.MINUTES); log.info("开始推送 {} 订单到XPU。", xpOrder.getOrderNumber()); } } 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(XpTestOrderPageQuery query) { // 0. 车牌号筛选:先查车辆服务获取车辆ID列表 List vehicleIdsFilter = null; if (StringUtils.isNotBlank(query.getPlateNumber())) { R> vehicleResult = xpVehicleClient.listByPlateNumber(query.getPlateNumber()); if (vehicleResult == null || CollectionUtils.isEmpty(vehicleResult.getData())) { return buildEmptyOrderPage(query); } vehicleIdsFilter = vehicleResult.getData().stream() .map(XpVehicle::getId) .filter(Objects::nonNull) .distinct() .collect(Collectors.toList()); if (CollectionUtils.isEmpty(vehicleIdsFilter)) { return buildEmptyOrderPage(query); } } // 1. 构建查询条件 LambdaQueryWrapper wrapper = buildQueryWrapper(query); if (CollUtil.isNotEmpty(vehicleIdsFilter)) { wrapper.in(XpTestOrder::getVehicleId, vehicleIdsFilter); } wrapper.orderByDesc(XpTestOrder::getCreateTime); // 2. 分页查询 Page page = new Page<>(query.getPageNum(), query.getPageSize()); Page pageResult = this.page(page, wrapper); if (CollectionUtils.isEmpty(pageResult.getRecords())) { return buildEmptyOrderPage(query); } // 3. 收集关联ID Set vehicleIds = new HashSet<>(); Set fleetIds = new HashSet<>(); for (XpTestOrder order : pageResult.getRecords()) { if (Objects.nonNull(order.getVehicleId())) { vehicleIds.add(order.getVehicleId()); } if (Objects.nonNull(order.getFleetId())) { fleetIds.add(order.getFleetId()); } } // 4. 批量查询关联数据 Map vehicleMap = batchGetVehicles(vehicleIds); Map fleetMap = batchGetFleets(fleetIds); // 5. 组装 VO List voList = pageResult.getRecords().stream() .map(order -> convertToPageVO(order, vehicleMap, fleetMap)) .collect(Collectors.toList()); // 6. 返回分页结果 PageInfo resultPage = new PageInfo<>(query.getPageNum(), query.getPageSize()); resultPage.setTotal(pageResult.getTotal()); resultPage.setRecords(voList); return resultPage; } private PageInfo buildEmptyOrderPage(XpTestOrderPageQuery query) { PageInfo resultPage = new PageInfo<>(query.getPageNum(), query.getPageSize()); resultPage.setTotal(0L); resultPage.setRecords(new ArrayList<>()); return resultPage; } @Override public XpTestOrderDetailVO getOrderDetail(Long id) { // 1. 查询订单基础信息 XpTestOrder order = this.getById(id); if (order == null) { throw new ServiceException("订单不存在"); } // 2. 转换为详情 VO XpTestOrderDetailVO vo = BeanUtil.toBean(order, XpTestOrderDetailVO.class); // 4. 获取车辆信息 if (Objects.nonNull(order.getVehicleId())) { Map vehicleMap = batchGetVehicles(Collections.singleton(order.getVehicleId())); XpVehicle vehicle = vehicleMap.get(order.getVehicleId()); if (Objects.nonNull(vehicle)) { vo.setPlateNumber(vehicle.getPlateNumber()); vo.setVehicleModel(vehicle.getVehicleModel()); } } // 5. 获取车队信息 if (Objects.nonNull(order.getFleetId())) { Map fleetMap = batchGetFleets(Collections.singleton(order.getFleetId())); XpFleet fleet = fleetMap.get(order.getFleetId()); if (Objects.nonNull(fleet)) { vo.setFleetName(fleet.getFleetName()); } } // 6. 获取评价信息 LambdaQueryWrapper evaluateWrapper = new LambdaQueryWrapper<>(); evaluateWrapper.eq(XpOrderEvaluate::getOrderId, id); XpOrderEvaluate evaluate = xpOrderEvaluateService.getOne(evaluateWrapper.orderByDesc(XpOrderEvaluate::getCreateTime).last(" limit 0,1")); if (Objects.nonNull(evaluate)) { vo.setOrderRating(evaluate.getOrderRating()); } // 7. 查询mongodb 该订单上传的经纬度轨迹信息(按订单id) List trajectories = queryOrderTrajectories(id); vo.setOrderPositionList(convertTrajectoriesToPositions(trajectories)); // 8. 起步价、里程费、时长费、远途费:按订单费用明细聚合 Map feeMap = sumOrderDetailMoneyByDetailType(id); vo.setStartMoney(feeMap.getOrDefault(1, BigDecimal.ZERO)); vo.setMileageMoney(feeMap.getOrDefault(2, BigDecimal.ZERO)); vo.setDurationMoney(feeMap.getOrDefault(3, BigDecimal.ZERO)); vo.setLongDistancemoney(feeMap.getOrDefault(4, BigDecimal.ZERO)); return vo; } @Override public void deleteOrder(Long id) { // 1. 校验订单是否存在 XpTestOrder order = this.getById(id); if (Objects.isNull(order)) { throw new ServiceException("订单不存在", I18nKeyConstant.ORDER_NOT_EXISTS); } this.removeById(id); } /** * 构建查询条件 */ private LambdaQueryWrapper buildQueryWrapper(XpTestOrderPageQuery query) { LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.like(StringUtils.isNotBlank(query.getOrderNumber()), XpTestOrder::getOrderNumber, query.getOrderNumber()) .eq(Objects.nonNull(query.getStatus()), XpTestOrder::getStatus, query.getStatus()); if (Objects.nonNull(query.getStartTime())) { wrapper.ge(XpTestOrder::getCreateTime, LocalDateTime.of(query.getStartTime(), LocalTime.MIN)); } if (Objects.nonNull(query.getEndTime())) { wrapper.le(XpTestOrder::getCreateTime, LocalDateTime.of(query.getEndTime(), LocalTime.MAX)); } wrapper.orderByDesc(XpTestOrder::getCreateTime); return wrapper; } /** * 批量获取车辆信息 */ private Map batchGetVehicles(Set vehicleIds) { if (CollectionUtils.isEmpty(vehicleIds)) { return new HashMap<>(); } try { R> result = xpVehicleClient.listByIds(new ArrayList<>(vehicleIds)); if (Objects.nonNull(result) && Objects.nonNull(result.getData())) { return result.getData().stream() .collect(Collectors.toMap(XpVehicle::getId, Function.identity(), (a, b) -> a)); } } catch (Exception e) { log.error("批量获取车辆信息失败: {}", e.getMessage()); } return new HashMap<>(); } /** * 批量获取车队信息 */ private Map batchGetFleets(Set fleetIds) { if (CollectionUtils.isEmpty(fleetIds)) { return new HashMap<>(); } try { R> result = xpFleetClient.listByIds(new ArrayList<>(fleetIds)); if (Objects.nonNull(result) && Objects.nonNull(result.getData())) { return result.getData().stream() .collect(Collectors.toMap(XpFleet::getId, Function.identity(), (a, b) -> a)); } } catch (Exception e) { log.error("批量获取车队信息失败: {}", e.getMessage()); } return new HashMap<>(); } @Override @Transactional @ShardingSphereTransactionType(TransactionType.BASE) public void cancelOrCloseOrder(Long orderId, Integer type) { // 1. 校验订单是否存在 XpTestOrder order = this.getById(orderId); if (Objects.isNull(order)) { throw new ServiceException("订单不存在"); } // 2. 校验操作类型 if (type != 1 && type != 2) { throw new ServiceException("操作类型错误,type只能为1或2"); } // 3. 校验订单状态是否允许操作 Integer currentStatus = order.getStatus(); //取消订单:在未接到乘客前都可以,关闭订单,在送达目的地可以操作关闭 if (type == 1) { // 取消订单:只有待接单(1)、待出发(2)、接驾中(3)状态可以取消 if (currentStatus != 1 && currentStatus != 2 && currentStatus != 3 && currentStatus != 4) { throw new ServiceException("当前订单状态不允许取消,只有待接单、待出发、接驾中状态的订单可以取消", I18nKeyConstant.ORDER_CANCEL_FAILED); } } else { // 关闭订单:0=已取消,1=待接单,2=待出发,3=接驾中,4=待客上车,5=乘客已上车,6=服务中 状态可以关闭 if (currentStatus == 7 || currentStatus == 8 || currentStatus == 9 || currentStatus == 10) { throw new ServiceException("当前订单状态不允许关闭", I18nKeyConstant.ORDER_CLOSE_FAILED); } } // 4. 执行操作 if (type == 1) { // 取消订单 cancelOrder(order); } else { // 关闭订单 closeOrder(order); } } /** * 转换为分页列表 VO */ private XpTestOrderPageVO convertToPageVO(XpTestOrder order, Map vehicleMap, Map fleetMap) { XpTestOrderPageVO vo = new XpTestOrderPageVO(); vo.setId(order.getId()); vo.setOrderNumber(order.getOrderNumber()); vo.setCreateTime(order.getCreateTime()); vo.setOrderSource(order.getOrderSource()); vo.setStartAddress(order.getStartAddress()); vo.setEndAddress(order.getEndAddress()); vo.setStatus(order.getStatus()); // 车辆信息 if (Objects.nonNull(order.getVehicleId()) && vehicleMap.containsKey(order.getVehicleId())) { XpVehicle vehicle = vehicleMap.get(order.getVehicleId()); vo.setPlateNumber(vehicle.getPlateNumber()); } // 车队信息 if (Objects.nonNull(order.getFleetId()) && fleetMap.containsKey(order.getFleetId())) { XpFleet fleet = fleetMap.get(order.getFleetId()); vo.setFleetName(fleet.getFleetName()); } return vo; } /** * 取消订单 */ private void cancelOrder(XpTestOrder order) { redisService.removeByPattern(order.getQueueName(), order.getOrderNumber()); //判断还原车辆状态 if (null == order.getVehicleId()) { Object cacheObject1 = redisService.getCacheObject("CREATE_ORDER:" + order.getId().toString()); if (null != cacheObject1) { JSONObject jsonObject = JSON.parseObject(cacheObject1.toString()); String vin = jsonObject.getString("vin"); XpVehicle vehicle = xpVehicleClient.findByVehicleVin(vin).getData(); if (null != vehicle && 0 != vehicle.getState()) { XpVehicle vehicle1 = new XpVehicle(); vehicle1.setId(vehicle.getId()); vehicle1.setState(0); xpVehicleClient.updateVehicleById(vehicle1); } } } // 1. 更新订单状态为已取消(0) lambdaUpdate().set(XpTestOrder::getStatus, 0) .eq(XpTestOrder::getId, order.getId()).update(); // 2. 保存取消记录 XpTestOrderCancel orderCancel = new XpTestOrderCancel(); orderCancel.setOrderId(order.getId()); orderCancel.setCancelSource(3); // 3=运营人员(后台管理接口固定为运营人员) orderCancel.setUserId(SecurityUtils.getUserId()); // 当前登录用户ID orderCancel.setCancelReason("运营人员取消订单"); xpTestOrderCancelService.save(orderCancel); //查询车辆信息 R xpVehicleR = xpVehicleClient.queryById(order.getVehicleId()); if (xpVehicleR.getCode() == HttpStatus.SUCCESS && Objects.nonNull(xpVehicleR.getData())) { XpVehicle xpVehicle = xpVehicleR.getData(); //还原车辆状态 if(1 == xpVehicle.getState()){ XpVehicle vehicle1 = new XpVehicle(); vehicle1.setId(xpVehicle.getId()); vehicle1.setState(0); xpVehicleClient.updateVehicleById(vehicle1); } // 下发命令 PushOrderDto pushOrderDto = new PushOrderDto(); pushOrderDto.setOrderId(order.getId().toString()); pushOrderDto.setVin(xpVehicle.getVehicleVin()); pushOrderDto.setOperation_type(OrderOperationType.CLOUD_CANCEL_ORDER); orderMissionClient.cancelOrder(pushOrderDto); log.info("运营人员取消订单成功,开始执行巡游订单 {}", order.getId()); //车辆继续巡游 addCruiseMission(xpVehicle.getId()); } else { log.info("查询车辆信息失败,订单ID: {}, 下发取消订单命令失败", order.getId()); } log.info("订单取消成功,订单ID: {}, 操作人: {}", order.getId(), SecurityUtils.getUsername()); } /** * 关闭订单 */ private void closeOrder(XpTestOrder order) { XpVehicle xpVehicle = xpVehicleClient.queryById(order.getVehicleId()).getData(); order.setStatus(7); order.setGetoffTime(LocalDateTime.now()); // 解析下车地点 VehiclePosition vehiclePosition = mongoTemplate.findOne(new Query(Criteria.where("vin").is(xpVehicle.getVehicleVin())), VehiclePosition.class); if(Objects.nonNull(vehiclePosition)){ GeoJsonPoint point = vehiclePosition.getPoint(); if(Objects.nonNull(point)){ double x = point.getX(); double y = point.getY(); Map geocode = gdMapGeocodingUtil.geocode(String.valueOf(x), String.valueOf(y)); order.setGetoffAddress(geocode.get("address")); } } //计算行驶总距离和时长 List orderTrajectoryList = mongoTemplate.find(new Query().addCriteria(Criteria.where("orderId").is(order.getId())), OrderTrajectory.class); Double reduce = orderTrajectoryList.stream().map(OrderTrajectory::getDistance).reduce(0.0, Double::sum); order.setMileageKilometers(new BigDecimal(reduce).divide(new BigDecimal(1000)).setScale(4, RoundingMode.HALF_EVEN).doubleValue()); long duration = LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8")) - order.getTripBegin().toEpochSecond(ZoneOffset.of("+8")); order.setDuration(new BigDecimal(duration).divide(new BigDecimal(60), 2, RoundingMode.HALF_EVEN).doubleValue()); order.setVehicleId(null); this.updateById(order); //给XPU推送订单取消的报文 PushOrderDto pushOrderDto = new PushOrderDto(); pushOrderDto.setOrderId(order.getId().toString()); pushOrderDto.setVin(xpVehicle.getVehicleVin()); pushOrderDto.setOperation_type(OrderOperationType.CLOUD_CANCEL_ORDER); orderMissionClient.cancelOrder(pushOrderDto); //结束行程,修改状态和计算费用 endTrip(order.getId()); //创建定时任务执行延迟驶离终点和下发巡游单 XpTestPoint point = xpTestPointClient.queryById(order.getEndTestPointId()).getData(); Map jobMaps = new ImmutableMap.Builder() .put("id", order.getId().toString()) .put("appid", appid) .build(); QuartzManager.addJob( AddCruiseMissionJob.class, (AddCruiseMissionJob.name + order.getId()).toUpperCase(), TimeJobType.ADD_CRUISE_MISSION, DateUtils.localDateTimeToDate(LocalDateTime.now().plusMinutes(point.getParkingTimeLimit())), jobMaps ); } /** * 改派订单 */ @Override @Transactional @ShardingSphereTransactionType(TransactionType.BASE) public void reassignOrder(Long orderId, Long newVehicleId, String reassignReason) { // 1. 校验订单是否存在 XpTestOrder order = this.getById(orderId); if (Objects.isNull(order)) { throw new ServiceException("订单不存在"); } // 2. 校验订单状态(只有待出发、接驾中、待乘客上车状态可以改派) Integer currentStatus = order.getStatus(); if (currentStatus != 2 && currentStatus != 3 && currentStatus != 4) { throw new ServiceException("当前订单状态不允许改派,只有待出发、接驾中、待乘客上车状态的订单可以改派", I18nKeyConstant.ORDER_REASSIGN_FAILED); } // 3. 校验新旧车辆是否相同 if (Objects.equals(order.getVehicleId(), newVehicleId)) { throw new ServiceException("新车辆与原车辆相同,无需改派", I18nKeyConstant.ORDER_REASSIGN_VEHICLE_FAILED); } // 4. 查询新车辆信息并校验 R> vehicleResult = xpVehicleClient.listByIds(Collections.singletonList(newVehicleId)); if (Objects.isNull(vehicleResult) || Objects.isNull(vehicleResult.getData()) || vehicleResult.getData().isEmpty()) { throw new ServiceException("新车辆不存在", I18nKeyConstant.ORDER_REASSIGN_NEW_VEHICLE_NOT_EXISTS); } XpVehicle newVehicle = vehicleResult.getData().get(0); // 5. 校验新车辆的运营状态 // 车辆状态:1=待完善 2=待运营 3=保险/年检过期 // 运营状态:1=运营中 2=维修中 3=已收车 4=车辆异常 // 当前模式:1=接单 2=测试 // 模式状态:1=开始接单/开始测试 2=停止接单/停止测试 if (Objects.isNull(newVehicle.getStatus()) || newVehicle.getStatus() == 3) { throw new ServiceException("新车辆保险或年检已过期,无法接单", I18nKeyConstant.ORDER_REASSIGN_NEW_VEHICLE_STATUS_INVALID); } if (Objects.isNull(newVehicle.getOperatingStatus()) || newVehicle.getOperatingStatus() != 1) { throw new ServiceException("新车辆运营状态异常,无法接单", I18nKeyConstant.ORDER_REASSIGN_NEW_VEHICLE_OPERATING_STATUS_INVALID); } if (Objects.isNull(newVehicle.getAcceptOrTest()) || newVehicle.getAcceptOrTest() != 1) { throw new ServiceException("新车辆当前不在接单模式", I18nKeyConstant.ORDER_REASSIGN_NEW_VEHICLE_NOT_IN_ACCEPT_MODE); } if (Objects.isNull(newVehicle.getAcceptOrTestStatus()) || newVehicle.getAcceptOrTestStatus() != 1) { throw new ServiceException("新车辆未开始接单", I18nKeyConstant.ORDER_REASSIGN_NEW_VEHICLE_NOT_START_ACCEPT); } if (Objects.isNull(newVehicle.getState()) || newVehicle.getState() != 0) { throw new ServiceException("新车辆已接单,请重新选择", I18nKeyConstant.ORDER_REASSIGN_NEW_VEHICLE_NOT_FREE); } // 6. 保存原车辆和车队信息 Long oldVehicleId = order.getVehicleId(); Long oldFleetId = order.getFleetId(); // 6.1 处理原车辆:取消订单指令、解绑乘客、更新状态、创建返程巡游订单 if (Objects.nonNull(oldVehicleId)) { try { R oldVehicleResult = xpVehicleClient.queryById(oldVehicleId); if (oldVehicleResult != null && oldVehicleResult.getCode() == HttpStatus.SUCCESS && oldVehicleResult.getData() != null) { XpVehicle oldVehicle = oldVehicleResult.getData(); // 6.1.1 取消原订单指令 PushOrderDto cancelDto = new PushOrderDto(); cancelDto.setOrderId(order.getId().toString()); cancelDto.setVin(oldVehicle.getVehicleVin()); cancelDto.setOperation_type(OrderOperationType.CLOUD_CANCEL_ORDER); orderMissionClient.cancelOrder(cancelDto); log.info("已取消原车辆订单指令,订单ID: {}, VIN: {}", orderId, oldVehicle.getVehicleVin()); // 6.1.3 修改原车辆状态为空闲 XpVehicle vehicle1 = new XpVehicle(); vehicle1.setId(oldVehicle.getId()); vehicle1.setState(0); xpVehicleClient.updateVehicleById(vehicle1); log.info("已将原车辆状态设为空闲,车辆ID: {}", oldVehicleId); } else { log.warn("查询原车辆信息失败,车辆ID: {}", oldVehicleId); } } catch (Exception e) { log.error("处理原车辆失败,车辆ID: {},错误: {}", oldVehicleId, e.getMessage()); } } // 7. 查询新车辆关联的车队ID Long newFleetId = null; try { R fleetIdResult = xpFleetVehicleClient.getFleetIdByVehicleId(newVehicleId); if (Objects.nonNull(fleetIdResult) && Objects.nonNull(fleetIdResult.getData())) { newFleetId = fleetIdResult.getData(); log.info("查询到新车辆ID: {} 关联的车队ID: {}", newVehicleId, newFleetId); } else { log.warn("新车辆ID: {} 未关联任何车队,将使用原车队ID: {}", newVehicleId, oldFleetId); newFleetId = oldFleetId; } } catch (Exception e) { log.error("查询新车辆关联的车队ID失败,车辆ID: {},错误: {}", newVehicleId, e.getMessage()); newFleetId = oldFleetId; } // 8. 如果旧订单的车队ID为空,尝试查询旧车辆的车队ID if (Objects.isNull(oldFleetId) && Objects.nonNull(oldVehicleId)) { try { R oldFleetIdResult = xpFleetVehicleClient.getFleetIdByVehicleId(oldVehicleId); if (Objects.nonNull(oldFleetIdResult) && Objects.nonNull(oldFleetIdResult.getData())) { oldFleetId = oldFleetIdResult.getData(); log.info("补充查询到旧车辆ID: {} 关联的车队ID: {}", oldVehicleId, oldFleetId); } } catch (Exception e) { log.warn("查询旧车辆关联的车队ID失败,车辆ID: {},错误: {}", oldVehicleId, e.getMessage()); } } // 9. 更新订单的车辆和车队信息 order.setVehicleId(newVehicleId); if (Objects.nonNull(newFleetId)) { order.setFleetId(newFleetId); } this.updateById(order); // 10. 保存改派记录 XpOrderReassign orderReassign = new XpOrderReassign(); orderReassign.setOrderId(orderId); orderReassign.setUserId(SecurityUtils.getUserId()); orderReassign.setOperationTime(LocalDateTime.now()); orderReassign.setOldVehicleId(oldVehicleId); orderReassign.setNewVehicleId(newVehicleId); orderReassign.setOldFleetId(oldFleetId); orderReassign.setNewFleetId(newFleetId); orderReassign.setReassignReason( StringUtils.isNotBlank(reassignReason) ? reassignReason : "运营人员改派订单" ); orderReassign.setCreateBy(SecurityUtils.getLoginUser().getSysUser().getNickName()); xpOrderReassignService.save(orderReassign); log.info("订单改派成功,订单ID: {}, 原车辆ID: {}, 新车辆ID: {}, 原车队ID: {}, 新车队ID: {}, 操作人: {}", orderId, oldVehicleId, newVehicleId, oldFleetId, newFleetId, SecurityUtils.getUsername()); // 11. 处理新车辆:绑定乘客、下发订单、更新状态 try { // 11.2 下发订单到新车辆 PushOrderDto pushOrderDto = new PushOrderDto(); pushOrderDto.setVin(newVehicle.getVehicleVin()); pushOrderDto.setOrderId(order.getId().toString()); pushOrderDto.setStartLatitude(order.getStartLat()); pushOrderDto.setStartLongitude(order.getStartLon()); pushOrderDto.setEndLatitude(order.getEndLat()); pushOrderDto.setEndLongitude(order.getEndLon()); pushOrderDto.setOrderType(OrderType.USER_ORDER); pushOrderDto.setStage(1); // 调用推送服务 orderId + carVin orderMissionClient.pushOrder(pushOrderDto); log.info("已下发订单到新车辆,订单ID: {}, VIN: {}", orderId, newVehicle.getVehicleVin()); // 11.3 更新新车辆状态为已接单 XpVehicle vehicle1 = new XpVehicle(); vehicle1.setId(newVehicle.getId()); vehicle1.setState(1); xpVehicleClient.updateVehicleById(vehicle1); log.info("已将新车辆状态设为已接单,车辆ID: {}", newVehicleId); } catch (Exception e) { log.error("处理新车辆失败,车辆ID: {},错误: {}", newVehicleId, e.getMessage()); } } @Override public R updateOrderStatus(Long orderId, Integer status) { XpTestOrder xpOrder = this.getById(orderId); if (Objects.isNull(xpOrder)) { return R.fail("订单不存在"); } if (xpOrder.getStatus() == 0) { return R.fail("订单已取消"); } if(xpOrder.getStatus() >= 8){ return R.fail("订单已结束"); } // 查询车辆 XpVehicle xpVehicle = xpVehicleClient.queryById(xpOrder.getVehicleId()).getData(); if(xpOrder.getOrderType() == 1){ switch (status) { case 4: // 查询起点点位 Long startPointId = xpOrder.getStartTestPointId(); XpTestPoint xpPoint = xpTestPointClient.queryById(startPointId).getData(); // 添加取消订单定时任务job 时间为点位限停时长 Map cancelJobMaps = new ImmutableMap.Builder(). put("id", xpOrder.getId()) .build(); QuartzManager.addJob( OrderCancelJob.class, (OrderCancelJob.name + xpOrder.getId()).toUpperCase(), TimeJobType.ORDER_CANCEL, DateUtils.localDateTimeToDate(LocalDateTime.now().plusMinutes(xpPoint.getParkingTimeLimit())), cancelJobMaps ); // 车辆到达上车点时间 xpOrder.setArrivalTime(LocalDateTime.now()); break; case 5: LocalDateTime now = LocalDateTime.now(); xpOrder.setBoardingTime(now); xpOrder.setRideTime(now); // 删除定时任务job QuartzManager.removeJob((OrderCancelJob.name + xpOrder.getId()).toUpperCase(), TimeJobType.ORDER_CANCEL); break; case 6: // 行程开始时间 xpOrder.setTripBegin(LocalDateTime.now()); break; case 7: xpOrder.setGetoffTime(LocalDateTime.now()); // 解析下车地点 VehiclePosition vehiclePosition = mongoTemplate.findOne(new Query(Criteria.where("vin").is(xpVehicle.getVehicleVin())), VehiclePosition.class); if(Objects.nonNull(vehiclePosition)){ GeoJsonPoint point = vehiclePosition.getPoint(); if(Objects.nonNull(point)){ double x = point.getX(); double y = point.getY(); Map geocode = gdMapGeocodingUtil.geocode(String.valueOf(x), String.valueOf(y)); xpOrder.setGetoffAddress(geocode.get("address")); } } //计算行驶总距离和时长 List orderTrajectoryList = mongoTemplate.find(new Query().addCriteria(Criteria.where("orderId").is(xpOrder.getId())), OrderTrajectory.class); Double reduce = orderTrajectoryList.stream().map(OrderTrajectory::getDistance).reduce(0.0, Double::sum); xpOrder.setMileageKilometers(new BigDecimal(reduce).divide(new BigDecimal(1000)).setScale(4, RoundingMode.HALF_EVEN).doubleValue()); long duration = LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8")) - xpOrder.getTripBegin().toEpochSecond(ZoneOffset.of("+8")); xpOrder.setDuration(new BigDecimal(duration).divide(new BigDecimal(60), 2, RoundingMode.HALF_EVEN).doubleValue()); //创建定时结束订单和等待结束后下发巡游单的定时任务 Integer arriveTime = xpSystemSettingClient.getOrderFinishTime().getData(); // 添加自动结束行程定时任务job Map endTripJobMaps = new ImmutableMap.Builder() .put("id", xpOrder.getId().toString()) .put("appid", appid) .build(); QuartzManager.addJob( EndTripJob.class, (EndTripJob.name + xpOrder.getId()).toUpperCase(), TimeJobType.END_TRIP, DateUtils.localDateTimeToDate(LocalDateTime.now().plusMinutes(arriveTime)), endTripJobMaps ); // 添加车辆到达终点后限停时间到后驶离的定时任务 //时间到后将车辆状态回滚到空闲、添加巡游订单,解绑用户和车辆关系,车辆驶离点位 XpTestPoint point = xpTestPointClient.queryById(xpOrder.getEndTestPointId()).getData(); Map jobMaps = new ImmutableMap.Builder() .put("id", xpOrder.getId().toString()) .put("appid", appid) .build(); QuartzManager.addJob( AddCruiseMissionJob.class, (AddCruiseMissionJob.name + xpOrder.getId()).toUpperCase(), TimeJobType.ADD_CRUISE_MISSION, DateUtils.localDateTimeToDate(LocalDateTime.now().plusMinutes(point.getParkingTimeLimit())), jobMaps ); break; } xpOrder.setStatus(status); xpOrder.setVehicleId(null); this.updateById(xpOrder); redisService.setCacheObject("ORDER_STATUS:" + xpOrder.getId(), status, 24L, TimeUnit.HOURS); }else{ if(status == 10){ xpOrder.setStatus(status); xpOrder.setVehicleId(null); this.updateById(xpOrder); redisService.setCacheObject("ORDER_STATUS:" + xpOrder.getId(), status, 24L, TimeUnit.HOURS); } } return R.ok(); } @Override public Boolean endTrip(Long orderId) { List xpOrderDetails = new ArrayList<>(); // 起步价格明细 List start = new ArrayList<>(); // 里程费价格明细 List mileage = new ArrayList<>(); // 时长费价格明细 List duration = new ArrayList<>(); // 远途费价格明细 List longDistance = new ArrayList<>(); XpTestOrder order = this.getById(orderId); if (order == null) { throw new RuntimeException("订单不存在"); } if (order.getStatus() != 7) { throw new RuntimeException("订单状态错误"); } order.setStatus(8); order.setVehicleId(null); order.setTripEnd(LocalDateTime.now()); baseMapper.updateById(order); redisService.setCacheObject("ORDER_STATUS:" + order.getId(), 8, 24L, TimeUnit.HOURS); // 根据订单所属开通城市id 查询定价 R pricingByCityId = xpPricingClient.getPricingByCid(order.getCityId()); XpPricing pricing = pricingByCityId.getData(); if (pricing == null) { throw new ServiceException("当前城市未配置定价规则", I18nKeyConstant.ORDER_PRICING_NOT_EXISTS); } // 费用 // 查询订单轨迹 List trajectories = mongoTemplate.find( new Query(Criteria.where("orderId").is(orderId)) .with(Sort.by(Sort.Direction.ASC, "locatetime")), OrderTrajectory.class ); List gpsPoints = new ArrayList<>(); for (OrderTrajectory trajectory : trajectories) { TaxiPricingDemoService.GpsPoint point = new TaxiPricingDemoService.GpsPoint(trajectory.getDistance(), trajectory.getLocatetime()); gpsPoints.add(point); } TaxiPricingDemoService.PricingConfig config = new TaxiPricingDemoService.PricingConfig(); JSONArray startingFareJsonArray = JSONArray.parseArray(pricing.getStartingFareJson()); LocalDateTime boardingTime = order.getBoardingTime(); Date date = DateUtils.localDateTimeToDate(boardingTime); double paramNum4 = 0.0; double paramNum5 = 0.0; //一口价 if (pricing.getPricingType() == 2) { Double mileageKilometers = order.getMileageKilometers(); Double duration1 = order.getDuration(); JSONObject basicPricingJson = JSONObject.parseObject(pricing.getBasicPricingJson()); XpPricingCardVo xpPricingCardVo = xpPricingUtil.calculationFixedPrice(uid, basicPricingJson, null, mileageKilometers, duration1, 0, 0.0, pricing.getIsDynamicPrice(), order.getCityId(), pricing.getDynamicPricingJson()); XpTestOrderDetail xpOrderDetail = new XpTestOrderDetail(); xpOrderDetail.setDetailType(1); xpOrderDetail.setOrderId(orderId); xpOrderDetail.setType(1); xpOrderDetail.setMileage(new BigDecimal(mileageKilometers).setScale(2, RoundingMode.HALF_UP)); String currentTimeSlot = DateUtils.getCurrentTimeSlot(); xpOrderDetail.setTime(currentTimeSlot); xpOrderDetail.setDuration(duration1); xpOrderDetail.setMoney(new BigDecimal(xpPricingCardVo.getStartPrice()).setScale(2, RoundingMode.HALF_UP)); xpOrderDetails.add(xpOrderDetail); xpOrderDetail = new XpTestOrderDetail(); xpOrderDetail.setDetailType(2); xpOrderDetail.setOrderId(orderId); xpOrderDetail.setType(1); xpOrderDetail.setMoney(new BigDecimal(xpPricingCardVo.getDistancePrice()).setScale(2,RoundingMode.HALF_UP)); xpOrderDetails.add(xpOrderDetail); xpOrderDetail = new XpTestOrderDetail(); xpOrderDetail.setDetailType(3); xpOrderDetail.setOrderId(orderId); xpOrderDetail.setType(1); xpOrderDetail.setMoney(new BigDecimal(xpPricingCardVo.getDurationPrice()).setScale(2, RoundingMode.HALF_UP)); xpOrderDetails.add(xpOrderDetail); BigDecimal totalPrice =BigDecimal.ZERO; for (XpTestOrderDetail xpOrderDetail1 : xpOrderDetails) { totalPrice = totalPrice.add(xpOrderDetail1.getMoney()); xpTestOrderDetailMapper.insert(xpOrderDetail1); } order.setOrderMoney(totalPrice); order.setVehicleId(null); baseMapper.updateById(order); } else { //动态计费 if (pricing.getPricingType() == 1) { for (int i = 0; i < startingFareJsonArray.size(); i++) { JSONObject jsonObject = startingFareJsonArray.getJSONObject(i); String num1 = jsonObject.getString("num1"); String num2 = jsonObject.getString("num2"); String num3 = jsonObject.getString("num3"); String num4 = jsonObject.getString("num4"); String num5 = jsonObject.getString("num5"); String num6 = jsonObject.getString("num6"); String num7 = jsonObject.getString("num7"); config.normals.add(new TaxiPricingDemoService.PricingRule("普通时段", num1, num2, Double.parseDouble(num3), Double.parseDouble(num4), Double.parseDouble(num5), Double.parseDouble(num6), Double.parseDouble(num7))); // 判断当前是那个时段 Calendar calendarStart = Calendar.getInstance(); calendarStart.setTime(date); calendarStart.set(Calendar.HOUR_OF_DAY, Integer.valueOf(jsonObject.getString("num1").split(":")[0])); calendarStart.set(Calendar.MINUTE, Integer.valueOf(jsonObject.getString("num1").split(":")[1])); Calendar calendarEnd = Calendar.getInstance(); calendarEnd.setTime(date); calendarEnd.set(Calendar.HOUR_OF_DAY, Integer.valueOf(jsonObject.getString("num2").split(":")[0])); calendarEnd.set(Calendar.MINUTE, Integer.valueOf(jsonObject.getString("num2").split(":")[1])); // 找到符合条件的直接跳出循环 if (date.getTime() > calendarStart.getTimeInMillis() && date.getTime() < calendarEnd.getTimeInMillis()) { paramNum4 = jsonObject.getDouble("num4"); paramNum5 = jsonObject.getDouble("num5"); break; } } JSONObject basicPricingJson = JSONObject.parseObject(pricing.getBasicPricingJson()); config.peaks.add(new TaxiPricingDemoService.PricingRule("高峰期", basicPricingJson.getString("num21").split(" - ")[0], basicPricingJson.getString("num21").split(" - ")[1], Double.parseDouble(basicPricingJson.getString("num23")), paramNum4, paramNum5, Double.parseDouble(basicPricingJson.getString("num24")), Double.parseDouble(basicPricingJson.getString("num25")))); config.peaks.add(new TaxiPricingDemoService.PricingRule("高峰期", basicPricingJson.getString("num22").split(" - ")[0], basicPricingJson.getString("num22").split(" - ")[1], Double.parseDouble(basicPricingJson.getString("num23")), paramNum4, paramNum5, Double.parseDouble(basicPricingJson.getString("num24")), Double.parseDouble(basicPricingJson.getString("num25")))); config.night = new TaxiPricingDemoService.PricingRule("夜间行驶", basicPricingJson.getString("num14").split(" - ")[0], basicPricingJson.getString("num14").split(" - ")[1], Double.parseDouble(basicPricingJson.getString("num15")), paramNum4, paramNum5, Double.parseDouble(basicPricingJson.getString("num16")), Double.parseDouble(basicPricingJson.getString("num17"))); config.holiday = new TaxiPricingDemoService.PricingRule("节假日", "00:00", "23:59", Double.parseDouble(basicPricingJson.getString("num29")), paramNum4, paramNum5, Double.parseDouble(basicPricingJson.getString("num30")), Double.parseDouble(basicPricingJson.getString("num31"))); // --- 远途加价区间配置 (关键修改) --- // 1. 超过 num1(12) 不足 num2(20), 加收 num3(1.2) config.longSteps.add(new TaxiPricingDemoService.LongRangeStep(paramNum4, Double.parseDouble(basicPricingJson.getString("num7")), Double.parseDouble(basicPricingJson.getString("num8")))); // 2. 超过 num4(20) 不足 num5(35), 加收 num6(2.0) config.longSteps.add(new TaxiPricingDemoService.LongRangeStep(Double.parseDouble(basicPricingJson.getString("num9")), Double.parseDouble(basicPricingJson.getString("num10")), Double.parseDouble(basicPricingJson.getString("num11")))); // 3. 超过 num7(35) 以后, 加收 num8(3.5) -> numEnd 传个极大值 config.longSteps.add(new TaxiPricingDemoService.LongRangeStep(Double.parseDouble(basicPricingJson.getString("num12")), 999999, Double.parseDouble(basicPricingJson.getString("num13")))); } TaxiPricingDemoService.TaxiPricingService service = new TaxiPricingDemoService.TaxiPricingService(); Integer holidayToday = holidayUtil.isHolidayToday(); Map bill = service.calculateOrderPrice(DateUtils.toDate(order.getBoardingTime()), order.getMileageKilometers(), gpsPoints, config, holidayToday); log.info("======= 订单计费账单 ======="); bill.forEach((detail, price) -> log.info(detail + ": " + price + " 元")); BigDecimal totalAmount = bill.values().stream().reduce(BigDecimal.ZERO, BigDecimal::add); log.info("--------------------------"); log.info("应付总额: " + totalAmount + " 元"); BigDecimal startMoney = BigDecimal.ZERO; // 默认费率1 double priceRate = 1; if (pricing.getIsDynamicPrice() == 1) { // 先判断是否已经调价过了 Object userDynamicPriceObj = redisService.getCacheObject("USER_DYNAMIC_PRICE_" + order.getCityId() + "_" + uid); if (userDynamicPriceObj != null) { // 是否调价过了 priceRate = Double.parseDouble(userDynamicPriceObj.toString()); } else { // 没有调过 //是否触发动态调价 redis获取 Object dynamicPriceObj = redisService.getCacheObject("DYNAMIC_PRICE_" + order.getCityId()); if (dynamicPriceObj != null) { JSONObject jsonObject = JSONObject.parseObject(pricing.getDynamicPricingJson()); // 触发动态调价 // value=时间 String timeStr = dynamicPriceObj.toString(); // 判断当前距离这个时间范围的新增订单数(排除取消) / 上浮数量 算出应该增加的价格率 Integer num = this.getOrderNum(new GetOrderNumDto(timeStr, order.getCityId())); // X单上升一次调价 Integer orderNum = getOrderNum(jsonObject, 4); // 需要调价的次数 int count = num / orderNum; if (count > 0) { // 计算每次上升的费率 Integer upRate = getOrderNum(jsonObject, 5); BigDecimal divide = new BigDecimal(upRate).divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP); for (int i = count; i > 0; i--) { // 当前费率+(费率*上升百分比) priceRate = new BigDecimal(priceRate).multiply(divide).add(new BigDecimal(priceRate)).setScale(2, RoundingMode.HALF_UP).doubleValue(); } // 冷却时间 Integer time = getOrderNum(jsonObject, 6); // 触发调价了 redisService.setCacheObject("USER_DYNAMIC_PRICE_" + order.getCityId() + "_" + uid, priceRate, time.longValue(), TimeUnit.MINUTES); } } } } // 封装费用明细数据 for (Map.Entry stringBigDecimalEntry : bill.entrySet()) { // 封装起步价 FareBreakdownDetailVo startDetail = new FareBreakdownDetailVo(); String key = stringBigDecimalEntry.getKey(); if (key.contains("起步价")) { startDetail.setTime(key.split("\\|")[1]); startDetail.setMoney(stringBigDecimalEntry.getValue()); startMoney = startMoney.add(stringBigDecimalEntry.getValue()); if (key.contains("普通时段")) { startDetail.setType(1); } else if (key.contains("夜间")) { startDetail.setType(2); } else if (key.contains("高峰")) { startDetail.setType(3); } else if (key.contains("节假")) { startDetail.setType(4); } String durationAndMileage = key.split("\\|")[2]; startDetail.setDuration(Double.valueOf(durationAndMileage.split("&")[0])); startDetail.setMileage(new BigDecimal(durationAndMileage.split("&")[1]).multiply(BigDecimal.valueOf(1000))); start.add(startDetail); } // 封装里程费 if (!key.contains("起步") && !key.contains("时长") && !key.contains("远途") && !key.contains("distance")) { FareBreakdownDetailVo mileageDetail = new FareBreakdownDetailVo(); if (key.contains("普通时段")) { mileageDetail.setType(1); String key1 = "distance普通时段|" + key.split("\\|")[1]; String key2 = "普通时段|" + key.split("\\|")[1]; BigDecimal distance = bill.get(key1); BigDecimal money = bill.get(key2); mileageDetail.setMileage(distance); order.setStartMoney(money.multiply(BigDecimal.valueOf(priceRate))); mileageDetail.setMoney(money.multiply(BigDecimal.valueOf(priceRate))); } else if (key.contains("夜间行驶")) { mileageDetail.setType(2); String key1 = "distance夜间行驶|" + key.split("\\|")[1]; String key2 = "夜间行驶|" + key.split("\\|")[1]; BigDecimal distance = bill.get(key1); BigDecimal money = bill.get(key2); mileageDetail.setMileage(distance); order.setStartMoney(money.multiply(BigDecimal.valueOf(priceRate))); mileageDetail.setMoney(money.multiply(BigDecimal.valueOf(priceRate))); } else if (key.contains("高峰期")) { mileageDetail.setType(3); String key1 = "distance高峰期|" + key.split("\\|")[1]; String key2 = "高峰期|" + key.split("\\|")[1]; BigDecimal distance = bill.get(key1); BigDecimal money = bill.get(key2); mileageDetail.setMileage(distance); order.setStartMoney(money.multiply(BigDecimal.valueOf(priceRate))); mileageDetail.setMoney(money.multiply(BigDecimal.valueOf(priceRate))); } else if (key.contains("节假")) { mileageDetail.setType(4); String key1 = "distance节假日|" + key.split("\\|")[1]; String key2 = "节假日|" + key.split("\\|")[1]; BigDecimal distance = bill.get(key1); BigDecimal money = bill.get(key2); mileageDetail.setMileage(distance); order.setStartMoney(money.multiply(BigDecimal.valueOf(priceRate))); mileageDetail.setMoney(money.multiply(BigDecimal.valueOf(priceRate))); } mileage.add(mileageDetail); } // 封装时长费 FareBreakdownDetailVo timeDetail = new FareBreakdownDetailVo(); if (key.contains("时长费")) { if(key.contains("普通时段")){ timeDetail.setType(1); } if(key.contains("夜间行驶")){ timeDetail.setType(2); } if(key.contains("高峰")){ timeDetail.setType(3); } if(key.contains("节假")){ timeDetail.setType(4); } timeDetail.setMoney(stringBigDecimalEntry.getValue()); timeDetail.setDuration(Double.valueOf(key.split("&")[1])); duration.add(timeDetail); } // 封装远途费 FareBreakdownDetailVo longDetail = new FareBreakdownDetailVo(); if (key.contains("远途费")) { longDetail.setType(startDetail.getType()); longDetail.setMoney(stringBigDecimalEntry.getValue()); // longDetail.setMileage(new BigDecimal(key.split("&")[1])); longDistance.add(longDetail); } } for (FareBreakdownDetailVo fareBreakdownDetailVo : start) { XpTestOrderDetail xpOrderDetail = new XpTestOrderDetail(); BeanUtils.copyProperties(fareBreakdownDetailVo, xpOrderDetail); xpOrderDetail.setOrderId(orderId); xpOrderDetail.setDetailType(1); xpOrderDetail.setCreateTime(LocalDateTime.now()); xpOrderDetails.add(xpOrderDetail); } BigDecimal mileageMoney = BigDecimal.ZERO; for (FareBreakdownDetailVo fareBreakdownDetailVo : mileage) { XpTestOrderDetail xpOrderDetail = new XpTestOrderDetail(); BeanUtils.copyProperties(fareBreakdownDetailVo, xpOrderDetail); xpOrderDetail.setOrderId(orderId); xpOrderDetail.setDetailType(2); xpOrderDetail.setCreateTime(LocalDateTime.now()); mileageMoney = mileageMoney.add(fareBreakdownDetailVo.getMoney()); xpOrderDetails.add(xpOrderDetail); } order.setMileageMoney(mileageMoney); for (FareBreakdownDetailVo fareBreakdownDetailVo : longDistance) { XpTestOrderDetail xpOrderDetail = new XpTestOrderDetail(); BeanUtils.copyProperties(fareBreakdownDetailVo, xpOrderDetail); xpOrderDetail.setOrderId(orderId); xpOrderDetail.setDetailType(4); xpOrderDetail.setCreateTime(LocalDateTime.now()); mileageMoney = mileageMoney.add(fareBreakdownDetailVo.getMoney()); xpOrderDetails.add(xpOrderDetail); } for (FareBreakdownDetailVo fareBreakdownDetailVo : duration) { XpTestOrderDetail xpOrderDetail = new XpTestOrderDetail(); BeanUtils.copyProperties(fareBreakdownDetailVo, xpOrderDetail); xpOrderDetail.setOrderId(orderId); xpOrderDetail.setDetailType(3); xpOrderDetail.setCreateTime(LocalDateTime.now()); mileageMoney = mileageMoney.add(fareBreakdownDetailVo.getMoney()); xpOrderDetails.add(xpOrderDetail); } for (XpTestOrderDetail xpOrderDetail : xpOrderDetails) { xpTestOrderDetailMapper.insert(xpOrderDetail); } for (FareBreakdownDetailVo fareBreakdownDetailVo : duration) { if (order.getDurationMoney() == null) { order.setDurationMoney(BigDecimal.ZERO); } BigDecimal add = order.getDurationMoney().add(fareBreakdownDetailVo.getMoney()); order.setDurationMoney(add); } BigDecimal orderMoney = startMoney.add(mileageMoney).add(order.getDurationMoney() != null ? order.getDurationMoney() : BigDecimal.ZERO) .add(order.getLongDistancemoney() != null ? order.getLongDistancemoney() : BigDecimal.ZERO) .add(order.getWaitMoney() != null ? order.getWaitMoney() : BigDecimal.ZERO); order.setOrderMoney(orderMoney); order.setVehicleId(null); baseMapper.updateById(order); } // 计算完价格后 挑选优惠券 GetUserCouponDto getUserCouponDto = new GetUserCouponDto(); getUserCouponDto.setUserId(Long.valueOf(uid)); getUserCouponDto.setTotalAmount(order.getOrderMoney().doubleValue()); getUserCouponDto.setUserCouponId(order.getCouponId()); getUserCouponDto.setOpenCityId(order.getCityId()); R userCouponVoR = xpAppUserCouponClient.getUserCoupon(getUserCouponDto); UserCouponVo userCouponVo = userCouponVoR.getData(); // 提示是否换券 boolean tip =false; if (userCouponVo != null) { // 重新选券 if(userCouponVo.getStatus()==2){ getUserCouponDto.setUserId(Long.valueOf(uid)); getUserCouponDto.setTotalAmount(order.getOrderMoney().doubleValue()); getUserCouponDto.setUserCouponId(order.getCouponId()); getUserCouponDto.setOpenCityId(order.getCityId()); userCouponVoR = xpAppUserCouponClient.getUserCoupon(getUserCouponDto); userCouponVo = userCouponVoR.getData(); tip=true; } order.setCouponId(userCouponVo.getId()); order.setCouponMoney(BigDecimal.valueOf(userCouponVo.getMoney())); } order.setVehicleId(null); baseMapper.updateById(order); return tip; } /** * 获取配置 4每X单 5上升X% 6冷却 * * @param rule * @param num * @return */ private Integer getOrderNum(JSONObject rule, int num) { Integer param = rule.getInteger("num" + num); return param; } /** * 获取订单数量 * * @param dto * @return */ @Override public Integer getOrderNum(GetOrderNumDto dto) { Long num = this.baseMapper.selectCount(new LambdaQueryWrapper().eq(XpTestOrder::getCityId, dto.getOpenCityId()).between(BaseModel::getCreateTime, dto.getTimeStr(), new Date())); return num.intValue(); } @Override public void saveVehicleLocation(OrderTrajectory orderTrajectory) { // 查询订单信息 Integer status = redisService.getCacheObject("ORDER_STATUS:" + orderTrajectory.getOrderId()); if(status < 6){ return; } // 计算距离 // 获取上一次定位 String oldLocation = redisService.getCacheObject(CacheConstants.ORDER_OLD_LOCATION + orderTrajectory.getOrderId()); if (StringUtils.isEmpty(oldLocation)) { orderTrajectory.setDistance(0D); // 存储mongo数据 mongoTemplate.save(orderTrajectory); // 存储首次定位 redisService.setCacheObject(CacheConstants.ORDER_OLD_LOCATION + orderTrajectory.getOrderId(), orderTrajectory.getLocation(), 1L, TimeUnit.HOURS); } else { Map distance = GeodesyUtil.getDistance(orderTrajectory.getLocation(), oldLocation); if (null != distance) { Double distance1 = distance.get("WGS84"); // 存储本次距离 orderTrajectory.setDistance(distance1); // 存储mongo数据 mongoTemplate.save(orderTrajectory); // 更新定位 redisService.setCacheObject(CacheConstants.ORDER_OLD_LOCATION + orderTrajectory.getOrderId(), orderTrajectory.getLocation(), 1L, TimeUnit.HOURS); } else { log.error("调用高德计算距离出错"); } } } /** * 查询订单轨迹(mongodb) * * @param orderId 订单id * @return 轨迹列表(按 locatetime 升序) */ private List queryOrderTrajectories(Long orderId) { if (orderId == null) { return new ArrayList<>(); } List trajectories = mongoTemplate.find( new Query(Criteria.where("orderId").is(orderId)) .with(Sort.by(Sort.Direction.ASC, "locatetime")), OrderTrajectory.class ); return trajectories; } /** * 订单费用明细聚合:按 detail_type 汇总 money * *

detail_type:1=起步价 2=里程费 3=时长费 4=远途费

*/ private Map sumOrderDetailMoneyByDetailType(Long orderId) { if (orderId == null) { return Collections.emptyMap(); } List detailList = xpTestOrderDetailMapper.selectList( new LambdaQueryWrapper() .eq(XpTestOrderDetail::getOrderId, orderId) .ne(XpTestOrderDetail::getIsDeleted, true) ); if (CollectionUtils.isEmpty(detailList)) { return Collections.emptyMap(); } Map sumMap = new HashMap<>(); for (XpTestOrderDetail detail : detailList) { if (detail == null || detail.getDetailType() == null) { continue; } BigDecimal money = detail.getMoney() == null ? BigDecimal.ZERO : detail.getMoney(); sumMap.put(detail.getDetailType(), sumMap.getOrDefault(detail.getDetailType(), BigDecimal.ZERO).add(money)); } return sumMap; } /** * 将轨迹点转换为订单经纬度列表 */ private List convertTrajectoriesToPositions(List trajectories) { if (CollectionUtils.isEmpty(trajectories)) { return new ArrayList<>(); } List positions = new ArrayList<>(); for (OrderTrajectory trajectory : trajectories) { if (trajectory == null || StringUtils.isBlank(trajectory.getLocation())) { continue; } String[] parts = trajectory.getLocation().split(","); if (parts.length != 2) { continue; } try { double longitude = Double.parseDouble(parts[0].trim()); double latitude = Double.parseDouble(parts[1].trim()); XpTestOrderDetailVO.OrderPosition position = new XpTestOrderDetailVO.OrderPosition(); position.setLongitude(longitude); position.setLatitude(latitude); positions.add(position); } catch (Exception e) { // ignore invalid point } } return positions; } /** * 发送乘客导航路径数据 * * @param appUserId * @param keyStr */ public void sendRoute(String appUserId, Long orderId, String keyStr, String vin) { VehiclePosition vehiclePosition = mongoTemplate.findOne(new Query(Criteria.where("vin").is(vin)), VehiclePosition.class); Object cacheObject = redisService.getCacheObject(keyStr + ":" + appUserId); if (null != cacheObject) { RouteVo routeVo = JSON.parseObject(cacheObject.toString(), RouteVo.class); JSONObject jsonObject = new JSONObject(); jsonObject.put("type", "ORDER_ROUTE"); jsonObject.put("timestamp", System.currentTimeMillis()); JSONObject content = new JSONObject(); content.put("orderId", orderId.toString()); content.put("routeType", "ORDER_NAVIGATION_START_DATA".equals(keyStr) ? 1 : 2); content.put("direction", null != vehiclePosition ? vehiclePosition.getCourse() : 90); JSONObject route = new JSONObject(); route.put("distance", routeVo.getDistance()); route.put("duration", routeVo.getDuration()); route.put("routeID", routeVo.getRouteID()); List collect = routeVo.getPolyline().stream().map(item -> { JSONObject point = new JSONObject(); point.put("latitude", item.getLatitude()); point.put("longitude", item.getLongitude()); return point; }).collect(Collectors.toList()); route.put("polyline", JSON.toJSONString(collect)); content.put("route", route.toJSONString()); jsonObject.put("content", content.toJSONString()); SendMessageDTO messageDTO = new SendMessageDTO(); messageDTO.setAppUserId(appUserId); messageDTO.setMessage(jsonObject.toJSONString()); R stringR = xpPushMessageClient.sendRoute(messageDTO); XpTestOrder xpOrder = this.getById(orderId); //推送失败,重试3次 if(stringR.getCode() != Constants.SUCCESS){ ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>()); executor.execute(() -> { for (int i = 0; i < 3; i++) { R stringR1 = xpPushMessageClient.sendStatus(messageDTO); if(stringR1.getCode() == Constants.SUCCESS){ break; } try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } log.info("推送乘客端导航数据消息失败:{} {} {}", stringR1.getMsg(), JSON.toJSONString(messageDTO), xpOrder.getOrderNumber()); } }); } } } @Override public void changeAmount(Long id, BigDecimal amount) { if (Objects.isNull(id)) { throw new ServiceException("订单ID不能为空", I18nKeyConstant.ORDER_CHANGE_AMOUNT_ID_REQUIRED); } if (Objects.isNull(amount)) { throw new ServiceException("订单金额不能为空", I18nKeyConstant.ORDER_CHANGE_AMOUNT_AMOUNT_REQUIRED); } if (amount.compareTo(BigDecimal.ZERO) < 0) { throw new ServiceException("订单金额不能小于0", I18nKeyConstant.ORDER_CHANGE_AMOUNT_AMOUNT_INVALID); } XpTestOrder order = this.getById(id); if (Objects.isNull(order)) { throw new ServiceException("订单不存在", I18nKeyConstant.ORDER_NOT_EXISTS); } final Integer pendingPaymentStatus = 8; if (!Objects.equals(order.getStatus(), pendingPaymentStatus)) { throw new ServiceException("当前订单状态不允许改价,只有待付款状态的订单可以改价", I18nKeyConstant.ORDER_CHANGE_AMOUNT_STATUS_INVALID); } if (Objects.nonNull(order.getOrderMoney()) && order.getOrderMoney().compareTo(amount) == 0) { return; } boolean updated = this.lambdaUpdate() .set(XpTestOrder::getOrderMoney, amount) .set(XpTestOrder::getChangeMoneyFlag, Boolean.TRUE) .eq(XpTestOrder::getId, id) .eq(XpTestOrder::getStatus, pendingPaymentStatus) .update(); if (!updated) { throw new ServiceException("订单改价失败,请稍后重试", I18nKeyConstant.ORDER_CHANGE_AMOUNT_FAILED); } } }