xp-robotaxi-api/src/main/java/com/xiaopeng/api/dto/map/RouteAndFareDto.java
@@ -9,6 +9,10 @@ @lombok.Data @ApiModel(description = "线路和运费Dto") public class RouteAndFareDto { @ApiModelProperty(value = "订单id") private Long orderId; @ApiModelProperty(value = "出发地") @NotNull(message = "出发地不能为空") private LonLatDto origin; xp-robotaxi-api/src/main/java/com/xiaopeng/api/dto/order/OrderChangeDto.java
New file @@ -0,0 +1,51 @@ package com.xiaopeng.api.dto.order; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import javax.validation.constraints.NotNull; @Data public class OrderChangeDto { @ApiModelProperty(value = "订单Id") @NotNull(message = "订单Id不能为空") private Long orderId; @ApiModelProperty("终点站点id") @NotNull(message = "终点站点id不能为空") private Long endPointId; /* @ApiModelProperty(value = "目的地经度") @NotNull(message = "目的地经度不能为空") private Double endLon; @ApiModelProperty(value = "目的地纬度") @NotNull(message = "目的地纬度不能为空") private Double endLat; @ApiModelProperty(value = "目的地地点") @NotNull(message = "目的地地点不能为空") private String endAddress; */ @ApiModelProperty(value = "优惠券id") private Long couponId; @ApiModelProperty("多路线快照 ID(选路线下单时与 selectedRouteId 同时传)") private String navigationSnapshotId; @ApiModelProperty("选中的路径唯一标识(高德 routeID)") private String selectedRouteId; @ApiModelProperty("选中路线对应的高德 strategy,用于推单队列;多路线下单时由服务端写入") private Integer selectedRoutePlanRule; @ApiModelProperty(value = "距离 米") private Integer distance = 0; @ApiModelProperty(value = "时间 秒") private Integer duration = 0; } xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/order/XpOrder.java
@@ -36,7 +36,7 @@ @ApiModelProperty(value = "用户id") @TableField(updateStrategy = FieldStrategy.NEVER) private Long appUserId; @ApiModelProperty(value = "用户id") @ApiModelProperty(value = "开通城市id") private Long cityId; @ApiModelProperty(value = "车辆id") private Long vehicleId; @@ -183,7 +183,7 @@ @ApiModelProperty(value = "下单城市编码") private String cityCode; @ApiModelProperty(value = "下单站点id") @ApiModelProperty(value = "起点站点id") @TableField("start_point_id") private Long startPointId; @@ -191,7 +191,7 @@ @TableField("end_point_id") private Long endPointId; @ApiModelProperty(value = "下单站点名称") @ApiModelProperty(value = "起点站点名称") @TableField("start_point_name") private String startPointName; @@ -239,4 +239,8 @@ @ApiModelProperty(value = "车辆是否故障 0-否 1-是") private Boolean carAbnormal; @ApiModelProperty(value = "订单修改目的地的次数 默认0") private Integer orderChange; } xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/order/XpOrderWaypoint.java
New file @@ -0,0 +1,46 @@ package com.xiaopeng.api.model.order; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.Accessors; import javax.validation.constraints.NotNull; @Data @EqualsAndHashCode(callSuper = false) @Accessors(chain = true) @TableName("xp_order_waypoint") @ApiModel(value="xp_order_waypoint", description="订单途经点") public class XpOrderWaypoint { private static final long serialVersionUID = 1L; @TableId(value = "id", type = IdType.NONE) private Long id; @ApiModelProperty(value = "订单id") @NotNull(message = "订单id不能为空") private Long orderId; @ApiModelProperty(value = "途经点点位id") @NotNull(message = "途经点点位id不能为空") private Long pointId; @ApiModelProperty(value = "途经点名称") @NotNull(message = "途经点名称不能为空") private String pointName; @ApiModelProperty(value = "经度") @NotNull(message = "经度不能为空") private Double longitude; @ApiModelProperty(value = "纬度") @NotNull(message = "纬度不能为空") private Double latitude; } xp-robotaxi-common/xp-common-core/src/main/java/com/xiaopeng/common/core/utils/DateUtils.java
@@ -443,4 +443,17 @@ return count; } /** * 获取指定时间的 毫秒 * @return */ public static long getMilliseconds(LocalDateTime localDateTime){ // 转换为毫秒时间戳(需指定时区) Instant instant = localDateTime.atZone(ZoneId.systemDefault()).toInstant(); long timestamp = instant.toEpochMilli(); return timestamp; } } xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/controller/XpPricingController.java
@@ -7,6 +7,7 @@ import com.xiaopeng.api.dto.map.RoutePreferenceSaveDto; import com.xiaopeng.api.dto.map.RouteStaleCheckDto; import com.xiaopeng.api.dto.map.SelectNavigationRouteDto; import com.xiaopeng.api.dto.order.OrderChangeDto; import com.xiaopeng.api.model.order.XpOrder; import com.xiaopeng.api.vo.operation.MultiRouteAndFareVo; import com.xiaopeng.api.vo.operation.RoutePreferenceItemVo; @@ -18,6 +19,7 @@ import com.xiaopeng.common.core.web.controller.BaseController; import com.xiaopeng.common.redis.service.RedisService; import com.xiaopeng.itinerary.dto.XpOrderDto; import com.xiaopeng.itinerary.service.ActiveOrderService; import com.xiaopeng.itinerary.service.IXpGeoQueryEntityService; import com.xiaopeng.itinerary.service.XpPricingService; import com.xiaopeng.api.vo.HotFixInfoVo; @@ -31,6 +33,7 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import javax.validation.Valid; import java.util.List; @@ -54,6 +57,8 @@ private final IXpGeoQueryEntityService xpGeoQueryEntityService; private final RedisService redisService; private final ImageTagProcessor imageTagProcessor; @Resource private ActiveOrderService activeOrderService; public XpPricingController(XpPricingService xpPricingService, IXpGeoQueryEntityService xpGeoQueryEntityService, RedisService redisService, ImageTagProcessor imageTagProcessor) { @@ -137,6 +142,25 @@ return R.ok(saveOrderVo); } /** * 行程中 修改目的地 确认按钮 * 修改目的地, * 1.重新确认路线 * 2.确认价格 */ @ApiOperation(value = "行程中 修改目的地 确认按钮", notes = "", tags = {"小程序-行程中修改目的地"}) @PostMapping("/api/v1/mini-program/order/orderChange") @XpClientCheck(checkToken = false, checkOtpToken = true) public R<?> orderChange(@RequestBody @Validated OrderChangeDto dto, HttpServletRequest request) { String uid = request.getHeader("XP-Uid"); activeOrderService.orderChange(dto, uid); return R.ok(); } /** * 获取热修复信息 * @param xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/dto/XpOrderDto.java
@@ -48,6 +48,9 @@ @NotNull(message = "下单地址不能为空") private String placementAddress; @ApiModelProperty("路线规划规则") @NotNull(message = "路线规划规则不能为空") private Integer routePlanRule; @ApiModelProperty("用户优惠券主键:null 或 0 表示不使用券;>0 为具体券 id") private Long userCouponId; xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/service/ActiveOrderService.java
New file @@ -0,0 +1,13 @@ package com.xiaopeng.itinerary.service; import com.xiaopeng.api.dto.order.OrderChangeDto; /** * 行驶中的订单 */ public interface ActiveOrderService { void orderChange(OrderChangeDto dto, String uid); } xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/service/XpPricingService.java
@@ -60,6 +60,8 @@ */ String saveOrder(XpOrderDto dto, String uid); Boolean isInOperationTime(String operationStrategy); /** * 多策略路径规划合并、计价与快照 */ xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/service/impl/ActiveOrderServiceImpl.java
New file @@ -0,0 +1,226 @@ package com.xiaopeng.itinerary.service.impl; import com.alibaba.fastjson2.JSON; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.xiaopeng.api.dto.order.OrderChangeDto; import com.xiaopeng.api.feignClient.map.XpMapClient; import com.xiaopeng.api.feignClient.operation.XpPointClient; import com.xiaopeng.api.feignClient.order.XpOrderClient; import com.xiaopeng.api.model.operation.XpElectronicFence; import com.xiaopeng.api.model.operation.XpElectronicFenceArea; import com.xiaopeng.api.model.operation.XpPoint; import com.xiaopeng.api.model.operation.XpRoutePlan; import com.xiaopeng.api.model.order.XpOrder; import com.xiaopeng.api.vo.operation.RouteOptionVo; 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.redis.service.RedisService; import com.xiaopeng.itinerary.constant.I18nKeyConstant; import com.xiaopeng.itinerary.mapper.XpElectronicFenceAreaMapper; import com.xiaopeng.itinerary.mapper.XpElectronicFenceMapper; import com.xiaopeng.itinerary.mapper.XpOpenCityMapper; import com.xiaopeng.itinerary.mapper.XpRoutePlanMapper; import com.xiaopeng.itinerary.navigation.MultiRouteSnapshotPayload; import com.xiaopeng.itinerary.service.ActiveOrderService; import com.xiaopeng.itinerary.service.XpPointService; import com.xiaopeng.itinerary.service.XpPricingService; import com.xiaopeng.itinerary.utils.map.GDMapGeocodingUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.time.LocalDateTime; import java.util.*; import java.util.concurrent.TimeUnit; @Slf4j @Service public class ActiveOrderServiceImpl implements ActiveOrderService { @Resource private GDMapGeocodingUtil gdMapGeocodingUtil; @Resource private XpOpenCityMapper xpOpenCityMapper; @Resource private XpOrderClient xpOrderClient; @Resource private XpPricingService xpPricingService; @Resource private XpMapClient xpMapClient; @Resource private XpElectronicFenceAreaMapper xpElectronicFenceAreaMapper; @Autowired private XpElectronicFenceMapper xpElectronicFenceMapper; @Autowired private XpPointService xpPointService; @Resource private XpPointClient xpPointClient; @Resource private RedisService redisService; @Resource private XpRoutePlanMapper xpRoutePlanMapper; /* public void getRouteAndFareParamValidate(RouteAndFareDto routeAndFareDto, String userId) { //获取订单信息 R<XpOrder> xpOrderR = xpOrderClient.queryOrderById(routeAndFareDto.getOrderId()); if (xpOrderR.getCode() != R.SUCCESS || xpOrderR.getData() == null) { throw new ServiceException("订单不存在"); } // 1. 基础数据查询 Map<String, String> geocode = gdMapGeocodingUtil.geocode(routeAndFareDto.getOrigin().getLongitude().toString(), routeAndFareDto.getOrigin().getLatitude().toString()); if(null == geocode){ throw new ServiceException("获取城市信息失败", I18nKeyConstant.CITY_NOT_FOUND); } Long cityCode = routeAndFareDto.getOpenCityId(); XpOpenCity openCities = xpOpenCityMapper.selectById(cityCode); if (openCities == null ) { throw new ServiceException("获取城市信息失败", I18nKeyConstant.CITY_NOT_FOUND); } XpPricing xpPricing = xpPricingService.getOne(new LambdaQueryWrapper<XpPricing>().eq(XpPricing::getCityId, routeAndFareDto.getOpenCityId())); if (xpPricing == null) { throw new ServiceException("当前区域未开通服务", I18nKeyConstant.NO_CURRENT_REGION); } routeAndFareDto.setRoutePlanRule(xpOrderR.getData().getRoutePlanRule()); // 算距离 R<RouteVo> distanceAndDuration = xpMapClient.getDistanceAndDuration(routeAndFareDto); if (distanceAndDuration.getCode() != 200) { throw new ServiceException("获取导肮信息失败", I18nKeyConstant.PATH_NOT_FOUND); } RouteVo routeVo = distanceAndDuration.getData(); } */ @Override public void orderChange(OrderChangeDto dto, String uid){ //获取订单信息 R<XpOrder> xpOrderR = xpOrderClient.queryOrderById(dto.getOrderId()); if (xpOrderR.getCode() != R.SUCCESS || xpOrderR.getData() == null) { throw new ServiceException("订单不存在"); } Integer orderChange = 2;//todo yyy 从配置文件读取 XpOrder order = xpOrderR.getData(); //判断订单修改的次数 if(order.getOrderChange() > orderChange){ throw new ServiceException("目的地修改次数已超上限"); } // 是否在运营区域时间内 XpElectronicFenceArea xpElectronicFenceArea = xpElectronicFenceAreaMapper.selectById(order.getXpElectronicFenceAreaId()); XpElectronicFence xpElectronicFence = xpElectronicFenceMapper.selectById(xpElectronicFenceArea.getElectronicId()); String operationStrategy = xpElectronicFence.getOperationStrategy(); Boolean inOperationTime = xpPricingService.isInOperationTime(operationStrategy); if (!inOperationTime && xpElectronicFence.getFailureType()!=1) { log.info("当前区域暂未开通,下单失败 {}", uid); throw new ServiceException("当前区域未开通服务", I18nKeyConstant.NO_CURRENT_REGION); } // 终点 XpPoint endXpPoint = xpPointService.getById(dto.getEndPointId()); if(endXpPoint ==null || endXpPoint.getOperationStatus()!=2){ throw new ServiceException("该站点已停止运营", 200,I18nKeyConstant.NO_CURRENT_POINT); } boolean inTime = xpPointClient.nowInTimePoint(dto.getEndPointId()).getData(); if (!inTime) { throw new ServiceException("当前点位未开通服务", I18nKeyConstant.NO_CURRENT_REGION); } //修改订单数据 XpOrder xpOrder = new XpOrder(); xpOrder.setId(order.getId()); xpOrder.setEndLon(endXpPoint.getPointLon()); xpOrder.setEndLat(endXpPoint.getPointLat()); xpOrder.setEndAddress(endXpPoint.getPointAddress()); xpOrder.setEndPointId(dto.getEndPointId()); xpOrder.setEndPointName(endXpPoint.getPointName()); Long couponRef = dto.getCouponId(); xpOrder.setCouponId(couponRef != null && couponRef > 0L ? couponRef : null); xpOrder.setUpdateTime(LocalDateTime.now()); xpOrder.setOrderChange(order.getOrderChange()+1); xpOrderClient.updateOrder(xpOrder); //获取距离和时间 this.getDD(uid,dto); //将修改信息存入到redis, // 获取起点经纬度 XpPoint xpPoint = xpPointService.getById(order.getStartPointId()); XpRoutePlan xpRoutePlan = xpRoutePlanMapper.selectOne(new LambdaQueryWrapper<XpRoutePlan>().eq(XpRoutePlan::getAuditStatus, 2).eq(XpRoutePlan::getCityCode, order.getCityCode())); final Integer planRuleForQueue = dto.getSelectedRoutePlanRule() != null ? dto.getSelectedRoutePlanRule() : (xpRoutePlan != null ? xpRoutePlan.getRoutePlanRule() : null); // order.getId() + "_" + xpPoint.getPointLon() + "," + xpPoint.getPointLat() + "_0" + "_" + uid + "_" + order.getOrderNumber() + "_" + dto.getDistance() + "_" + dto.getDuration() + "_" + null StringJoiner points = new StringJoiner("_"); points.add(order.getId().toString()); points.add(xpPoint.getPointLon() + "," + xpPoint.getPointLat()); points.add("0"); points.add(uid); points.add(order.getOrderNumber()); points.add(dto.getDistance()+""); points.add(dto.getDuration()+""); String key = CacheConstants.QUEUE_POINT_ID + "-" + xpPoint.getId() + "-" + xpPoint.getPointLon() + "," + xpPoint.getPointLat(); if (planRuleForQueue == null) { redisService.addToTail(key,points+"_"+null); } else { redisService.addToTail(key,points+"_"+planRuleForQueue); } log.info("订单推单:{} {}", key, redisService.getCacheList(key)); xpOrder.setQueueName(key); xpOrderClient.updateOrder(xpOrder); } /** * 获取距离和时间 * @param uid * @param dto */ public void getDD (String uid, OrderChangeDto dto ){ Integer distance = 0; Integer duration = 0; if (dto.getNavigationSnapshotId() != null && dto.getSelectedRouteId() != null) { String snap = redisService.getCacheObject(CacheConstants.ORDER_MULTI_ROUTE_SNAPSHOT + uid + ":" + dto.getNavigationSnapshotId()); if (snap != null) { MultiRouteSnapshotPayload snapPayload = JSON.parseObject(snap, MultiRouteSnapshotPayload.class); RouteOptionVo opt = snapPayload.getRoutes().stream() .filter(r2 -> dto.getSelectedRouteId().equals(r2.getPathFingerprint())) .findFirst() .orElse(null); if (opt != null && opt.getRoute() != null) { distance = opt.getRoute().getDistance(); duration = opt.getRoute().getDuration(); redisService.setCacheObject("ORDER_NAVIGATION_END_DATA:" + uid, JSON.toJSONString(opt.getRoute()), 24L, TimeUnit.HOURS); if (dto.getSelectedRoutePlanRule() == null) { dto.setSelectedRoutePlanRule(opt.getPrimaryRoutePlanRule()); } } } } if (distance == 0 && duration == 0) { Object cacheObject = redisService.getCacheObject("ORDER_NAVIGATION_END_DATA:" + uid); if (cacheObject != null) { RouteVo routeVo = JSON.parseObject(cacheObject.toString(), RouteVo.class); distance = routeVo.getDistance(); duration = routeVo.getDuration(); } } dto.setDistance(distance); dto.setDuration(duration); } } xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/service/impl/XpPricingServiceImpl.java
@@ -7,13 +7,10 @@ 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.OrderRouteAndFareDto; import com.xiaopeng.api.dto.map.RouteAndFareDto; import com.xiaopeng.api.dto.map.RouteStaleCheckDto; import com.xiaopeng.api.dto.map.SelectNavigationRouteDto; import com.xiaopeng.api.dto.operation.LonLatDto; import com.xiaopeng.api.enums.RouteStrategyKind; import com.xiaopeng.api.dto.pushMessage.SendMessageDTO; import com.xiaopeng.api.feignClient.account.XpAppUserClient; import com.xiaopeng.api.feignClient.account.XpAppUserCouponClient; import com.xiaopeng.api.feignClient.map.XpMapClient; @@ -409,7 +406,7 @@ // 11. 生成订单ID并缓存 String id = CacheConstants.ORD + System.currentTimeMillis() + "_" + UUID.randomUUID().toString().substring(0, 8); redisService.setCacheObject(CacheConstants.ORD + id + userId, xpPricingCardVo, 1l, TimeUnit.DAYS); redisService.setCacheObject(CacheConstants.ORD + id + userId, xpPricingCardVo, 1L, TimeUnit.DAYS); fareVo.setId(id); // 12. 设置费用信息 @@ -612,24 +609,23 @@ xpOrder.setPlacementAddress(dto.getPlacementAddress()); xpOrder.setCityId(dto.getOpenCityId()); xpOrder.setXpElectronicFenceAreaId(dto.getXpElectronicFenceAreaId()); xpOrder.setStartPointId(dto.getStartPointId()); xpOrder.setStartLon(xpPoint.getPointLon()); xpOrder.setStartLat(xpPoint.getPointLat()); xpOrder.setBoardingLon(xpPoint.getPointLon()); xpOrder.setBoardingLat(xpPoint.getPointLat()); xpOrder.setBoardingAddress(xpPoint.getPointAddress()); xpOrder.setStartAddress(xpPoint.getPointAddress()); xpOrder.setStartPointName(xpPoint.getPointName()); xpOrder.setEndPointId(dto.getEndPointId()); xpOrder.setEndLon(endXpPoint.getPointLon()); xpOrder.setEndLat(endXpPoint.getPointLat()); xpOrder.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(); @@ -732,7 +728,6 @@ xpGeoQueryEntity.setStatus(0); xpGeoQueryEntity.setTimestamp(System.currentTimeMillis()); geoQueryEntityService.save(xpGeoQueryEntity); return orderId.toString(); } } @@ -760,8 +755,8 @@ return orderId.toString(); } private Boolean isInOperationTime(String operationStrategy) { @Override public Boolean isInOperationTime(String operationStrategy) { JSONArray jsonArray = JSONArray.parseArray(operationStrategy); Integer holidayToday = holidayUtil.isHolidayToday(); for (Object o : jsonArray) { xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/controller/XpOrderController.java
@@ -51,6 +51,7 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -79,7 +80,7 @@ private final RedisService redisService; @Autowired private MongoTemplate mongoTemplate; @Autowired @Resource private XpVehicleClient xpVehicleClient; /** @@ -106,7 +107,7 @@ if (!list.isEmpty()) { return R.ok(list.get(0).getId()); } return R.ok(0l); return R.ok(0L); } @@ -391,6 +392,17 @@ return R.ok(xpOrderService.getActiveOrder(uid)); } /** * 获取正在进行中的订单_行程管理 */ @ApiOperation(value = "获取正在进行中的订单_行程管理", tags = {"小程序-获取正在进行中的订单_行程管理"}) @GetMapping("/api/v1/mini-program/order/active_itineraryManage") @XpClientCheck(checkToken = false, checkOtpToken = true) public R<XpOrderDetailVO> getActiveOrder_itineraryManage(HttpServletRequest request) { // uid已通过拦截器otp校验,可以直接使用 String uid = request.getHeader("XP-Uid"); return R.ok(xpOrderService.getActiveOrder_itineraryManage(uid)); } /** * 查询历史订单列表 @@ -421,7 +433,6 @@ * + * 取消订单 */ @PutMapping("/api/v1/mini-program/order/cancel/{orderId}") @ApiOperation(value = "取消订单", tags = {"小程序-取消订单"}) @XpClientCheck(checkToken = false, checkOtpToken = true) xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/controller/XpOrderWaypointController.java
New file @@ -0,0 +1,51 @@ package com.xiaopeng.order.controller; import com.xiaopeng.api.model.order.XpOrder; import com.xiaopeng.api.model.order.XpOrderWaypoint; import com.xiaopeng.common.core.domain.R; import com.xiaopeng.order.service.IXpOrderService; import com.xiaopeng.order.service.IXpOrderWaypointService; import com.xiaopeng.unifiedverification.interceptor.XpClientCheck; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Lazy; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; @RestController @Api(tags = "订单途经点") @RequiredArgsConstructor(onConstructor_ = @Lazy) public class XpOrderWaypointController { @Resource private final IXpOrderWaypointService xpOrderWaypointService; private final IXpOrderService xpOrderService; /** * 添加订单途径点 */ @PostMapping("/api/v1/mini-program/orderWaypoint/save") @ApiOperation(value = "添加订单途径点", tags = {"小程序-添加订单途径点"}) @XpClientCheck(checkToken = false, checkOtpToken = true) public R save(@Validated @RequestBody XpOrderWaypoint entity) { XpOrder xpOrder = xpOrderService.getById(entity.getOrderId()); if(xpOrder == null){ return R.fail("订单不存在"); } entity.setId(null); boolean b = xpOrderWaypointService.save(entity); if(b){ return R.ok(); } return R.fail("订单不存在"); } } xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/mapper/XpOrderWaypointMapper.java
New file @@ -0,0 +1,16 @@ package com.xiaopeng.order.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.xiaopeng.api.model.order.XpOrderWaypoint; /** * <p> * 订单支付记录 Mapper 接口 * </p> * * @author mitao * @since 2026-02-03 */ public interface XpOrderWaypointMapper extends BaseMapper<XpOrderWaypoint> { } xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/model/vo/XpOrderDetailVO.java
@@ -2,13 +2,12 @@ import com.baomidou.mybatisplus.annotation.TableField; import com.fasterxml.jackson.annotation.JsonFormat; import com.xiaopeng.api.dto.operation.LonLatDto; import com.xiaopeng.api.model.order.XpOrderWaypoint; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; @@ -47,6 +46,8 @@ @ApiModelProperty(value = "下单城市名称") private String cityName; @ApiModelProperty(value = "下单城市编码") private String cityCode; @ApiModelProperty(value = "预计时间") @@ -131,6 +132,9 @@ @ApiModelProperty(value = "车辆id") private Long vehicleId; @ApiModelProperty(value = "车辆vin码") private String vehicleVin; @ApiModelProperty(value = "车牌号") private String plateNumber; @@ -205,4 +209,8 @@ } @ApiModelProperty(value = "后备箱状态(1=打开,2=关闭)") private Integer openTrunk; @ApiModelProperty(value = "途径点信息") private List<XpOrderWaypoint> xpOrderWaypoints; } xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/IXpOrderService.java
@@ -226,6 +226,8 @@ R<String> feeDescription(Long orderId, String uid); XpOrderDetailVO getActiveOrder_itineraryManage(String uid); /** * 订单改价 * @param id xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/IXpOrderWaypointService.java
New file @@ -0,0 +1,16 @@ package com.xiaopeng.order.service; import com.baomidou.mybatisplus.extension.service.IService; import com.xiaopeng.api.model.order.XpOrderWaypoint; /** * <p> * 订单支付记录 服务类 * </p> * * @author mitao * @since 2026-02-03 */ public interface IXpOrderWaypointService extends IService<XpOrderWaypoint> { } xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/impl/XpOrderServiceImpl.java
@@ -16,7 +16,7 @@ import com.xiaopeng.api.dto.automaticPilot.PushOrderDto; import com.xiaopeng.api.dto.automaticPilot.VehicleEventDTO; import com.xiaopeng.api.dto.map.OrderRouteAndFareDto; import com.xiaopeng.api.dto.operation.CruisePointDto; 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; @@ -27,18 +27,13 @@ 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.dto.vehicleConvoy.UpdateVehicleStateDto; import com.xiaopeng.api.dto.vehicleConvoy.VehicleTemperatureDto; 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.automaticPilot.VehicleBuriedPointClient; import com.xiaopeng.api.feignClient.maintenance.XpTaskRuleClient; import com.xiaopeng.api.feignClient.map.XpMapClient; import com.xiaopeng.api.feignClient.operation.XpCruisePlanClient; import com.xiaopeng.api.feignClient.operation.XpPointClient; import com.xiaopeng.api.feignClient.operation.XpPricingClient; import com.xiaopeng.api.feignClient.operation.XpSystemSettingClient; import com.xiaopeng.api.feignClient.operation.*; import com.xiaopeng.api.feignClient.pushMessage.XpPushMessageClient; import com.xiaopeng.api.feignClient.vehicleConvoy.*; import com.xiaopeng.api.model.maintenance.XpTaskRule; @@ -92,7 +87,6 @@ import com.xiaopeng.order.utils.GDMapGeocodingUtil; import com.xiaopeng.order.utils.TaxiPricingDemoService; 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; @@ -163,6 +157,8 @@ private final VehicleBuriedPointClient vehicleBuriedPointClient; private final IXpTestOrderService xpTestOrderService; private final XpMapClient xpMapClient; @Resource private XpRoutePlanClient xpRoutePlanClient; @Autowired @@ -1591,11 +1587,11 @@ @ShardingSphereTransactionType(TransactionType.BASE) public void cancelOrder(Long orderId, String uid) { XpOrder order = this.getById(orderId); if (order.getStatus() == 0) { return; } if (order == null) { throw new ServiceException("订单不存在", I18nKeyConstant.ORDER_NOT_EXISTS); } if (order.getStatus() == 0) { return; } if (!order.getAppUserId().toString().equals(uid)) { throw new ServiceException("订单不存在", I18nKeyConstant.ORDER_NOT_EXISTS); @@ -1603,7 +1599,6 @@ if (order.getStatus() > 5 || order.getStatus() < 1) { throw new ServiceException("订单状态错误", I18nKeyConstant.ORDER_STATUS_ERROR); } try { redisService.removeByPattern(order.getQueueName(), order.getOrderNumber()); } catch (Exception e) { @@ -1625,39 +1620,20 @@ } } } if (order.getStatus() == 1) { order.setStatus(0); order.setAppUserId(null); baseMapper.updateById(order); redisService.setCacheObject("ORDER_STATUS:" + order.getId(), 0, 24L, TimeUnit.HOURS); return; } /* order.setStatus(0); order.setAppUserId(null); baseMapper.updateById(order); redisService.setCacheObject("ORDER_STATUS:" + order.getId(), 0, 24L, TimeUnit.HOURS); XpOrderCancel xpOrderCancel = new XpOrderCancel(); xpOrderCancel.setOrderId(orderId); xpOrderCancel.setUserId(Long.valueOf(uid)); xpOrderCancel.setCancelSource(1); xpOrderCancel.setCancelReason("用户取消订单"); xpOrderCancelService.save(xpOrderCancel); */ // 移除mongo数据 xpPricingClient.deleteXpGeoQueryEntity(order.getId().toString()); // 下发命令 if (null != order.getVehicleId()) { else{// 下发命令 R<XpVehicle> xpVehicleR = xpVehicleClient.queryById(order.getVehicleId()); // 组装调用车机参数 CancelTaxiPassengerVo cancelTaxiPassengerVo = new CancelTaxiPassengerVo(); XpVehicle xpVehicle = xpVehicleR.getData(); if (xpVehicle == null) { throw new ServiceException("车辆不存在", I18nKeyConstant.VEHICLE_NOT_EXISTS); } //是否能取消行程 boolean cancelShow = this.isCancelShow(order,xpVehicle.getVehicleVin()); if(cancelShow){ throw new ServiceException("当前订单状态不允许取消,行程即将完成", I18nKeyConstant.ORDER_CANCEL_FAILED); } // 组装调用车机参数 CancelTaxiPassengerVo cancelTaxiPassengerVo = new CancelTaxiPassengerVo(); cancelTaxiPassengerVo.setVin(xpVehicle.getVehicleVin()); cancelTaxiPassengerVo.setUid(uid); cancelTaxiPassengerVo.setOrderNo(order.getOrderNumber()); @@ -1688,7 +1664,19 @@ //车辆继续巡游 addCruiseMission(xpVehicle.getId()); } order.setStatus(0); order.setAppUserId(null); baseMapper.updateById(order); redisService.setCacheObject("ORDER_STATUS:" + order.getId(), 0, 24L, TimeUnit.HOURS); XpOrderCancel xpOrderCancel = new XpOrderCancel(); xpOrderCancel.setOrderId(orderId); xpOrderCancel.setUserId(Long.valueOf(uid)); xpOrderCancel.setCancelSource(1); xpOrderCancel.setCancelReason("用户取消订单"); xpOrderCancelService.save(xpOrderCancel); // 移除mongo数据 xpPricingClient.deleteXpGeoQueryEntity(order.getId().toString()); } @Override @@ -3478,6 +3466,7 @@ if (xpOrder.getVehicleId() != null) { R<XpVehicle> xpVehicleR = xpVehicleClient.queryById(xpOrder.getVehicleId()); XpVehicle vehicle = xpVehicleR.getData(); xpOrderDetailVO.setVehicleVin(vehicle.getVehicleVin()); xpOrderDetailVO.setPlateNumber(vehicle.getPlateNumber()); xpOrderDetailVO.setVehiclePictures(vehicle.getVehiclePictures()); } @@ -3489,24 +3478,113 @@ xpOrder.setAppUserId(null); this.updateById(xpOrder); } Boolean cancelShow = true; //是否能取消行程 xpOrderDetailVO.setCancelShow(cancelShow); return xpOrderDetailVO; } @Override public XpOrderDetailVO getActiveOrder_itineraryManage(String uid){ XpOrderDetailVO xpOrderDetailVO = new XpOrderDetailVO(); XpOrder xpOrder = this.baseMapper.selectOne(new LambdaQueryWrapper<XpOrder>().eq(XpOrder::getAppUserId, Long.parseLong(uid)).in(XpOrder::getStatus, Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8)).orderByDesc(BaseModel::getCreateTime).last("LIMIT 1")); if (xpOrder == null) { return null; } BeanUtils.copyProperties(xpOrder, xpOrderDetailVO); if (xpOrder.getVehicleId() != null) { R<XpVehicle> xpVehicleR = xpVehicleClient.queryById(xpOrder.getVehicleId()); XpVehicle vehicle = xpVehicleR.getData(); xpOrderDetailVO.setVehicleVin(vehicle.getVehicleVin()); } Boolean cancelShow = isCancelShow(xpOrder,xpOrderDetailVO.getVehicleVin()); //是否能取消行程 xpOrderDetailVO.setCancelShow(cancelShow); return xpOrderDetailVO; } /** * 取消行程:若处于“送驾中”且离目的地小于10分钟路程(后台配置),【取消行程】按钮应置灰; * 修改行程: * 1. 当前剩余里程 < 800米 (后台配置)或 剩余时间 < 5分钟(后台配置)(以先到者为准),禁止修改,按钮置灰。 * 2. 单次行程最多允许修改 2次 (后台配置)目的地,防恶意高频调用 * 特殊豁免:若系统检测到车辆在当前坐标时速 < 3km/h 且持续时间 > 3分钟(后台配置)(判定为极度拥堵或异常停滞),此时解除限制,允许用户修改目的地或新增途径点以寻找更近的下车位置。 * @return */ private Boolean isCancelShow(){ private boolean isCancelShow(XpOrder xpOrder,String vehicleVin) { boolean isCancel = false; //获取配置文件 int sylc = 800;//单位米 todo yyy 需要读取后台配置文件 int sysj = 5;//单位分钟 todo yyy 需要读取后台配置文件 // 减少3分钟 获取3分账内的 行程数据 long minute =3;//todo yyy 需要读取后台配置文件 return true; List<OrderTrajectory> orderTrajectories = queryOrderTrajectories(xpOrder.getId()); if(!orderTrajectories.isEmpty()){// 减少3分钟 获取3分账内的 行程数据 //3千米/小时 转换成分钟 50米/分钟 BigDecimal suduDefault = new BigDecimal("3");//3千米/小时 BigDecimal suduDefault1 = suduDefault.multiply(BigDecimal.valueOf(1000)) ; BigDecimal suduDefaultNew =suduDefault1.divide(BigDecimal.valueOf(60),2,RoundingMode.HALF_EVEN); LocalDateTime minutesBefore = LocalDateTime.now().minusMinutes(minute); long milliseconds = DateUtils.getMilliseconds(minutesBefore); List<OrderTrajectory> orderTrajectoriesNew = new ArrayList<>(); for (OrderTrajectory orderTrajectory : orderTrajectories) { if(orderTrajectory.getLocatetime() > milliseconds){ orderTrajectoriesNew.add(orderTrajectory); } } /* 获取最近3分钟的 数据 速度、平均 */ //计算行驶总距离和时长 Double reduce = orderTrajectoriesNew.stream().map(OrderTrajectory::getDistance).reduce(0.0, Double::sum); BigDecimal xcsudu = new BigDecimal(reduce).divide(new BigDecimal(minute),2, RoundingMode.HALF_EVEN);//米/分钟 //特殊豁免:若系统检测到车辆在当前坐标时速 < 3km/h 且持续时间 > 3分钟(后台配置)(判定为极度拥堵或异常停滞),此时解除限制,允许用户修改目的地或新增途径点以寻找更近的下车位置。 if(xcsudu.compareTo(suduDefaultNew)<0){ return true; } } RouteVo routeVo = this.getRouteVo(xpOrder, vehicleVin); if(routeVo.getDistance() <sylc){ isCancel = true; } if(routeVo.getDuration() < sysj){ isCancel = true; } return isCancel; } /** * 获取两个点位车的距离 和时间 * @param xpOrder * @param vehicleVin * @return */ private RouteVo getRouteVo(XpOrder xpOrder,String vehicleVin){ VehiclePosition vehiclePosition = mongoTemplate.findOne(new Query(Criteria.where("vin").is(vehicleVin)), VehiclePosition.class); // 计算距离 和 时间 RouteAndFareDto routeAndFareDto = new RouteAndFareDto(); LonLatDto lonLatDto = new LonLatDto(); lonLatDto.setLongitude(vehiclePosition.getPoint().getX());//经度 lonLatDto.setLatitude(vehiclePosition.getPoint().getY()); routeAndFareDto.setOrigin(lonLatDto); LonLatDto lonLatDto1 = new LonLatDto(); lonLatDto1.setLongitude(xpOrder.getEndLon()); lonLatDto1.setLatitude(xpOrder.getEndLat()); routeAndFareDto.setDestination(lonLatDto1); // XpOpenCity xpOpenCity = xpOpenCityMapper.selectById(dto.getOpenCityId()); // xpRoutePlanClient. XpRoutePlan xpRoutePlan = xpRoutePlanClient.getXpRoutePlanOpenCityId(xpOrder.getCityId()).getData(); if (xpRoutePlan != null) { routeAndFareDto.setRoutePlanRule(xpRoutePlan.getRoutePlanRule()); } R<RouteVo> distanceAndDuration = xpMapClient.getDistanceAndDuration(routeAndFareDto); if(distanceAndDuration.getCode()!=200){ throw new ServiceException("未找到路径", I18nKeyConstant.PATH_NOT_FOUND); } return distanceAndDuration.getData(); } xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/impl/XpOrderWaypointServiceImpl.java
New file @@ -0,0 +1,24 @@ package com.xiaopeng.order.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.xiaopeng.api.model.order.XpOrderWaypoint; import com.xiaopeng.order.mapper.XpOrderWaypointMapper; import com.xiaopeng.order.service.IXpOrderWaypointService; import org.springframework.stereotype.Service; /** * <p> * 订单支付记录 服务实现类 * </p> * * @author mitao * @since 2026-02-03 */ @Service public class XpOrderWaypointServiceImpl extends ServiceImpl<XpOrderWaypointMapper, XpOrderWaypoint> implements IXpOrderWaypointService { }