xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/operation/XpPointClientFallbackFactory.java
@@ -70,6 +70,12 @@ } @Override public R deletePoint(String ids) { log.error("删除点位失败:{}", cause.getMessage()); return R.fail("删除点位失败"); } @Override public R enableDisable(XpPointEnableDisableDto dto) { log.error("修改点位运营状态失败:{}", cause.getMessage()); return R.fail("修改点位运营状态失败"); xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/operation/XpPointClient.java
@@ -53,6 +53,11 @@ */ @GetMapping("/api/v1/point/queryPointById") public R<XpPoint> queryById(@RequestParam Long id); /** * 点位删除 */ @GetMapping("/api/v1/point/deletePoint") R deletePoint(@RequestParam("ids") String ids); /** * 启用/禁用 xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/operation/PricingDetailVo.java
New file @@ -0,0 +1,19 @@ package com.xiaopeng.api.vo.operation; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import java.math.BigDecimal; @lombok.Data @ApiModel(description = "计价规则时段Vo") public class PricingDetailVo { @ApiModelProperty(value = "时段 1普通 2夜间行驶 3高峰期 4节假日") private Integer type; @ApiModelProperty(value = "金额") private BigDecimal amount; } xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/operation/PricingVo.java
New file @@ -0,0 +1,26 @@ package com.xiaopeng.api.vo.operation; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import java.util.List; @lombok.Data @ApiModel(description = "计价规则Vo") public class PricingVo { @ApiModelProperty(value = "起步价") private Double startAmount; @ApiModelProperty(value = "起步价公里") private Double startMileage; @ApiModelProperty(value = "起步价分钟") private Double startMinute; @ApiModelProperty(value = "起步价时段") private List<PricingDetailVo> startTimes; @ApiModelProperty(value = "里程费时段") private List<PricingDetailVo> mileageTimes; @ApiModelProperty(value = "时长费") private List<PricingDetailVo> durationTimes; @ApiModelProperty(value = "远途费") private List<PricingDetailVo> longDistanceTimes; } xp-robotaxi-automatic-pilot-boot/src/main/java/com/xiaopeng/automaticPilot/controller/OrderMissionController.java
@@ -54,7 +54,7 @@ long sequence = snowFlake.nextId(); MqttPublishDto mqttPublishDto = xpuUtil.cloudCreateOrder(task, dto.getVin(),sequence); if (mqttPublishDto.getCode()== 200) { // 修改车子为微单状态 // 修改车子为接单状态 xpVehicleClient.updateVehicleStatus(new UpdateVehicleStateDto(dto.getVin(),1)); return R.ok(true); xp-robotaxi-automatic-pilot-boot/src/main/java/com/xiaopeng/automaticPilot/service/KafkaConsumerService.java
@@ -91,7 +91,6 @@ private UserVehicleClient userVehicleClient; /** * 消费消息 - XPU上报Robotaxi云端的消息topic(高优先级) * @@ -115,7 +114,7 @@ * @param msgContent * @param message */ public void handleXPUMessage(MsgContent msgContent, String message){ public void handleXPUMessage(MsgContent msgContent, String message) { RtMsgHeader header = msgContent.getHeader(); MessageType type = header.getType(); //处理请求消息 @@ -186,15 +185,15 @@ } //消息应答 if(MessageType.MESSAGE_TYPE_RESPONSE == type){ if (MessageType.MESSAGE_TYPE_RESPONSE == type) { com.xiaopeng.api.vo.automaticPilot.proto.up.ResponseMessage resposne = msgContent.getContent_detail().getObject("resposne", com.xiaopeng.api.vo.automaticPilot.proto.up.ResponseMessage.class); com.xiaopeng.api.vo.automaticPilot.proto.up.StatusCode code = resposne.getCode(); Long requestSeq = resposne.getRequest_seq(); if(code == com.xiaopeng.api.vo.automaticPilot.proto.up.StatusCode.OK){ if (code == com.xiaopeng.api.vo.automaticPilot.proto.up.StatusCode.OK) { Object sequence = redisTemplate.opsForValue().get(requestSeq); if(sequence!=null){ if (sequence != null) { String[] split = sequence.toString().split("_"); if(split[0].equals("pushOrder")){ if (split[0].equals("pushOrder")) { PopOrderDto dto = new PopOrderDto(); dto.setOrderId(Long.valueOf(split[1])); dto.setXpVehicleVin(split[2]); @@ -204,9 +203,6 @@ } } } /** @@ -314,22 +310,31 @@ userVehicleClient.cancelTaxiPassenger(cancelTaxiPassengerVo); } // 取消订单 R<?> r = xpOrderClient.cancelOrder(String.valueOf(xpOrder.getId())); if (r.getCode() == R.SUCCESS){ log.info("取消订单成功"); // TODO 重新派单 ReassignOrderDTO reassignOrderDTO = new ReassignOrderDTO(); reassignOrderDTO.setUid(xpOrder.getAppUserId().toString()); reassignOrderDTO.setOrderNo(xpOrder.getOrderNumber()); reassignOrderDTO.setCount(10); RealtimePose pose = systemRuntimeStatus.getPose(); Wgs naviPose = pose.getNavi_pose(); Double latitude = naviPose.getLatitude(); Double longitude = naviPose.getLongitude(); reassignOrderDTO.setLongitude(longitude); reassignOrderDTO.setLatitude(latitude); reassignOrderClient.reassignOrder(reassignOrderDTO); } // R<?> r = xpOrderClient.cancelOrder(String.valueOf(xpOrder.getId())); // if (r.getCode() == R.SUCCESS){ log.info("取消订单成功"); // TODO 重新派单 ReassignOrderDTO reassignOrderDTO = new ReassignOrderDTO(); reassignOrderDTO.setUid(xpOrder.getAppUserId().toString()); reassignOrderDTO.setOrderNo(xpOrder.getOrderNumber()); reassignOrderDTO.setCount(10); RealtimePose pose = systemRuntimeStatus.getPose(); Wgs naviPose = pose.getNavi_pose(); Double latitude = naviPose.getLatitude(); Double longitude = naviPose.getLongitude(); reassignOrderDTO.setLongitude(longitude); reassignOrderDTO.setLatitude(latitude); reassignOrderClient.reassignOrder(reassignOrderDTO); // } } } @@ -556,9 +561,9 @@ snowFlake.withStartTimestamp(1420041600000L); long sequence = snowFlake.nextId(); // 处理数据 redisTemplate.opsForValue().set(sequence,"pushOrder_"+orderId+"_"+vin,30, TimeUnit.MINUTES); redisTemplate.opsForValue().set(sequence, "pushOrder_" + orderId + "_" + vin, 30, TimeUnit.MINUTES); //开发发送请求到XPU xpuUtil.cloudOrderOperation(orderOperation, vin,sequence); xpuUtil.cloudOrderOperation(orderOperation, vin, sequence); } @@ -662,12 +667,13 @@ * @param orderId * @param status */ public void buildPushOrderStatus(String orderId, OrderStatusEnum status, Long sequence){ if(orderId.contains("XU")){ public void buildPushOrderStatus(String orderId, OrderStatusEnum status, Long sequence) { if (orderId.contains("XU")) { //巡游单 //响应消息 // TODO 1.处理结束当前巡游订单 2.创建新的巡游订单 返航 3.调用xpu车机接口 (判断是返航还是正常 返航需要存储之前的订单id) responseMessage(sequence, ""); }else{ } else { //普通单 R<XpOrder> xpOrderR = xpOrderClient.queryOrderById(Long.parseLong(orderId)); XpOrder xpOrder = xpOrderR.getData(); xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/config/MyLocaleResolver.java
@@ -24,6 +24,9 @@ if (StringUtils.isEmpty(language)) { locale = Locale.getDefault(); } if(language==null){ language="1"; } //如果请求不为空 //If the request is not empty. if (language.equals("1")) { xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/constant/I18nKeyConstant.java
New file @@ -0,0 +1,56 @@ package com.xiaopeng.itinerary.constant; /** * @author MiTao * @date 2026-02-06 17:34 * */ public interface I18nKeyConstant { String CITY_INFORMATION_DOES_NOT_EXIST = "city.information.does.not.exist"; String NO_CURRENT_REGION = "no.current.region"; String NO_CURRENT_POINT = "no.current.point"; String SYSTEM_EXCEPTION = "system.exception"; String TO_WORK = "to.work"; String WAITING_TIME = "wait.time"; // 编辑电子围栏失败 String EDIT_ELECTRONIC_ERROR = "edit.electronic.error"; // 添加电子围栏失败 String ADD_ELECTRONIC_ERROR = "add.electronic.error"; // 区域重叠 String AREA_OVERLAP = "area.overlap"; // 围栏构成不存在 String FENCE_STRUCTURE_NOT_EXIST = "fence.structure.not.exist"; // 围栏不存在 String FENCE_NOT_EXIST = "fence.not.exist"; // 围栏启用状态不可复制 String FENCE_STATUS_CANNOT_COPIED = "fence.status.cannot.copied"; // 订单创建失败 String ORDER_CREATE_FAIL = "order.create.fail"; //开通城市已存在 String OPEN_CITY_EXISTS = "open.city.exists"; //定价已存在 String PRICING_EXISTS = "pricing.exists"; //定价不存在 String PRICING_NOT_EXIST = "pricing.not.exist"; //价格配置json格式错误 String PRICING_JSON_INVALID = "pricing.json.invalid"; //价格规则不能为空 String PRICING_RULE_EMPTY = "pricing.rule.empty"; // 路径不存在 String PATH_NOT_FOUND = "path.not.found"; } xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/controller/XpPricingController.java
New file @@ -0,0 +1,103 @@ package com.xiaopeng.itinerary.controller; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.xiaopeng.api.dto.map.RouteAndFareDto; import com.xiaopeng.api.dto.operation.LonLatDto; import com.xiaopeng.api.model.operation.XpPricing; import com.xiaopeng.api.vo.operation.RouteAndFareVo; import com.xiaopeng.common.core.domain.R; import com.xiaopeng.common.core.web.controller.BaseController; import com.xiaopeng.itinerary.dto.XpOrderDto; import com.xiaopeng.itinerary.service.IXpGeoQueryEntityService; import com.xiaopeng.itinerary.service.XpPricingService; import com.xiaopeng.itinerary.vo.SaveOrderVo; import com.xiaopeng.unifiedverification.interceptor.XpClientCheck; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; import javax.validation.Valid; /** * <p> * 费项管理 前端控制器 * </p> * * @author mitao * @since 2026-02-03 */ @Slf4j @RestController @RequestMapping("") @Api(tags = "费用管理") public class XpPricingController extends BaseController { private final XpPricingService xpPricingService; private final IXpGeoQueryEntityService xpGeoQueryEntityService; public XpPricingController(XpPricingService xpPricingService, IXpGeoQueryEntityService xpGeoQueryEntityService) { this.xpPricingService = xpPricingService; this.xpGeoQueryEntityService = xpGeoQueryEntityService; } @PostMapping("/base/v1/mini-program/navigation/route-and-fare") @ApiOperation(value = "导航-获取路线和费用", tags = {"小程序-获取路线和费用"}, response = RouteAndFareVo.class) @XpClientCheck(checkToken = false, checkOtpToken = true) public R<RouteAndFareVo> getRouteAndFare(@RequestBody @Valid RouteAndFareDto routeAndFareDto, HttpServletRequest request) { // uid已通过拦截器otp校验,可以直接使用 String uid = request.getHeader("XP-Uid"); return R.ok(xpPricingService.getRouteAndFare(routeAndFareDto, uid)); } @GetMapping("/base/v1/mini-program/pricing/rules/{openCityId}") @ApiOperation(value = "获取费用规则", tags = {"小程序-根据开通城市获取费用规则"}, response = String.class) public R<?> getPricingRules(@PathVariable Long openCityId) { XpPricing xpPricing = xpPricingService.getOne(new LambdaQueryWrapper<XpPricing>().eq(XpPricing::getCityId, openCityId).eq(XpPricing::getPricingType, 1).last("limit 1")); if (xpPricing != null) { return R.ok(xpPricing.getPriceRule()); } return R.ok(""); } /** * 获取等待时间 * @param dto * @return */ @PostMapping("/base/v1/mini-program/pricing/getWaitTime") @ApiOperation(value = "获取等待时间", tags = {"小程序-获取等待时间"}, response = Integer.class) public R<Integer> getWaitTime(@RequestBody LonLatDto dto) { return R.ok(xpPricingService.getWaitTime(dto.getLongitude(), dto.getLatitude())); } /** * 播单 */ @PostMapping("/base/v1/mini-program/order/save") @ApiOperation(value = "保存订单", tags = {"小程序-保存订单"}) @XpClientCheck(checkToken = false, checkOtpToken = true) public R<SaveOrderVo> saveOrder(@RequestBody @Validated XpOrderDto dto, HttpServletRequest request) { String uid = request.getHeader("XP-Uid"); Long l = xpPricingService.saveOrder(dto, uid); SaveOrderVo saveOrderVo = new SaveOrderVo(); saveOrderVo.setOrderId(l); return R.ok(saveOrderVo); } } xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/dto/XpOrderDto.java
New file @@ -0,0 +1,52 @@ package com.xiaopeng.itinerary.dto; import com.fasterxml.jackson.annotation.JsonFormat; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import javax.validation.constraints.NotNull; import java.time.LocalDateTime; @Data @ApiModel("拨单信息Dto") public class XpOrderDto { @ApiModelProperty("开通城市id") @NotNull(message = "开通城市id不能为空") private Long openCityId; @ApiModelProperty("区域id") @NotNull(message = "区域id不能为空") private Long xpElectronicFenceAreaId; @ApiModelProperty("起点站点id") @NotNull(message = "起点站点id不能为空") private Long startPointId; @ApiModelProperty("终点站点id") @NotNull(message = "终点站点id不能为空") private Long endPointId; @ApiModelProperty("预计时间") @NotNull(message = "预计时间不能为空") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime estimatedTime; @ApiModelProperty("下单经度") @NotNull(message = "下单经度不能为空") private Double placementLon; @ApiModelProperty("下单纬度") @NotNull(message = "下单纬度不能为空") private Double placementLat; @ApiModelProperty("下单地址") @NotNull(message = "下单地址不能为空") private String placementAddress; } xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/mapper/XpElectronicFenceAreaMapper.java
New file @@ -0,0 +1,16 @@ package com.xiaopeng.itinerary.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.xiaopeng.api.model.operation.XpElectronicFenceArea; /** * <p> * 电子围栏区域构成 Mapper 接口 * </p> * * @author mitao * @since 2026-02-03 */ public interface XpElectronicFenceAreaMapper extends BaseMapper<XpElectronicFenceArea> { } xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/mapper/XpElectronicFenceMapper.java
New file @@ -0,0 +1,21 @@ package com.xiaopeng.itinerary.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.xiaopeng.api.model.operation.XpElectronicFence; import com.xiaopeng.api.query.operation.ElectronicPageListQuery; import com.xiaopeng.common.core.web.page.PageInfo; import org.apache.ibatis.annotations.Param; import java.util.List; /** * <p> * 电子围栏 Mapper 接口 * </p> * * @author mitao * @since 2026-02-03 */ public interface XpElectronicFenceMapper extends BaseMapper<XpElectronicFence> { } xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/mapper/XpOpenCityMapper.java
New file @@ -0,0 +1,17 @@ package com.xiaopeng.itinerary.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.xiaopeng.api.model.operation.XpOpenCity; /** * <p> * 开通城市 Mapper 接口 * </p> * * @author liu * @since 2026-02-03 */ public interface XpOpenCityMapper extends BaseMapper<XpOpenCity> { } xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/mapper/XpPointMapper.java
New file @@ -0,0 +1,19 @@ package com.xiaopeng.itinerary.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.xiaopeng.api.model.operation.XpPoint; /** * <p> * 点位管理 Mapper 接口 * </p> * * @author liu * @author WuGuanFengYue * @since 2026-02-03 */ public interface XpPointMapper extends BaseMapper<XpPoint> { } xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/mapper/XpPricingMapper.java
New file @@ -0,0 +1,17 @@ package com.xiaopeng.itinerary.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.xiaopeng.api.model.operation.XpPricing; /** * <p> * 定价管理 Mapper 接口 * </p> * * @author liu * @since 2026-02-03 */ public interface XpPricingMapper extends BaseMapper<XpPricing> { } xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/mapper/XpRoutePlanMapper.java
New file @@ -0,0 +1,16 @@ package com.xiaopeng.itinerary.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.xiaopeng.api.model.operation.XpRoutePlan; /** * <p> * 路线规划配置 Mapper 接口 * </p> * * @author mitao * @since 2026-02-03 */ public interface XpRoutePlanMapper extends BaseMapper<XpRoutePlan> { } xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/mapper/XpSystemSettingMapper.java
New file @@ -0,0 +1,17 @@ package com.xiaopeng.itinerary.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.xiaopeng.api.model.operation.XpSystemSetting; /** * <p> * 系统设置表 Mapper 接口 * </p> * * @author liu * @since 2026-02-03 */ public interface XpSystemSettingMapper extends BaseMapper<XpSystemSetting> { } xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/mapper/XpTradingStrategyMapper.java
New file @@ -0,0 +1,16 @@ package com.xiaopeng.itinerary.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.xiaopeng.api.model.operation.XpTradingStrategy; /** * <p> * 交易策略配置 Mapper 接口 * </p> * * @author mitao * @since 2026-02-03 */ public interface XpTradingStrategyMapper extends BaseMapper<XpTradingStrategy> { } xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/mapper/XpWhitelistMapper.java
New file @@ -0,0 +1,16 @@ package com.xiaopeng.itinerary.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.xiaopeng.api.model.operation.XpWhitelist; /** * <p> * 白名单管理 Mapper 接口 * </p> * * @author mitao * @since 2026-02-03 */ public interface XpWhitelistMapper extends BaseMapper<XpWhitelist> { } xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/repository/XpGeoQueryEntityRepository.java
New file @@ -0,0 +1,64 @@ package com.xiaopeng.itinerary.repository; import com.xiaopeng.api.model.operation.XpGeoQueryEntity; import org.springframework.data.geo.Distance; import org.springframework.data.geo.Point; import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.data.mongodb.repository.Query; import java.util.Date; import java.util.List; /** * MongoDB Repository 接口 * 用于支持地理空间查询和其他操作 */ public interface XpGeoQueryEntityRepository extends MongoRepository<XpGeoQueryEntity, String> { /** * 通过经纬度查询范围1000米内、一周内、状态>1的数据 * @param point 中心点坐标 * @param distance 距离范围 * @param startTime 开始时间(一周前) * @param status 状态阈值 * @return 符合条件的实体列表 */ @Query("{ 'location' : { '$nearSphere' : ?0, '$maxDistance' : ?1 }, 'timestamp' : { '$gte' : ?2 } }") List<XpGeoQueryEntity> findByLocationNearAndTimestampAfterAndStatusGreaterThan( Point point, Distance distance, Date startTime, Integer status ); /** * 通过经纬度查询范围500米内、一周内、指定时段、状态>1的数据 * @param point 中心点坐标 * @param distance 距离范围 * @param startTime 开始时间(一周前) * @param time 时段(格式:HH-HH) * @param status 状态阈值 * @return 符合条件的实体列表 */ @Query("{ 'location' : { '$nearSphere' : ?0, '$maxDistance' : ?1 }, 'timestamp' : { '$gte' : ?2 }, 'time' : ?3 }") List<XpGeoQueryEntity> findByLocationNearAndTimestampAfterAndTimeAndStatusGreaterThan( Point point, Integer distance, Date startTime, String time, Integer status ); /** * 通过时间范围和状态查询数据 * @param startTime 开始时间 * @param endTime 结束时间 * @param status 状态阈值 * @return 符合条件的实体列表 */ List<XpGeoQueryEntity> findByTimestampBetweenAndStatusGreaterThan( Date startTime, Date endTime, Integer status ); } xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/service/IXpGeoQueryEntityService.java
New file @@ -0,0 +1,57 @@ package com.xiaopeng.itinerary.service; import com.xiaopeng.api.model.operation.XpGeoQueryEntity; import java.util.List; /** * MongoDB 地理查询服务接口 */ public interface IXpGeoQueryEntityService { /** * 通过经纬度查询固定范围1000米内、一周内、状态>1的数据 * @param longitude 经度 * @param latitude 纬度 */ List<XpGeoQueryEntity> findByLocationWithin500MetersAndLastWeekAndStatusGreater( double longitude, double latitude, String time ); /** * 保存地理查询实体 * @param entity 地理查询实体 * @return 保存后的实体 */ XpGeoQueryEntity save(XpGeoQueryEntity entity); /** * 修改 */ XpGeoQueryEntity updateByOrderId(String orderId); /** * 根据ID查询实体 * @param id 实体ID * @return 实体 */ XpGeoQueryEntity findById(String id); /** * 删除实体 * @param id 实体ID */ void deleteById(String id); /** * 查询所有实体 * @return 实体列表 */ List<XpGeoQueryEntity> findAll(); } xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/service/XpPointService.java
New file @@ -0,0 +1,30 @@ package com.xiaopeng.itinerary.service; import com.baomidou.mybatisplus.extension.service.IService; import com.xiaopeng.api.model.operation.XpPoint; import java.time.LocalTime; /** * <p> * 点位管理 服务类 * </p> * * @author liu * @author WuGuanFengYue * @since 2026-02-03 */ public interface XpPointService extends IService<XpPoint> { /** * 判断点位是否时间段内 * @param now * @return */ boolean getInTime(LocalTime now, Integer effectivePeriod, String effectiveTimePeriod); } xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/service/XpPricingService.java
New file @@ -0,0 +1,50 @@ package com.xiaopeng.itinerary.service; import com.baomidou.mybatisplus.extension.service.IService; import com.xiaopeng.api.dto.map.RouteAndFareDto; import com.xiaopeng.api.model.operation.XpPricing; import com.xiaopeng.api.vo.operation.RouteAndFareVo; import com.xiaopeng.itinerary.dto.XpOrderDto; /** * <p> * 定价管理 服务类 * </p> * * @author liu * @since 2026-02-03 */ public interface XpPricingService extends IService<XpPricing> { /** * + * 获取导航-获取路线和费用 * @param routeAndFareDto dto对象 * @param userId 用户id * @return */ RouteAndFareVo getRouteAndFare(RouteAndFareDto routeAndFareDto, String userId); /** * 获取等待时间 * @param lon 经度 * @param lat 纬度 * @return */ Integer getWaitTime(double lon,double lat); /** * 保存订单 * @param dto * @param uid * @return */ Long saveOrder(XpOrderDto dto, String uid); } xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/service/impl/XpGeoQueryEntityServiceImpl.java
New file @@ -0,0 +1,143 @@ package com.xiaopeng.itinerary.service.impl; import com.xiaopeng.api.model.operation.XpGeoQueryEntity; import com.xiaopeng.itinerary.repository.XpGeoQueryEntityRepository; import com.xiaopeng.itinerary.service.IXpGeoQueryEntityService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.geo.GeoResults; import org.springframework.data.geo.Metrics; import org.springframework.data.geo.Point; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.NearQuery; import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.query.Update; import org.springframework.stereotype.Service; import java.util.Calendar; import java.util.Date; import java.util.List; import java.util.stream.Collectors; /** * MongoDB 地理查询服务实现类 */ @Service public class XpGeoQueryEntityServiceImpl implements IXpGeoQueryEntityService { @Autowired private XpGeoQueryEntityRepository repository; @Autowired private MongoTemplate mongoTemplate; /** * 通过经纬度查询固定范围500米内、一周内、指定时段的数据 * @param longitude 经度 * @param latitude 纬度 * @param time 时段(格式:HH-HH,例如:19-20) * @return 符合条件的实体列表 */ @Override public List<XpGeoQueryEntity> findByLocationWithin500MetersAndLastWeekAndStatusGreater( double longitude, double latitude, String time ) { // // 计算一周前的时间 Calendar calendar = Calendar.getInstance(); calendar.setTime(new Date()); calendar.add(Calendar.DAY_OF_YEAR, -7); Long startTime = calendar.getTime().getTime(); // 1. 创建查询点 Point referencePoint = new Point(longitude, latitude); // 2. 构建 NearQuery - 完全使用 NearQuery 的 API NearQuery nearQuery = NearQuery.near(referencePoint, Metrics.KILOMETERS) .maxDistance(0.5) // 转成公里 .spherical(true); // 3.时间>startTime time = time参数 // 3. 创建额外的查询条件 Query query = new Query(); query.addCriteria(Criteria.where("timestamp").gte(startTime)); query.addCriteria(Criteria.where("time").is(time)); // 将 NearQuery 和额外的查询条件合并 nearQuery.query(query); // 4. 执行查询 - 明确指定集合名 GeoResults<XpGeoQueryEntity> geoResults = mongoTemplate.geoNear( nearQuery, XpGeoQueryEntity.class, "xp_geo_query_entity" // 明确指定集合名,避免映射错误 ); // 5. 提取结果 return geoResults.getContent().stream() .map(geoResult -> geoResult.getContent()) .collect(Collectors.toList()); } /** * 保存地理查询实体 * @param entity 地理查询实体 * @return 保存后的实体 */ @Override public XpGeoQueryEntity save(XpGeoQueryEntity entity) { return repository.save(entity); } @Override public XpGeoQueryEntity updateByOrderId(String orderId) { Query query = new Query(Criteria.where("orderId").is(orderId)); XpGeoQueryEntity xpGeoQueryEntity = mongoTemplate.findOne(query, XpGeoQueryEntity.class); Long timestamp = xpGeoQueryEntity.getTimestamp(); long time = new Date().getTime(); // 计算2个时间的秒数差 long seconds = (time- timestamp) / 1000; Update update = new Update().set("seconds", seconds).set("status", 1); if(xpGeoQueryEntity.getStatus()==0){ mongoTemplate.findAndModify(query, update, XpGeoQueryEntity.class); } return null; } /** * 根据ID查询实体 * @param id 实体ID * @return 实体 */ @Override public XpGeoQueryEntity findById(String id) { return repository.findById(id).orElse(null); } /** * 删除实体 * @param id 实体ID */ @Override public void deleteById(String id) { repository.deleteById(id); } /** * 查询所有实体 * @return 实体列表 */ @Override public List<XpGeoQueryEntity> findAll() { return repository.findAll(); } } xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/service/impl/XpPointServiceImpl.java
New file @@ -0,0 +1,87 @@ package com.xiaopeng.itinerary.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.xiaopeng.api.dto.operation.PointMapQuery; import com.xiaopeng.api.dto.order.HistoryEndPointsDto; import com.xiaopeng.api.dto.order.HistoryStartPointsDto; import com.xiaopeng.api.model.operation.XpPoint; import com.xiaopeng.api.model.operation.XpSystemSetting; import com.xiaopeng.api.query.operation.PointPageListQuery; import com.xiaopeng.api.vo.order.XpHistoryEndPointsVo; import com.xiaopeng.api.vo.order.XpHistoryStartPointsVo; import com.xiaopeng.api.vo.order.XpPointsListVo; import com.xiaopeng.api.vo.order.XpPointsSearchVo; import com.xiaopeng.common.core.constant.CacheConstants; import com.xiaopeng.common.redis.service.RedisService; import com.xiaopeng.itinerary.mapper.XpPointMapper; import com.xiaopeng.itinerary.mapper.XpSystemSettingMapper; import com.xiaopeng.itinerary.service.XpPointService; import com.xiaopeng.itinerary.utils.TimeRangeChecker; import com.xiaopeng.itinerary.utils.holiday.HolidayUtil; import com.xiaopeng.itinerary.utils.map.FastDistanceCalculator; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.time.LocalTime; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; /** * <p> * 点位管理 服务实现类 * </p> * * @author liu * @author WuGuanFengYue * @since 2026-02-03 */ @Service public class XpPointServiceImpl extends ServiceImpl<XpPointMapper, XpPoint> implements XpPointService { @Resource private HolidayUtil holidayUtil; public boolean getInTime(LocalTime now, Integer effectivePeriod, String effectiveTimePeriod){ // 2非工作日 0工作日 1节假日 3调休日 Integer holidayToday = holidayUtil.isHolidayToday(); if(effectivePeriod==1){ boolean inTime = TimeRangeChecker.isCurrentTimeInRanges(now,effectiveTimePeriod); if (!inTime) { return false; } }else if(effectivePeriod==2){ if(holidayToday!=0){ return false; } boolean inTime = TimeRangeChecker.isCurrentTimeInRanges(now,effectiveTimePeriod); if (!inTime) { return false; } }else if(effectivePeriod==3){ if(holidayToday!=2){ return false; } boolean inTime = TimeRangeChecker.isCurrentTimeInRanges(now,effectiveTimePeriod); if (!inTime) { return false; } }else if(effectivePeriod==4){ if(holidayToday!=1){ return false; } boolean inTime = TimeRangeChecker.isCurrentTimeInRanges(now,effectiveTimePeriod); if (!inTime) { return false; } } return true; } } xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/service/impl/XpPricingServiceImpl.java
New file @@ -0,0 +1,668 @@ package com.xiaopeng.itinerary.service.impl; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.xiaopeng.api.dto.account.GetUserCouponDto; import com.xiaopeng.api.dto.map.RouteAndFareDto; import com.xiaopeng.api.dto.operation.LonLatDto; import com.xiaopeng.api.feignClient.account.XpAppUserClient; import com.xiaopeng.api.feignClient.account.XpAppUserCouponClient; import com.xiaopeng.api.feignClient.map.XpMapClient; import com.xiaopeng.api.feignClient.order.XpOrderClient; import com.xiaopeng.api.model.account.XpAppUser; import com.xiaopeng.api.model.operation.*; import com.xiaopeng.api.model.order.XpOrder; import com.xiaopeng.api.vo.account.UserCouponVo; import com.xiaopeng.api.vo.operation.FareVo; import com.xiaopeng.api.vo.operation.RouteAndFareVo; import com.xiaopeng.api.vo.operation.RouteVo; import com.xiaopeng.api.vo.order.XpPricingCardVo; import com.xiaopeng.common.core.constant.CacheConstants; import com.xiaopeng.common.core.domain.R; import com.xiaopeng.common.core.exception.ServiceException; import com.xiaopeng.common.core.utils.DateUtils; import com.xiaopeng.common.redis.service.RedisService; import com.xiaopeng.itinerary.constant.I18nKeyConstant; import com.xiaopeng.itinerary.dto.XpOrderDto; import com.xiaopeng.itinerary.mapper.*; import com.xiaopeng.itinerary.service.*; import com.xiaopeng.itinerary.utils.XpPricingUtil; import com.xiaopeng.itinerary.utils.holiday.HolidayUtil; import com.xiaopeng.itinerary.utils.map.FastDistanceCalculator; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.mongodb.core.geo.GeoJsonPoint; import org.springframework.stereotype.Service; import javax.annotation.PreDestroy; import javax.annotation.Resource; import java.time.LocalDateTime; import java.time.LocalTime; import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; /** * <p> * 定价管理 服务实现类 * </p> * * @author liu * @since 2026-02-03 */ @Service public class XpPricingServiceImpl extends ServiceImpl<XpPricingMapper, XpPricing> implements XpPricingService { @Resource private XpPricingUtil xpPricingUtil; @Resource private XpMapClient xpMapClient; @Resource private XpOpenCityMapper xpOpenCityMapper; @Resource private XpRoutePlanMapper xpRoutePlanMapper; @Resource private RedisService redisService; @Resource private XpSystemSettingMapper systemSettingMapper; @Resource private XpAppUserCouponClient xpAppUserCouponClient; @Resource private XpOrderClient xpOrderClient; @Resource private XpTradingStrategyMapper xpTradingStrategyMapper; @Resource private XpElectronicFenceAreaMapper xpElectronicFenceAreaMapper; @Resource private XpWhitelistMapper xpWhitelistMapper; @Resource private XpAppUserClient xpAppUserClient; @Resource private HolidayUtil holidayUtil; @Autowired private IXpGeoQueryEntityService geoQueryEntityService; @Autowired private XpElectronicFenceMapper xpElectronicFenceMapper; @Autowired private XpPointService xpPointService; // 自定义线程池用于并行执行任务 private final ExecutorService executorService; // 初始化自定义线程池 public XpPricingServiceImpl() { this.executorService = new ThreadPoolExecutor( 40, // 核心线程数 100, // 最大线程数 60, // 线程空闲时间(秒) TimeUnit.SECONDS, // 时间单位 new LinkedBlockingQueue<>(400), // 工作队列 new ThreadFactory() { private final AtomicInteger threadNumber = new AtomicInteger(1); @Override public Thread newThread(Runnable r) { Thread thread = new Thread(r); thread.setName("pricing-service-thread-" + threadNumber.getAndIncrement()); thread.setDaemon(false); return thread; } }, // 线程工厂 new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略 ); } // 关闭线程池 @PreDestroy public void destroy() { if (executorService != null && !executorService.isShutdown()) { executorService.shutdown(); try { if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) { executorService.shutdownNow(); } } catch (InterruptedException e) { executorService.shutdownNow(); Thread.currentThread().interrupt(); } } } /** * 根据当前时间获取时段 * 例如:15:50 → 15-16 * @return 时段字符串,格式:HH-HH */ private String getCurrentTimeSlot() { Calendar calendar = Calendar.getInstance(); int hour = calendar.get(Calendar.HOUR_OF_DAY); return hour + "-" + (hour + 1); } @Override public RouteAndFareVo getRouteAndFare(RouteAndFareDto routeAndFareDto, String userId) { // 判断是否5分钟内进行了查询 相同站点的起始点 并且是同时段 String key = "xp-map-query-" + routeAndFareDto.getOpenCityId() + "-" + routeAndFareDto.getOrigin().getLongitude() + "-" + routeAndFareDto.getOrigin().getLatitude() + "-" + routeAndFareDto.getDestination().getLongitude() + "-" + routeAndFareDto.getDestination().getLatitude() + "-" + getCurrentTimeSlot() + "-" + userId; if (redisService.hasKey(key)) { String jsonStr = Objects.requireNonNull(redisService.getCacheObject(key)).toString(); RouteAndFareVo RouteAndFareVo = JSONObject.parseObject(jsonStr, RouteAndFareVo.class); return (RouteAndFareVo); } // 1. 基础数据查询 XpOpenCity xpOpenCity = xpOpenCityMapper.selectById(routeAndFareDto.getOpenCityId()); if (xpOpenCity == null) { throw new ServiceException("未找到步行路线", I18nKeyConstant.CITY_INFORMATION_DOES_NOT_EXIST); } String cityCode = xpOpenCity.getCityCode(); XpRoutePlan xpRoutePlan = xpRoutePlanMapper.selectOne(new LambdaQueryWrapper<XpRoutePlan>().eq(XpRoutePlan::getAuditStatus, 2).eq(XpRoutePlan::getCityCode, cityCode)); if (xpRoutePlan != null) { routeAndFareDto.setRoutePlanRule(xpRoutePlan.getRoutePlanRule()); } XpPricing xpPricing = this.baseMapper.selectOne(new LambdaQueryWrapper<XpPricing>().eq(XpPricing::getCityId, routeAndFareDto.getOpenCityId())); if (xpPricing == null) { throw new ServiceException("当前区域未开通服务", I18nKeyConstant.NO_CURRENT_REGION); } // // 算距离 R<RouteVo> distanceAndDuration = xpMapClient.getDistanceAndDuration(routeAndFareDto); if(distanceAndDuration.getCode()!=200){ throw new ServiceException("未找到路径", I18nKeyConstant.PATH_NOT_FOUND); } RouteVo routeVo = distanceAndDuration.getData(); // 2. 计算价格(需要在并行任务前完成,因为优惠券查询需要价格) XpPricingCardVo xpPricingCardVo; if (xpPricing.getPricingType() == 1) { xpPricingCardVo = xpPricingUtil.calculationPrice(userId, JSON.parseObject(xpPricing.getBasicPricingJson()), JSON.parseArray(xpPricing.getStartingFareJson()), routeVo.getDistance(), routeVo.getDuration(), 0, 0.0, xpPricing.getIsDynamicPrice(), routeAndFareDto.getOpenCityId(), xpPricing.getDynamicPricingJson() ); } else { xpPricingCardVo = xpPricingUtil.calculationFixedPrice(userId, JSON.parseObject(xpPricing.getBasicPricingJson()), JSON.parseArray(xpPricing.getStartingFareJson()), routeVo.getDistance(), routeVo.getDuration(),0, 0.0, xpPricing.getIsDynamicPrice(), routeAndFareDto.getOpenCityId(), xpPricing.getDynamicPricingJson() ); } // 3. 并行执行远程服务调用 判断是否同一经营区域 CompletableFuture<R<Boolean>> diagnosisAreaFuture = CompletableFuture.supplyAsync( () -> xpMapClient.diagnosisArea(routeAndFareDto), executorService ); // 优惠券 XpPricingCardVo finalXpPricingCardVo = xpPricingCardVo; CompletableFuture<R<UserCouponVo>> couponFuture = CompletableFuture.supplyAsync( () -> { // 优惠券 return xpAppUserCouponClient.getUserCoupon( new GetUserCouponDto(Long.valueOf(userId), finalXpPricingCardVo.getTotalPrice(),routeAndFareDto.getOpenCityId()) // new GetUserCouponDto(Long.valueOf(userId), 1d, routeAndFareDto.getOpenCityId()) ); }, executorService ); // 4. 等待所有并行任务完成并获取结果 R<Boolean> diagnosisResult = diagnosisAreaFuture.join(); R<UserCouponVo> couponResult = couponFuture.join(); // 5. 处理诊断结果 if (diagnosisResult.getCode() != R.SUCCESS) { throw new RuntimeException(diagnosisResult.getMsg()); } if (!diagnosisResult.getData()) { throw new ServiceException("当前区域未开通服务", I18nKeyConstant.NO_CURRENT_REGION); } // // 6. 处理路线结果 // if (routeResult.getCode() != 200) { // throw new RuntimeException(routeResult.getMsg()); // } // RouteVo routeVo = routeResult.getData(); // 起终点最小距离 走缓存 Integer minOrderDistance; Object minOrderDistanceObj = redisService.getCacheObject(CacheConstants.MIN_ORDER_DISTANCE); if (minOrderDistanceObj != null) { minOrderDistance = Integer.valueOf(minOrderDistanceObj.toString()); } else { minOrderDistance = systemSettingMapper.selectOne(null).getMinOrderDistance(); } if (routeVo.getDistance() < Integer.valueOf(minOrderDistance)) { throw new ServiceException("当前上下车点距离过近,建议步行前往", I18nKeyConstant.TO_WORK); } // 7. 处理优惠券结果 if (couponResult.getCode() != R.SUCCESS) { throw new RuntimeException(couponResult.getMsg()); } UserCouponVo userCouponVo = couponResult.getData(); if (userCouponVo != null) { xpPricingCardVo.setCoupon(String.valueOf(userCouponVo.getMoney())); } // 8. 构建响应对象 RouteAndFareVo routeAndFareVo = new RouteAndFareVo(); // MOCK数据 // RouteVo routeVo = new RouteVo(); // routeVo.setDistance(3000); // routeVo.setDuration(5); // routeVo.setRouteID("1"); // List<LonLatDto> routePolyline = Arrays.asList( // // 起点:天府新谷 // new LonLatDto(104.05363, 30.58937), // // // 途经点1:府城大道与交子北二路交叉口附近 // new LonLatDto(104.05821, 30.58745), // // // 途经点2:交子大道与成汉南路交叉口 // new LonLatDto(104.06418, 30.58492), // // // 途经点3:交子大道与万象南路交叉口 // new LonLatDto(104.06975, 30.58321), // // // 途经点4:交子大道与益州大道交叉口 // new LonLatDto(104.07366, 30.58198), // // // 终点:金融城(地铁站附近) // new LonLatDto(104.076544, 30.581039) // ); // routeVo.setPolyline(routePolyline); routeAndFareVo.setRoute(routeVo); routeAndFareVo.setXpPricingCard(xpPricingCardVo); FareVo fareVo = new FareVo(); fareVo.setEstimatedDistanceKm(routeVo.getDistance().doubleValue() / 1000); fareVo.setEstimatedDurationMin(routeVo.getDuration() / 60); // 9. 设置费用信息 fareVo.setOriginalFare(xpPricingCardVo.getTotalPrice()); if (userCouponVo != null) { fareVo.setDiscountAmount(userCouponVo.getMoney()); fareVo.setFinalFare(fareVo.getOriginalFare() - fareVo.getDiscountAmount()); fareVo.setCouponId(userCouponVo.getId()); } else { fareVo.setDiscountAmount(0.0); fareVo.setFinalFare(fareVo.getOriginalFare()); } // 10. 检查等待时间 Integer waitTime = getWaitTime(routeAndFareDto.getOrigin().getLongitude(), routeAndFareDto.getOrigin().getLatitude()); if (waitTime > 3) { throw new ServiceException("当前区域正在等待中,请耐心等待", I18nKeyConstant.WAITING_TIME); } fareVo.setWaitingTime(waitTime); // 11. 生成订单ID并缓存 String id = CacheConstants.ORD + System.currentTimeMillis() + "_" + UUID.randomUUID().toString().substring(0, 8); redisService.setCacheObject(CacheConstants.ORD + id + userId, xpPricingCardVo, 1l, TimeUnit.DAYS); fareVo.setId(id); // 12. 设置费用信息 routeAndFareVo.setFareEstimate(fareVo); String voJsonStr = JSONObject.toJSONString(routeAndFareVo); // 进行缓存 redisService.setCacheObject(key, voJsonStr, 5l, TimeUnit.MINUTES); return routeAndFareVo; } /** * 获取等待时间 * @param lon * @param lat * @return */ @Override public Integer getWaitTime(double lon, double lat) { List<XpGeoQueryEntity> entities = geoQueryEntityService.findByLocationWithin500MetersAndLastWeekAndStatusGreater( lon, lat, getCurrentTimeSlot() ); if (entities.isEmpty()) { return 1; } else { // 已完成接单的数据 List<XpGeoQueryEntity> validEntities = entities.stream().filter(e -> e.getStatus() == 1).collect(Collectors.toList()); // 计算平均等待时间 OptionalDouble averageOptional = validEntities.stream().mapToDouble(XpGeoQueryEntity::getSeconds).average(); if (averageOptional.isPresent()) { double average = averageOptional.getAsDouble(); // 未完成接单的数量 int count = entities.size() - validEntities.size(); if (count == 0) { return 1; } else { double waitMinutes = (average * count) / 60; if (waitMinutes > 3) { throw new ServiceException("当前区域正在等待中,请耐心等待", I18nKeyConstant.WAITING_TIME); } else if (waitMinutes == 0) { return 1; } return (int) waitMinutes; } } else { return 1; } } } @Override public Long saveOrder(XpOrderDto dto, String uid) { // 是否在运营区域时间内 XpElectronicFenceArea xpElectronicFenceArea = xpElectronicFenceAreaMapper.selectById(dto.getXpElectronicFenceAreaId()); XpElectronicFence xpElectronicFence = xpElectronicFenceMapper.selectById(xpElectronicFenceArea.getElectronicId()); String operationStrategy = xpElectronicFence.getOperationStrategy(); Boolean inOperationTime = isInOperationTime(operationStrategy); if (!inOperationTime) { throw new ServiceException("当前区域未开通服务", I18nKeyConstant.NO_CURRENT_REGION); } Integer cancelCount = xpTradingStrategyMapper.selectOne(null).getCancleCount(); // 判断是否有进行的订单 R<Long> r = xpOrderClient.hasUnPaodOrder(uid); if (r.getData() > 0) { throw new RuntimeException("有正在进行的订单"); } // 获取起点经纬度 XpPoint xpPoint = xpPointService.getById(dto.getStartPointId()); // 终点 XpPoint endXpPoint = xpPointService.getById(dto.getEndPointId()); // 判断点位是否在运营区域内 LocalTime now = LocalTime.now(); boolean inTime = xpPointService.getInTime(now, xpPoint.getEffectivePeriod(), xpPoint.getEffectiveTimePeriod()); if (!inTime) { throw new ServiceException("当前点位未开通服务", I18nKeyConstant.NO_CURRENT_REGION); } inTime = xpPointService.getInTime(now, endXpPoint.getEffectivePeriod(), endXpPoint.getEffectiveTimePeriod()); if (!inTime) { throw new ServiceException("当前点位未开通服务", I18nKeyConstant.NO_CURRENT_REGION); } // 判断当天取消的次数 后台配置 redis 查询 Object cancelOrder = redisService.getCacheObject("CANCEL_ORDER_" + uid); // 静默处理 boolean isSilent = false; if (cancelOrder != null) { Integer num = Integer.valueOf(cancelOrder.toString()); if (num >= cancelCount) { isSilent = true; } } // 获取起步里程 // XpPricing xpPricing = this.baseMapper.selectOne(new LambdaQueryWrapper<XpPricing>().eq(XpPricing::getCityId, dto.getOpenCityId())); // Integer startMileage = XpPricingUtil.getStartMileage(xpPricing.getStartingFareJson()); // if (startMileage == null) { // throw new ServiceException("当前区域未开通服务", I18nKeyConstant.NO_CURRENT_REGION); // } // 根据id获取手机号 Boolean isPriorityDispatch = false; R<XpAppUser> xpAppUserR = xpAppUserClient.queryOperationsUserById(Long.valueOf(uid)); if (xpAppUserR.getCode() == R.SUCCESS) { Long count = xpWhitelistMapper.selectCount(new LambdaQueryWrapper<XpWhitelist>().eq(XpWhitelist::getAccount, xpAppUserR.getData().getPhoneNumber()).eq(XpWhitelist::getIsPriorityDispatch, 1)); if (count > 0) { isPriorityDispatch = true; } } // 生成订单数据 XpOrder xpOrder = new XpOrder(); xpOrder.setVehicleId(1l); // xpOrder.setStartMileage(Double.valueOf(startMileage)); xpOrder.setAppUserId(Long.valueOf(uid)); xpOrder.setOrderNumber("XP" + System.currentTimeMillis()); xpOrder.setPlacementLon(dto.getPlacementLon()); xpOrder.setPlacementLat(dto.getPlacementLat()); xpOrder.setPlacementAddress(dto.getPlacementAddress()); xpOrder.setCityId(dto.getOpenCityId()); xpOrder.setStartLon(xpPoint.getPointLon()); xpOrder.setStartLat(xpPoint.getPointLat()); xpOrder.setBoardingLon(xpPoint.getPointLon()); xpOrder.setBoardingLat(xpPoint.getPointLat()); xpOrder.setBoardingAddress(xpPoint.getPointAddress()); xpOrder.setEndLon(endXpPoint.getPointLon()); xpOrder.setEndLat(endXpPoint.getPointLat()); xpOrder.setStartAddress(xpPoint.getPointAddress()); xpOrder.setEndAddress(endXpPoint.getPointAddress()); xpOrder.setXpElectronicFenceAreaId(dto.getXpElectronicFenceAreaId()); xpOrder.setStartPointId(dto.getStartPointId()); xpOrder.setEndPointId(dto.getEndPointId()); xpOrder.setStartPointName(xpPoint.getPointName()); xpOrder.setEndPointName(endXpPoint.getPointName()); // 当前时段 String currentTimeSlot = DateUtils.getCurrentTimeSlot(); xpOrder.setTimeSlot(currentTimeSlot); xpOrder.setCreateTime(LocalDateTime.now()); // 计算距离 和 时间 RouteAndFareDto routeAndFareDto = new RouteAndFareDto(); LonLatDto lonLatDto = new LonLatDto(); lonLatDto.setLongitude(xpPoint.getPointLon()); lonLatDto.setLatitude(xpPoint.getPointLat()); routeAndFareDto.setOrigin(lonLatDto); LonLatDto lonLatDto1 = new LonLatDto(); lonLatDto1.setLongitude(endXpPoint.getPointLon()); lonLatDto1.setLatitude(endXpPoint.getPointLat()); routeAndFareDto.setDestination(lonLatDto1); XpOpenCity xpOpenCity = xpOpenCityMapper.selectById(dto.getOpenCityId()); XpRoutePlan xpRoutePlan = xpRoutePlanMapper.selectOne(new LambdaQueryWrapper<XpRoutePlan>().eq(XpRoutePlan::getAuditStatus, 2).eq(XpRoutePlan::getCityCode, xpOpenCity.getCityCode())); if (xpRoutePlan != null) { routeAndFareDto.setRoutePlanRule(xpRoutePlan.getRoutePlanRule()); } R<RouteVo> distanceAndDuration = xpMapClient.getDistanceAndDuration(routeAndFareDto); Integer distance = 0; Integer duration = 0; if (distanceAndDuration.getCode() != 200) { // throw new ServiceException("订单生成失败", I18nKeyConstant.ORDER_CREATE_FAIL); } // distance = distanceAndDuration.getData().getDistance(); // duration = distanceAndDuration.getData().getDuration(); // 产生订单 R<Long> addOrderR = xpOrderClient.addOrder(xpOrder); if (addOrderR.getCode() != R.SUCCESS) { throw new ServiceException("订单生成失败", I18nKeyConstant.ORDER_CREATE_FAIL); } Long orderId = addOrderR.getData(); // 静默处理 不推单 if (isSilent) { return orderId; } // 最大距离 Object maxPushDistanceObj = redisService.getCacheObject(CacheConstants.MAX_PUSH_DISTANCE); Double maxPushDistance = 0.0; if (maxPushDistanceObj != null) { maxPushDistance = Double.valueOf(maxPushDistanceObj.toString()); } else { maxPushDistance = xpTradingStrategyMapper.selectOne(null).getThreePushDistance(); } // 找出现在所有的队列 通过计算距离 算出目前应该放在那个队列 如果没有符合的新增一个队列 符合后看是否白名单账号 是则直接放在队列前端 Set<String> quoteMatchName = redisService.getQuoteMatchName(CacheConstants.QUEUE_POINT_ID); for (String s : quoteMatchName) { // QUEUE_POINT_ID-1-30.233,120.233 String lonLat = s.split("-")[2]; // 和上车点进行判断是否<maxPushDistance double v = FastDistanceCalculator.distanceFast(Double.parseDouble(lonLat.split(",")[1]), Double.parseDouble(lonLat.split(",")[0]), xpPoint.getPointLat(), xpPoint.getPointLon()); if (v < maxPushDistance) { if (isPriorityDispatch) { // 头部 value 订单_经纬度_状态_用户id_订单编号_距离_时间 redisService.addToTail(s, orderId + "_" + xpPoint.getPointLon() + "," + xpPoint.getPointLat() + "_0" + "_" + uid + "_" + xpOrder.getOrderNumber() + "_" + distance + "_" + duration + "_" + routeAndFareDto.getRoutePlanRule()); } else { // 尾部 redisService.addToTail(s, orderId + "_" + xpPoint.getPointLon() + "," + xpPoint.getPointLat() + "_0" + "_" + uid + "_" + xpOrder.getOrderNumber() + "_" + distance + "_" + duration + "_" + routeAndFareDto.getRoutePlanRule()); } xpOrder.setQueueName(s); xpOrderClient.updateOrder(xpOrder); // 存储mongodb 数据 XpGeoQueryEntity xpGeoQueryEntity = new XpGeoQueryEntity(); xpGeoQueryEntity.setOrderId(orderId.toString()); xpGeoQueryEntity.setLocation(new GeoJsonPoint(xpPoint.getPointLon(), xpPoint.getPointLat())); // 计算2个时间的秒数 xpGeoQueryEntity.setTime(currentTimeSlot); xpGeoQueryEntity.setStatus(0); xpGeoQueryEntity.setTimestamp(System.currentTimeMillis()); geoQueryEntityService.save(xpGeoQueryEntity); return orderId; } } String key = CacheConstants.QUEUE_POINT_ID + "-" + xpPoint.getId() + "-" + xpPoint.getPointLon() + "," + xpPoint.getPointLat(); redisService.setCacheList(key, Arrays.asList(orderId + "_" + xpPoint.getPointLon() + "," + xpPoint.getPointLat() + "_0" + "_" + uid + "_" + xpOrder.getOrderNumber() + "_" + distance + "_" + duration + "_" + routeAndFareDto.getRoutePlanRule())); xpOrder.setQueueName(key); xpOrderClient.updateOrder(xpOrder); // 存储mongodb 数据 XpGeoQueryEntity xpGeoQueryEntity = new XpGeoQueryEntity(); xpGeoQueryEntity.setOrderId(orderId.toString()); xpGeoQueryEntity.setLocation(new GeoJsonPoint(xpPoint.getPointLon(), xpPoint.getPointLat())); // 计算2个时间的秒数 xpGeoQueryEntity.setTime(currentTimeSlot); xpGeoQueryEntity.setStatus(0); geoQueryEntityService.save(xpGeoQueryEntity); return orderId; // // XpElectronicFenceArea xpElectronicFenceArea = xpElectronicFenceAreaMapper.selectById(dto.getXpElectronicFenceAreaId()); // // 获取电子围栏的车辆 // R<List<XpVehicle>> vehicles = xpVehicleClient.listByFenceId(xpElectronicFenceArea.getElectronicId()); // // 产生订单 存订单数据 // 下发指令 消息推送用户端 // return null; } private Boolean isInOperationTime(String operationStrategy) { JSONArray jsonArray = JSONArray.parseArray(operationStrategy); Integer holidayToday = holidayUtil.isHolidayToday(); for (Object o : jsonArray) { JSONObject jsonObject = JSONObject.parseObject(o.toString()); String type = jsonObject.getString("type"); if (type.equals(holidayToday.toString())) { JSONArray time = jsonObject.getJSONArray("time"); // 判断当前时间 是否在time时间段中 for (Object object : time) { String[] timeArray = object.toString().split("-"); String startTime = timeArray[0]; String endTime = timeArray[1]; boolean currentTimeInRange = DateUtils.isCurrentTimeInRange(startTime, endTime); if (currentTimeInRange) { return true; } } } else if (type.equals("3")) { // 判断是什么类型 0每天 1-7 想起 String typeStr = jsonObject.getString("typeStr"); // 查看今天周几 Integer week = DateUtils.getWeek(); // 包含每天 或者 包含当天 if (typeStr.contains("0") || typeStr.contains(week.toString())) { JSONArray time = jsonObject.getJSONArray("time"); // 判断当前时间 是否在time时间段中 for (Object object : time) { String[] timeArray = object.toString().split("-"); String startTime = timeArray[0]; String endTime = timeArray[1]; boolean currentTimeInRange = DateUtils.isCurrentTimeInRange(startTime, endTime); if (currentTimeInRange) { return true; } } } } } return false; } } xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/utils/TimeRangeChecker.java
New file @@ -0,0 +1,85 @@ package com.xiaopeng.itinerary.utils; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import java.time.LocalTime; import java.time.format.DateTimeFormatter; public class TimeRangeChecker { private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("HH:mm"); /** * 判断当前时间是否在时间段内 * @param jsonArrayStr JSON数组字符串,格式如 ["08:00-12:00", "13:30-17:30", "18:00-21:00"] * @return true 如果在时间段内,false 如果不在 */ public static boolean isCurrentTimeInRanges(LocalTime now,String jsonArrayStr) { return isTimeInRanges(jsonArrayStr, now); } /** * 判断指定时间是否在时间段内 * @param jsonArrayStr JSON数组字符串 * @param time 要判断的时间 * @return true 如果在时间段内,false 如果不在 */ public static boolean isTimeInRanges(String jsonArrayStr, LocalTime time) { if (jsonArrayStr == null || jsonArrayStr.isEmpty()) { return false; } try { // 直接解析为JSONArray JSONArray timeArray = JSON.parseArray(jsonArrayStr); if (timeArray == null || timeArray.isEmpty()) { return false; } // 遍历所有时间段 for (int i = 0; i < timeArray.size(); i++) { String timeRange = timeArray.getString(i); if (isTimeInRange(time, timeRange)) { return true; } } return false; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 判断时间是否在单个时间段内 */ private static boolean isTimeInRange(LocalTime time, String timeRange) { if (timeRange == null || !timeRange.contains("-")) { return false; } String[] parts = timeRange.split("-"); if (parts.length != 2) { return false; } try { LocalTime start = LocalTime.parse(parts[0].trim(), TIME_FORMATTER); LocalTime end = LocalTime.parse(parts[1].trim(), TIME_FORMATTER); // 处理跨天的情况(如 22:00-02:00) if (end.isBefore(start)) { // 跨天时间段 return !time.isBefore(start) || !time.isAfter(end); } else { // 正常时间段 return !time.isBefore(start) && !time.isAfter(end); } } catch (Exception e) { return false; } } } xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/utils/XpPricingUtil.java
New file @@ -0,0 +1,749 @@ package com.xiaopeng.itinerary.utils; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.xiaopeng.api.dto.order.GetOrderNumDto; import com.xiaopeng.api.feignClient.order.XpOrderClient; import com.xiaopeng.api.vo.order.XpPricingCardVo; import com.xiaopeng.common.core.domain.R; import com.xiaopeng.common.redis.service.RedisService; import com.xiaopeng.itinerary.utils.holiday.HolidayUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.Calendar; import java.util.Date; import java.util.concurrent.TimeUnit; /** * 计算价格的工具类 */ @Slf4j @Component public class XpPricingUtil { @Autowired private HolidayUtil holidayUtil; @Autowired private RedisService redisService; @Autowired private XpOrderClient xpOrderClient; /** * 获取起步里程 * @param startingFareJsonStr * @return */ public static Integer getStartMileage(String startingFareJsonStr) { Date date = new Date(); JSONArray startingFareJson = JSONObject.parseArray(startingFareJsonStr); for (Object o : startingFareJson) { JSONObject startingFare = (JSONObject) o; Calendar calendarStart = Calendar.getInstance(); calendarStart.setTime(date); calendarStart.set(Calendar.HOUR_OF_DAY, Integer.valueOf(startingFare.getString("num1").split(":")[0])); calendarStart.set(Calendar.MINUTE, Integer.valueOf(startingFare.getString("num1").split(":")[1])); Calendar calendarEnd = Calendar.getInstance(); calendarEnd.setTime(date); calendarEnd.set(Calendar.HOUR_OF_DAY, Integer.valueOf(startingFare.getString("num2").split(":")[0])); calendarEnd.set(Calendar.MINUTE, Integer.valueOf(startingFare.getString("num2").split(":")[1])); // 找到符合条件的直接跳出循环 if (date.getTime() > calendarStart.getTimeInMillis() && date.getTime() < calendarEnd.getTimeInMillis()) { Integer mileage = startingFare.getInteger("num4"); return mileage; } } return null; } /** * 计算一口价 */ public XpPricingCardVo calculationFixedPrice(String uid, JSONObject rule, JSONArray startingFareJson, double distance, long duration, long wait, Double waitMoney, Integer isDynamicPriceStatus, Long openCityId, String dynamicPricingJson) { // 默认费率1 double priceRate=1; if(isDynamicPriceStatus==1){ // 先判断是否已经调价过了 Object userDynamicPriceObj = redisService.getCacheObject("USER_DYNAMIC_PRICE_" + openCityId + "_" + uid); if(userDynamicPriceObj!=null){ // 是否调价过了 priceRate = Double.parseDouble(userDynamicPriceObj.toString()); }else { // 没有调过 //是否触发动态调价 redis获取 Object dynamicPriceObj = redisService.getCacheObject("DYNAMIC_PRICE_" + openCityId); if(dynamicPriceObj!=null){ JSONObject jsonObject = JSONObject.parseObject(dynamicPricingJson); // 触发动态调价 // value=时间 String timeStr = dynamicPriceObj.toString(); // 判断当前距离这个时间范围的新增订单数(排除取消) / 上浮数量 算出应该增加的价格率 R<Integer> r = xpOrderClient.getOrderNum(new GetOrderNumDto(timeStr,openCityId)); if(r.getData()==200){ int num = r.getData(); // 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_" + openCityId + "_" + uid, priceRate,time.longValue(), TimeUnit.MINUTES); } } } } } XpPricingCardVo xpPricingCardVo = new XpPricingCardVo(); double amount = 0; double realDistance = distance / 1000;//实际公里 double realTime = duration / 60;//实际时间 double exceedDistance = (realDistance - rule.getDouble("num2")) < 0 ? 0 : realDistance - rule.getDouble("num2");//超出起步里程的公里 double exceedTime = (realTime - rule.getDouble("num3")) < 0 ? 0 : realTime - rule.getDouble("num3");//超过起步分钟数的时间 // 起步价 Double startPrice = rule.getDouble("num1"); // 超出公里数价格 Double distancePrice = rule.getDouble("num4") * exceedDistance; // 超出时间价格 Double durationPrice = rule.getDouble("num5") * exceedTime; startPrice = new BigDecimal(startPrice).multiply(BigDecimal.valueOf(priceRate)).setScale(2, RoundingMode.HALF_UP).doubleValue(); xpPricingCardVo.setStartPrice(startPrice); xpPricingCardVo.setDistancePrice(distancePrice); xpPricingCardVo.setDurationPrice(durationPrice); xpPricingCardVo.setTotalPrice(startPrice + distancePrice + durationPrice); // 里程 xpPricingCardVo.setDistance(realDistance); xpPricingCardVo.setDuration(realTime); return xpPricingCardVo; } /** * 计算基础价格 * @param rule 基础规则 * @param distance 里程 * @param duration 时间 * @param wait 时间 * @param waitMoney 等待费 * @param openCityId 开通城市id * @param isDynamicPriceStatus 是否开启动态调价 * @return * @throws Exception */ public XpPricingCardVo calculationPrice(String uid,JSONObject rule, JSONArray startingFareJson, double distance, long duration, long wait, Double waitMoney,Integer isDynamicPriceStatus,Long openCityId,String dynamicPricingJson) { // 默认费率1 double priceRate=1; if(isDynamicPriceStatus==1){ // 先判断是否已经调价过了 Object userDynamicPriceObj = redisService.getCacheObject("USER_DYNAMIC_PRICE_" + openCityId + "_" + uid); if(userDynamicPriceObj!=null){ // 是否调价过了 priceRate = Double.parseDouble(userDynamicPriceObj.toString()); }else { // 没有调过 //是否触发动态调价 redis获取 Object dynamicPriceObj = redisService.getCacheObject("DYNAMIC_PRICE_" + openCityId); if(dynamicPriceObj!=null){ JSONObject jsonObject = JSONObject.parseObject(dynamicPricingJson); // 触发动态调价 // value=时间 String timeStr = dynamicPriceObj.toString(); // 判断当前距离这个时间范围的新增订单数(排除取消) / 上浮数量 算出应该增加的价格率 R<Integer> r = xpOrderClient.getOrderNum(new GetOrderNumDto(timeStr,openCityId)); if(r.getData()==200){ int num = r.getData(); // 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_" + openCityId + "_" + uid, priceRate,time.longValue(), TimeUnit.MINUTES); } } } } } XpPricingCardVo xpPricingCardVo = new XpPricingCardVo(); double amount = 0; //等待费 Date date = new Date(); // 计算出是在那个时间段内 for (Object o : startingFareJson) { JSONObject startingFare = (JSONObject) o; Calendar calendarStart = Calendar.getInstance(); calendarStart.setTime(date); calendarStart.set(Calendar.HOUR_OF_DAY, Integer.valueOf(startingFare.getString("num1").split(":")[0])); calendarStart.set(Calendar.MINUTE, Integer.valueOf(startingFare.getString("num1").split(":")[1])); Calendar calendarEnd = Calendar.getInstance(); calendarEnd.setTime(date); calendarEnd.set(Calendar.HOUR_OF_DAY, Integer.valueOf(startingFare.getString("num2").split(":")[0])); calendarEnd.set(Calendar.MINUTE, Integer.valueOf(startingFare.getString("num2").split(":")[1])); // 找到符合条件的直接跳出循环 if (date.getTime() > calendarStart.getTimeInMillis() && date.getTime() < calendarEnd.getTimeInMillis()) { rule.put("num1", startingFare.get("num3")); rule.put("num2", startingFare.get("num4")); rule.put("num3", startingFare.get("num5")); rule.put("num4", startingFare.get("num6")); rule.put("num5", startingFare.get("num7")); rule.put("startTime", startingFare.getString("num1")); rule.put("endTime", startingFare.getString("num2")); break; } } double realDistance = distance / 1000;//实际公里 double realTime = duration / 60;//实际时间 double exceedDistance = (realDistance - rule.getDouble("num2")) < 0 ? 0 : realDistance - rule.getDouble("num2");//超出起步里程的公里 double exceedTime = (realTime - rule.getDouble("num3")) < 0 ? 0 : realTime - rule.getDouble("num3");//超过起步分钟数的时间 double longDistanceOne = 0;//远途1段 double longDistanceTwo = 0;//远途2段 double longDistanceThree = 0;//远途3段 //夜间服务处理逻辑 Calendar calendarNight = Calendar.getInstance(); calendarNight.setTime(date); calendarNight.set(Calendar.HOUR_OF_DAY, Integer.valueOf(rule.getString("num14").split(" - ")[0].split(":")[0])); calendarNight.set(Calendar.MINUTE, Integer.valueOf(rule.getString("num14").split(" - ")[0].split(":")[1])); Calendar calendar = Calendar.getInstance(); calendar.setTime(date); calendar.set(Calendar.HOUR_OF_DAY, Integer.valueOf(rule.getString("num14").split(" - ")[1].split(":")[0])); calendar.set(Calendar.MINUTE, Integer.valueOf(rule.getString("num14").split(" - ")[1].split(":")[1])); // 先判断今日是否节假日 boolean holiday = holidayUtil.isHoliday(); if (holiday) { if (realDistance > rule.getDouble("num6")) { longDistanceOne = rule.getDouble("num32") * (realDistance - rule.getDouble("num6")); } if (realDistance > rule.getDouble("num9")) { longDistanceTwo = rule.getDouble("num33") * (realDistance - rule.getDouble("num9")); } if (realDistance > rule.getDouble("num12")) { longDistanceThree = rule.getDouble("num34") * (realDistance - rule.getDouble("num12")); } amount = rule.getDouble("num29") + (exceedDistance * rule.getDouble("num30")) + (exceedTime * rule.getDouble("num31")) + (wait * waitMoney) + longDistanceOne + longDistanceTwo + longDistanceThree; // 起步价 xpPricingCardVo.setStartPrice(rule.getDouble("num29")); // 时间段 xpPricingCardVo.setTime("节假日"); // 里程 xpPricingCardVo.setDistance(realDistance); // 里程费 double distancePrice = (exceedDistance * rule.getDouble("num30")) + longDistanceOne + longDistanceTwo + longDistanceThree; xpPricingCardVo.setDistancePrice(distancePrice); // 时长 xpPricingCardVo.setDuration(realTime); // 时长费 xpPricingCardVo.setDurationPrice((exceedTime * rule.getDouble("num31"))); } else { if (date.getTime() > calendarNight.getTimeInMillis() && date.getTime() < calendar.getTimeInMillis()) { if (realDistance > rule.getDouble("num6")) { longDistanceOne = rule.getDouble("num18") * (realDistance - rule.getDouble("num6")); } if (realDistance > rule.getDouble("num9")) { longDistanceTwo = rule.getDouble("num19") * (realDistance - rule.getDouble("num9")); } if (realDistance > rule.getDouble("num12")) { longDistanceThree = rule.getDouble("num20") * (realDistance - rule.getDouble("num12")); } amount = rule.getDouble("num15") + (exceedDistance * rule.getDouble("num16")) + (exceedTime * rule.getDouble("num17")) + (wait * waitMoney) + longDistanceOne + longDistanceTwo + longDistanceThree; // 起步价 xpPricingCardVo.setStartPrice(rule.getDouble("num15")); // 时间段 xpPricingCardVo.setTime(rule.getString("num14")); // 里程 xpPricingCardVo.setDistance(realDistance); // 里程费 double distancePrice = (exceedDistance * rule.getDouble("num16")) + longDistanceOne + longDistanceTwo + longDistanceThree; xpPricingCardVo.setDistancePrice(distancePrice); // 时长 xpPricingCardVo.setDuration(realTime); // 时长费 xpPricingCardVo.setDurationPrice((exceedTime * rule.getDouble("num17"))); } else { // 高峰处理 Calendar calendarStart = Calendar.getInstance(); calendarStart.setTime(date); calendarStart.set(Calendar.HOUR_OF_DAY, Integer.valueOf(rule.getString("num21").split(" - ")[0].split(":")[0])); calendarStart.set(Calendar.MINUTE, Integer.valueOf(rule.getString("num21").split(" - ")[0].split(":")[1])); Calendar calendarEnd = Calendar.getInstance(); calendarEnd.setTime(date); calendarEnd.set(Calendar.HOUR_OF_DAY, Integer.valueOf(rule.getString("num21").split(" - ")[1].split(":")[0])); calendarEnd.set(Calendar.MINUTE, Integer.valueOf(rule.getString("num21").split(" - ")[1].split(":")[1])); Calendar s2 = Calendar.getInstance(); s2.setTime(date); s2.set(Calendar.HOUR_OF_DAY, Integer.valueOf(rule.getString("num22").split(" - ")[0].split(":")[0])); s2.set(Calendar.MINUTE, Integer.valueOf(rule.getString("num22").split(" - ")[0].split(":")[1])); Calendar e2 = Calendar.getInstance(); e2.setTime(date); e2.set(Calendar.HOUR_OF_DAY, Integer.valueOf(rule.getString("num22").split(" - ")[1].split(":")[0])); e2.set(Calendar.MINUTE, Integer.valueOf(rule.getString("num22").split(" - ")[1].split(":")[1])); //高峰时段处理逻辑 if ((date.getTime() > calendarStart.getTimeInMillis() && date.getTime() < calendarEnd.getTimeInMillis()) || (date.getTime() > s2.getTimeInMillis() && date.getTime() < e2.getTimeInMillis())) { if (realDistance > rule.getDouble("num6")) { longDistanceOne = rule.getDouble("num26") * (realDistance - rule.getDouble("num6")); } if (realDistance > rule.getDouble("num9")) { longDistanceTwo = rule.getDouble("num27") * (realDistance - rule.getDouble("num9")); } if (realDistance > rule.getDouble("num12")) { longDistanceThree = rule.getDouble("num28") * (realDistance - rule.getDouble("num12")); } amount = rule.getDouble("num23") + (exceedDistance * rule.getDouble("num24")) + (exceedTime * rule.getDouble("num25")) + (wait * waitMoney) + longDistanceOne + longDistanceTwo + longDistanceThree; // 起步价 xpPricingCardVo.setStartPrice(rule.getDouble("num23")); // 时间段 if (date.getTime() > calendarStart.getTimeInMillis() && date.getTime() < calendarEnd.getTimeInMillis()) { xpPricingCardVo.setTime(rule.getString("num21")); } else { xpPricingCardVo.setTime(rule.getString("num22")); } // 里程 xpPricingCardVo.setDistance(realDistance); // 里程费 double distancePrice = (exceedDistance * rule.getDouble("num24")) + longDistanceOne + longDistanceTwo + longDistanceThree; xpPricingCardVo.setDistancePrice(distancePrice); // 时长 xpPricingCardVo.setDuration(realTime); // 时长费 xpPricingCardVo.setDurationPrice((exceedTime * rule.getDouble("num25"))); } else { //其他时间段的计算 if (realDistance > rule.getDouble("num6")) { longDistanceOne = rule.getDouble("num8") * (realDistance - rule.getDouble("num6")); } if (realDistance > rule.getDouble("num9")) { longDistanceTwo = rule.getDouble("num11") * (realDistance - rule.getDouble("num9")); } if (realDistance > rule.getDouble("num12")) { longDistanceThree = rule.getDouble("num13") * (realDistance - rule.getDouble("num12")); } amount = rule.getDouble("num1") + (exceedDistance * rule.getDouble("num4")) + (exceedTime * rule.getDouble("num5")) + (wait * waitMoney) + longDistanceOne + longDistanceTwo + longDistanceThree; // 起步价 xpPricingCardVo.setStartPrice(rule.getDouble("num1")); // 时间段 xpPricingCardVo.setTime(rule.getString("startTime") + " - " + rule.getString("endTime")); xpPricingCardVo.setDistancePriceNormal(rule.getDouble("num2")); xpPricingCardVo.setDurationPriceNormal(rule.getDouble("num3")); // 里程 xpPricingCardVo.setDistance(realDistance); // 里程费 double distancePrice = (exceedDistance * rule.getDouble("num4")) + longDistanceOne + longDistanceTwo + longDistanceThree; xpPricingCardVo.setDistancePrice(distancePrice); // 时长 xpPricingCardVo.setDuration(realTime); // 时长费 xpPricingCardVo.setDurationPrice((exceedTime * rule.getDouble("num5"))); } } } // 订单总额 Double startPrice = xpPricingCardVo.getStartPrice(); // 先减去起步价重新计算 amount = amount - startPrice; // 动态计算费用 startPrice= new BigDecimal(startPrice).multiply(new BigDecimal(priceRate)).setScale(2, RoundingMode.HALF_UP).doubleValue(); amount = amount+startPrice; xpPricingCardVo.setTotalPrice(amount); return xpPricingCardVo; } /** * 行程结束后计算费用 * @param rule 基础规则 * @param distance 里程 * @param duration 时间 * @param wait 时间 * @param waitMoney 等待费 * @param openCityId 开通城市id * @param isDynamicPriceStatus 是否开启动态调价 * @return * @throws Exception */ // public XpPricingCardVo calculationFinalPrice(String uid,JSONObject rule, JSONArray startingFareJson, double distance, long duration, long wait, Double waitMoney,Integer isDynamicPriceStatus,Long openCityId,String dynamicPricingJson) { // // // // 默认费率1 // double priceRate=1; // if(isDynamicPriceStatus==1){ // // 先判断是否已经调价过了 // Object userDynamicPriceObj = redisService.getCacheObject("USER_DYNAMIC_PRICE_" + openCityId + "_" + uid); // if(userDynamicPriceObj!=null){ // // 是否调价过了 // priceRate = Double.parseDouble(userDynamicPriceObj.toString()); // }else { // // 没有调过 // //是否触发动态调价 redis获取 // Object dynamicPriceObj = redisService.getCacheObject("DYNAMIC_PRICE_" + openCityId); // if(dynamicPriceObj!=null){ // JSONObject jsonObject = JSONObject.parseObject(dynamicPricingJson); // // 触发动态调价 // // value=时间 // String timeStr = dynamicPriceObj.toString(); // // 判断当前距离这个时间范围的新增订单数(排除取消) / 上浮数量 算出应该增加的价格率 // R<Integer> r = xpOrderClient.getOrderNum(timeStr); // if(r.getData()==200){ // int num = r.getData(); // // 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_" + openCityId + "_" + uid, priceRate,time.longValue(), TimeUnit.MINUTES); // } // } // // } // } // // } // // // // XpPricingCardVo xpPricingCardVo = new XpPricingCardVo(); // double amount = 0; // //等待费 // Date date = new Date(); // // 计算出是在那个时间段内 // for (Object o : startingFareJson) { // JSONObject startingFare = (JSONObject) o; // Calendar calendarStart = Calendar.getInstance(); // calendarStart.setTime(date); // calendarStart.set(Calendar.HOUR_OF_DAY, Integer.valueOf(startingFare.getString("num1").split(":")[0])); // calendarStart.set(Calendar.MINUTE, Integer.valueOf(startingFare.getString("num1").split(":")[1])); // // Calendar calendarEnd = Calendar.getInstance(); // calendarEnd.setTime(date); // calendarEnd.set(Calendar.HOUR_OF_DAY, Integer.valueOf(startingFare.getString("num2").split(":")[0])); // calendarEnd.set(Calendar.MINUTE, Integer.valueOf(startingFare.getString("num2").split(":")[1])); // // 找到符合条件的直接跳出循环 // if (date.getTime() > calendarStart.getTimeInMillis() && date.getTime() < calendarEnd.getTimeInMillis()) { // rule.put("num1", startingFare.get("num3")); // rule.put("num2", startingFare.get("num4")); // rule.put("num3", startingFare.get("num5")); // rule.put("num4", startingFare.get("num6")); // rule.put("num5", startingFare.get("num7")); // // rule.put("startTime", startingFare.getString("num1")); // rule.put("endTime", startingFare.getString("num2")); // break; // } // } // // // double realDistance = distance / 1000;//实际公里 // double realTime = duration / 60;//实际时间 // double exceedDistance = (realDistance - rule.getDouble("num2")) < 0 ? 0 : realDistance - rule.getDouble("num2");//超出起步里程的公里 // double exceedTime = (realTime - rule.getDouble("num3")) < 0 ? 0 : realTime - rule.getDouble("num3");//超过起步分钟数的时间 // double longDistanceOne = 0;//远途1段 // double longDistanceTwo = 0;//远途2段 // double longDistanceThree = 0;//远途3段 // // // //夜间服务处理逻辑 // Calendar calendarNight = Calendar.getInstance(); // calendarNight.setTime(date); // calendarNight.set(Calendar.HOUR_OF_DAY, Integer.valueOf(rule.getString("num14").split(" - ")[0].split(":")[0])); // calendarNight.set(Calendar.MINUTE, Integer.valueOf(rule.getString("num14").split(" - ")[0].split(":")[1])); // // Calendar calendar = Calendar.getInstance(); // calendar.setTime(date); // calendar.set(Calendar.HOUR_OF_DAY, Integer.valueOf(rule.getString("num14").split(" - ")[1].split(":")[0])); // calendar.set(Calendar.MINUTE, Integer.valueOf(rule.getString("num14").split(" - ")[1].split(":")[1])); // // // 先判断今日是否节假日 // boolean holiday = holidayUtil.isHoliday(); // if (holiday) { // if (realDistance > rule.getDouble("num6")) { // longDistanceOne = rule.getDouble("num32") * (realDistance - rule.getDouble("num6")); // } // if (realDistance > rule.getDouble("num9")) { // longDistanceTwo = rule.getDouble("num33") * (realDistance - rule.getDouble("num9")); // } // if (realDistance > rule.getDouble("num12")) { // longDistanceThree = rule.getDouble("num34") * (realDistance - rule.getDouble("num12")); // } // amount = rule.getDouble("num29") + (exceedDistance * rule.getDouble("num30")) + (exceedTime * rule.getDouble("num31")) + (wait * waitMoney) + longDistanceOne + longDistanceTwo + longDistanceThree; // // // // 起步价 // xpPricingCardVo.setStartPrice(rule.getDouble("num29")); // // 时间段 // xpPricingCardVo.setTime("节假日"); // // // // 里程 // xpPricingCardVo.setDistance(realDistance); // // // 里程费 // double distancePrice = (exceedDistance * rule.getDouble("num30")) + longDistanceOne + longDistanceTwo + longDistanceThree; // xpPricingCardVo.setDistancePrice(distancePrice); // // // 时长 // xpPricingCardVo.setDuration(realTime); // // // 时长费 // xpPricingCardVo.setDurationPrice((exceedTime * rule.getDouble("num31"))); // // // } else { // if (date.getTime() > calendarNight.getTimeInMillis() && date.getTime() < calendar.getTimeInMillis()) { // if (realDistance > rule.getDouble("num6")) { // longDistanceOne = rule.getDouble("num18") * (realDistance - rule.getDouble("num6")); // } // if (realDistance > rule.getDouble("num9")) { // longDistanceTwo = rule.getDouble("num19") * (realDistance - rule.getDouble("num9")); // } // if (realDistance > rule.getDouble("num12")) { // longDistanceThree = rule.getDouble("num20") * (realDistance - rule.getDouble("num12")); // } // amount = rule.getDouble("num15") + (exceedDistance * rule.getDouble("num16")) + (exceedTime * rule.getDouble("num17")) + (wait * waitMoney) + longDistanceOne + longDistanceTwo + longDistanceThree; // // // // 起步价 // xpPricingCardVo.setStartPrice(rule.getDouble("num15")); // // 时间段 // xpPricingCardVo.setTime(rule.getString("num14")); // // // 里程 // xpPricingCardVo.setDistance(realDistance); // // // 里程费 // double distancePrice = (exceedDistance * rule.getDouble("num16")) + longDistanceOne + longDistanceTwo + longDistanceThree; // xpPricingCardVo.setDistancePrice(distancePrice); // // // 时长 // xpPricingCardVo.setDuration(realTime); // // // 时长费 // xpPricingCardVo.setDurationPrice((exceedTime * rule.getDouble("num17"))); // // // } else { // // // 高峰处理 // Calendar calendarStart = Calendar.getInstance(); // calendarStart.setTime(date); // calendarStart.set(Calendar.HOUR_OF_DAY, Integer.valueOf(rule.getString("num21").split(" - ")[0].split(":")[0])); // calendarStart.set(Calendar.MINUTE, Integer.valueOf(rule.getString("num21").split(" - ")[0].split(":")[1])); // // Calendar calendarEnd = Calendar.getInstance(); // calendarEnd.setTime(date); // calendarEnd.set(Calendar.HOUR_OF_DAY, Integer.valueOf(rule.getString("num21").split(" - ")[1].split(":")[0])); // calendarEnd.set(Calendar.MINUTE, Integer.valueOf(rule.getString("num21").split(" - ")[1].split(":")[1])); // // Calendar s2 = Calendar.getInstance(); // s2.setTime(date); // s2.set(Calendar.HOUR_OF_DAY, Integer.valueOf(rule.getString("num22").split(" - ")[0].split(":")[0])); // s2.set(Calendar.MINUTE, Integer.valueOf(rule.getString("num22").split(" - ")[0].split(":")[1])); // // Calendar e2 = Calendar.getInstance(); // e2.setTime(date); // e2.set(Calendar.HOUR_OF_DAY, Integer.valueOf(rule.getString("num22").split(" - ")[1].split(":")[0])); // e2.set(Calendar.MINUTE, Integer.valueOf(rule.getString("num22").split(" - ")[1].split(":")[1])); // //高峰时段处理逻辑 // if ((date.getTime() > calendarStart.getTimeInMillis() && date.getTime() < calendarEnd.getTimeInMillis()) || (date.getTime() > s2.getTimeInMillis() && date.getTime() < e2.getTimeInMillis())) { // if (realDistance > rule.getDouble("num6")) { // longDistanceOne = rule.getDouble("num26") * (realDistance - rule.getDouble("num6")); // } // if (realDistance > rule.getDouble("num9")) { // longDistanceTwo = rule.getDouble("num27") * (realDistance - rule.getDouble("num9")); // } // if (realDistance > rule.getDouble("num12")) { // longDistanceThree = rule.getDouble("num28") * (realDistance - rule.getDouble("num12")); // } // amount = rule.getDouble("num23") + (exceedDistance * rule.getDouble("num24")) + (exceedTime * rule.getDouble("num25")) + (wait * waitMoney) + longDistanceOne + longDistanceTwo + longDistanceThree; // // // // 起步价 // xpPricingCardVo.setStartPrice(rule.getDouble("num23")); // // 时间段 // if (date.getTime() > calendarStart.getTimeInMillis() && date.getTime() < calendarEnd.getTimeInMillis()) { // xpPricingCardVo.setTime(rule.getString("num21")); // } else { // xpPricingCardVo.setTime(rule.getString("num22")); // } // // // // 里程 // xpPricingCardVo.setDistance(realDistance); // // // 里程费 // double distancePrice = (exceedDistance * rule.getDouble("num24")) + longDistanceOne + longDistanceTwo + longDistanceThree; // xpPricingCardVo.setDistancePrice(distancePrice); // // // 时长 // xpPricingCardVo.setDuration(realTime); // // // 时长费 // xpPricingCardVo.setDurationPrice((exceedTime * rule.getDouble("num25"))); // // } else { // //其他时间段的计算 // if (realDistance > rule.getDouble("num6")) { // longDistanceOne = rule.getDouble("num8") * (realDistance - rule.getDouble("num6")); // } // if (realDistance > rule.getDouble("num9")) { // longDistanceTwo = rule.getDouble("num11") * (realDistance - rule.getDouble("num9")); // } // if (realDistance > rule.getDouble("num12")) { // longDistanceThree = rule.getDouble("num13") * (realDistance - rule.getDouble("num12")); // } // amount = rule.getDouble("num1") + (exceedDistance * rule.getDouble("num4")) + (exceedTime * rule.getDouble("num5")) + (wait * waitMoney) + longDistanceOne + longDistanceTwo + longDistanceThree; // // // // 起步价 // xpPricingCardVo.setStartPrice(rule.getDouble("num1")); // // 时间段 // xpPricingCardVo.setTime(rule.getString("startTime") + " - " + rule.getString("endTime")); // // xpPricingCardVo.setDistancePriceNormal(rule.getDouble("num2")); // // xpPricingCardVo.setDurationPriceNormal(rule.getDouble("num3")); // // // // 里程 // xpPricingCardVo.setDistance(realDistance); // // // 里程费 // double distancePrice = (exceedDistance * rule.getDouble("num4")) + longDistanceOne + longDistanceTwo + longDistanceThree; // xpPricingCardVo.setDistancePrice(distancePrice); // // // 时长 // xpPricingCardVo.setDuration(realTime); // // // 时长费 // xpPricingCardVo.setDurationPrice((exceedTime * rule.getDouble("num5"))); // // } // } // } // // // 订单总额 // Double startPrice = xpPricingCardVo.getStartPrice(); // // 先减去起步价重新计算 // amount = amount - startPrice; // // 动态计算费用 // startPrice= new BigDecimal(startPrice).multiply(new BigDecimal(priceRate)).setScale(2, RoundingMode.HALF_UP).doubleValue(); // amount = amount+startPrice; // xpPricingCardVo.setTotalPrice(amount); // // // return xpPricingCardVo; // } /** * 获取配置 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; } } xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/utils/holiday/HolidayUtil.java
New file @@ -0,0 +1,112 @@ package com.xiaopeng.itinerary.utils.holiday; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; import com.xiaopeng.common.core.constant.CacheConstants; import com.xiaopeng.common.core.utils.HttpUtils; import com.xiaopeng.common.redis.service.RedisService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.time.LocalDate; import java.util.concurrent.TimeUnit; @Component public class HolidayUtil { @Autowired private RedisService redisService; public boolean isHoliday() { LocalDate now = LocalDate.now(); // 判断缓存里面是否有节假日算法 if (redisService.getCacheObject(CacheConstants.IS_HOLIDAY+now) != null && ("1").equals(redisService.getCacheObject(CacheConstants.IS_HOLIDAY+now).toString())) { return true; } String result = HttpUtils.sendGet("https://publicapi.xiaoai.me/holiday/day?date=" + now); // String result = HttpUtil.get("https://publicapi.xiaoai.me/holiday/day?date=" +now); JSONObject jsonObject = JSONObject.parseObject(result); Object data = jsonObject.get("data"); JSONArray parse = JSONArray.parse(data.toString()); for (int i = 0; i < parse.size(); i++) { JSONObject jsonObject1 = parse.getJSONObject(i); if (jsonObject1.get("daytype").toString().equals("1")) { redisService.setCacheObject(CacheConstants.IS_HOLIDAY+now, "1", 1l, TimeUnit.DAYS); return true; } } redisService.setCacheObject(CacheConstants.IS_HOLIDAY+now, "0", 1l, TimeUnit.DAYS); return false; } /** * 2非工作日 0工作日 1节假日 3调休日 * @return */ public Integer isHolidayToday() { LocalDate now = LocalDate.now(); Object cacheObject = redisService.getCacheObject(CacheConstants.IS_DAY + now); // 判断缓存里面是否有节假日算法 if (cacheObject!=null) { return Integer.valueOf(cacheObject.toString()); } String result = HttpUtils.sendGet("https://publicapi.xiaoai.me/holiday/day?date=" +now); // String result = HttpUtils.sendGet("https://publicapi.xiaoai.me/holiday/day?date=2026-01-04"); JSONObject jsonObject = JSONObject.parseObject(result); Object data = jsonObject.get("data"); JSONArray parse = JSONArray.parse(data.toString()); for (int i = 0; i < parse.size(); i++) { JSONObject jsonObject1 = parse.getJSONObject(i); String daytype = jsonObject1.get("daytype").toString(); if (daytype.equals("0")) { redisService.setCacheObject(CacheConstants.IS_DAY + now, "0", 1l, TimeUnit.DAYS); return 0; } else if (daytype.equals("1")) { redisService.setCacheObject(CacheConstants.IS_DAY + now, "1", 1l, TimeUnit.DAYS); return 1; } else if (daytype.equals("2")) { redisService.setCacheObject(CacheConstants.IS_DAY + now, "0", 1l, TimeUnit.DAYS); return 0; } } return 0; } /** * 2非工作日 0工作日 1节假日 3调休日 * @return */ public Integer isHolidayToday(LocalDate now) { Object cacheObject = redisService.getCacheObject(CacheConstants.IS_DAY + now); // 判断缓存里面是否有节假日算法 if (cacheObject!=null) { return Integer.valueOf(cacheObject.toString()); } String result = HttpUtils.sendGet("https://publicapi.xiaoai.me/holiday/day?date=" +now); // String result = HttpUtils.sendGet("https://publicapi.xiaoai.me/holiday/day?date=2026-01-04"); JSONObject jsonObject = JSONObject.parseObject(result); Object data = jsonObject.get("data"); JSONArray parse = JSONArray.parse(data.toString()); for (int i = 0; i < parse.size(); i++) { JSONObject jsonObject1 = parse.getJSONObject(i); String daytype = jsonObject1.get("daytype").toString(); if (daytype.equals("0")) { redisService.setCacheObject(CacheConstants.IS_DAY + now, "0", 1l, TimeUnit.DAYS); return 0; } else if (daytype.equals("1")) { redisService.setCacheObject(CacheConstants.IS_DAY + now, "1", 1l, TimeUnit.DAYS); return 1; } else if (daytype.equals("2")) { redisService.setCacheObject(CacheConstants.IS_DAY + now, "0", 1l, TimeUnit.DAYS); return 0; } } return 0; } } xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/utils/mango/VehiclePositionService.java
New file @@ -0,0 +1,92 @@ package com.xiaopeng.itinerary.utils.mango; import com.xiaopeng.api.vo.automaticPilot.VehiclePosition; import org.springframework.data.domain.Sort; import org.springframework.data.geo.GeoResults; import org.springframework.data.geo.Metrics; import org.springframework.data.geo.Point; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.NearQuery; import org.springframework.data.mongodb.core.query.Query; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @Service public class VehiclePositionService { @Resource private MongoTemplate mongoTemplate; /** * 查询附近指定距离内且状态正常的车辆 * @param longitude 经度 * @param latitude 纬度 * @param distanceInMeters 距离(米) * @return 符合条件的车辆列表 */ public List<VehiclePosition> findAvailableVehiclesNearby(double longitude, double latitude, double distanceInMeters,int num) { try { // 1. 创建查询点 Point referencePoint = new Point(longitude, latitude); // 2. 构建 NearQuery - 完全使用 NearQuery 的 API NearQuery nearQuery = NearQuery.near(referencePoint, Metrics.KILOMETERS) .maxDistance(distanceInMeters / 1000) // 转成公里 .spherical(true); // 4. 执行查询 - 明确指定集合名 GeoResults<VehiclePosition> geoResults = mongoTemplate.geoNear( nearQuery, VehiclePosition.class, "vehicle_position" // 明确指定集合名,避免映射错误 ); // 5. 提取结果 return geoResults.getContent().stream() .map(geoResult -> geoResult.getContent()) .collect(Collectors.toList()); } catch (Exception e) { e.printStackTrace(); return new ArrayList<>(); } } /** * 根据车辆ID查询车辆信息 * @param vehicleId 车辆ID * @return 车辆信息,不存在则返回null */ public VehiclePosition findByVehicleId(Long vehicleId) { Query query = new Query(); query.addCriteria(Criteria.where("vehicle_id").is(vehicleId)); return mongoTemplate.findOne(query, VehiclePosition.class); } /** * 所有的车辆 */ public List<VehiclePosition> findAll() { // 构建查询条件 Query query = new Query(); query.addCriteria(Criteria.where("status").is(1)); // 运营中 query.addCriteria(Criteria.where("state").is(1)); // 接单中 // 可以添加排序,按距离排序 query.with(Sort.by(Sort.Direction.ASC, "point")); return mongoTemplate.find(query, VehiclePosition.class); } } xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/utils/map/FastDistanceCalculator.java
New file @@ -0,0 +1,46 @@ package com.xiaopeng.itinerary.utils.map; public class FastDistanceCalculator { private static final double R = 6371.0; // 地球半径(千米) private static final double DEG_TO_RAD = Math.PI / 180.0; /** * 快速近似算法 - 性能极高,误差<0.5% * 适合需要大量计算的场景(如KNN、聚类) * 米 */ public static double distanceFast(double lat1, double lon1, double lat2, double lon2) { double x = (lon2 - lon1) * DEG_TO_RAD * Math.cos((lat1 + lat2) * 0.5 * DEG_TO_RAD); double y = (lat2 - lat1) * DEG_TO_RAD; return R * Math.sqrt(x * x + y * y)*1000; } /** * 快速近似算法 - 误差<0.5% * 适合需要大量计算的场景(如KNN、聚类) * 千米 */ public static double distanceFastKm(double lat1, double lon1, double lat2, double lon2) { double x = (lon2 - lon1) * DEG_TO_RAD * Math.cos((lat1 + lat2) * 0.5 * DEG_TO_RAD); double y = (lat2 - lat1) * DEG_TO_RAD; return R * Math.sqrt(x * x + y * y); } /** * 平面近似 - 极高性能,适用于小范围(城市内) */ public static double distanceFlatApprox(double lat1, double lon1, double lat2, double lon2) { // 每度纬度约111km,每度经度随纬度变化 double kmPerDegreeLat = 111.0; double kmPerDegreeLon = 111.0 * Math.cos((lat1 + lat2) * 0.5 * DEG_TO_RAD); double dLat = lat2 - lat1; double dLon = lon2 - lon1; return Math.sqrt(Math.pow(dLat * kmPerDegreeLat, 2) + Math.pow(dLon * kmPerDegreeLon, 2)); } } xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/utils/map/GeodesyUtil.java
New file @@ -0,0 +1,71 @@ package com.xiaopeng.itinerary.utils.map; import org.gavaghan.geodesy.Ellipsoid; import org.gavaghan.geodesy.GeodeticCalculator; import org.gavaghan.geodesy.GeodeticCurve; import org.gavaghan.geodesy.GlobalCoordinates; import org.springframework.stereotype.Component; import java.util.HashMap; import java.util.Map; /** * 计算两个金纬度坐标之间的直线距离 */ @Component public class GeodesyUtil { /** * 获取直线距离 * @param fromLonLat * @param toLonLat * @return */ public Map<String, Double> getDistance(String fromLonLat, String toLonLat){ String[] from = fromLonLat.split(","); String[] to = toLonLat.split(","); GlobalCoordinates source = new GlobalCoordinates(Double.valueOf(from[1]), Double.valueOf(from[0])); GlobalCoordinates target = new GlobalCoordinates(Double.valueOf(to[1]), Double.valueOf(to[0])); double Sphere = getDistanceMeter(source, target, Ellipsoid.Sphere); double WGS84 = getDistanceMeter(source, target, Ellipsoid.WGS84); double GRS80 = getDistanceMeter(source, target, Ellipsoid.GRS80); double GRS67 = getDistanceMeter(source, target, Ellipsoid.GRS67); double ANS = getDistanceMeter(source, target, Ellipsoid.ANS); double WGS72 = getDistanceMeter(source, target, Ellipsoid.WGS72); double Clarke1858 = getDistanceMeter(source, target, Ellipsoid.Clarke1858); double Clarke1880 = getDistanceMeter(source, target, Ellipsoid.Clarke1880); // System.out.println("Sphere坐标系计算结果:"+Sphere + "米"); // System.out.println("WGS84坐标系计算结果:"+WGS84 + "米"); // System.out.println("GRS80坐标系计算结果:"+GRS80 + "米"); // System.out.println("GRS67坐标系计算结果:"+GRS67 + "米"); // System.out.println("ANS坐标系计算结果:"+ANS + "米"); // System.out.println("WGS72坐标系计算结果:"+WGS72 + "米"); // System.out.println("Clarke1858坐标系计算结果:"+Clarke1858 + "米"); // System.out.println("Clarke1880坐标系计算结果:"+Clarke1880 + "米"); Map<String, Double> map = new HashMap<>(); map.put("Sphere", Sphere); map.put("WGS84", WGS84); map.put("GRS80", GRS80); map.put("GRS67", GRS67); map.put("ANS", ANS); map.put("WGS72", WGS72); map.put("Clarke1858", Clarke1858); map.put("Clarke1880", Clarke1880); return map; } private double getDistanceMeter(GlobalCoordinates gpsFrom, GlobalCoordinates gpsTo, Ellipsoid ellipsoid){ //创建GeodeticCalculator,调用计算方法,传入坐标系、经纬度用于计算距离 GeodeticCurve geoCurve = new GeodeticCalculator().calculateGeodeticCurve(ellipsoid, gpsFrom, gpsTo); return geoCurve.getEllipsoidalDistance(); } public static void main(String[] ages){ GeodesyUtil geodesyUtil = new GeodesyUtil(); geodesyUtil.getDistance("115.481028,39.989643", "114.465302,40.004717"); } } xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/vo/SaveOrderVo.java
New file @@ -0,0 +1,12 @@ package com.xiaopeng.itinerary.vo; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; @Data @ApiModel(value = "保存订单返回") public class SaveOrderVo { @ApiModelProperty(value = "订单id 0静默不推单") private Long orderId; } xp-robotaxi-itinerary-boot/src/main/resources/i18n/messages.properties
@@ -1,2 +1,4 @@ # 中文 path.not.found=????? to.work= ??????????,?????? welcome.message=欢迎来到我们的应用程序! xp-robotaxi-itinerary-boot/src/main/resources/i18n/messages_en_US.properties
@@ -1,2 +1,4 @@ # English (US) path.not.found=Path does not exist to.work=\ The current pick-up and drop-off points are too close to each other. It is recommended to walk there welcome.message=Welcome to our application! xp-robotaxi-itinerary-boot/src/main/resources/i18n/messages_zh_CN.properties
@@ -1,2 +1,4 @@ # 中文 path.not.found=????? to.work=\ ??????????,?????? welcome.message=欢迎来到我们的应用程序! xp-robotaxi-itinerary-boot/src/main/resources/mapper/itinerary/XpElectronicFenceAreaMapper.xml
New file @@ -0,0 +1,7 @@ <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.xiaopeng.itinerary.mapper.XpElectronicFenceAreaMapper"> </mapper> xp-robotaxi-itinerary-boot/src/main/resources/mapper/itinerary/XpElectronicFenceMapper.xml
New file @@ -0,0 +1,10 @@ <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.xiaopeng.itinerary.mapper.XpElectronicFenceMapper"> <!-- 通用查询结果列 --> <sql id="Base_Column_List"> id, electronic_name, electronic_number, status, effect_type, effect_time, failure_type, failure_time, operation_strategy, is_deleted, create_time, update_time, create_by, update_by </sql> </mapper> xp-robotaxi-itinerary-boot/src/main/resources/mapper/itinerary/XpOpenCityMapper.xml
New file @@ -0,0 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.xiaopeng.itinerary.mapper.XpOpenCityMapper"> </mapper> xp-robotaxi-itinerary-boot/src/main/resources/mapper/itinerary/XpPointMapper.xml
New file @@ -0,0 +1,34 @@ <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.xiaopeng.itinerary.mapper.XpPointMapper"> <!-- 通用查询映射结果 --> <resultMap id="BaseResultMap" type="com.xiaopeng.api.model.operation.XpPoint"> <id column="id" property="id" /> <result column="point_name" property="pointName" /> <result column="point_poi" property="pointPoi" /> <result column="point_number" property="pointNumber" /> <result column="operation_status" property="operationStatus" /> <result column="point_type" property="pointType" /> <result column="verification_status" property="verificationStatus" /> <result column="parking_time_limit" property="parkingTimeLimit" /> <result column="operation_type" property="operationType" /> <result column="effective_period" property="effectivePeriod" /> <result column="effective_time_period" property="effectiveTimePeriod" /> <result column="remarks" property="remarks" /> <result column="point_pictures" property="pointPictures" /> <result column="point_lon" property="pointLon" /> <result column="point_lat" property="pointLat" /> <result column="is_deleted" property="deleted" /> <result column="create_time" property="createTime" /> <result column="update_time" property="updateTime" /> <result column="create_by" property="createBy" /> <result column="update_by" property="updateBy" /> </resultMap> <!-- 通用查询结果列 --> <sql id="Base_Column_List"> id,electronic_area_id, point_name, point_poi, point_number, operation_status, point_type, verification_status, parking_time_limit, operation_type, effective_period, effective_time_period, remarks, point_pictures, point_lon, point_lat, is_deleted, create_time, update_time, create_by, update_by </sql> </mapper> xp-robotaxi-itinerary-boot/src/main/resources/mapper/itinerary/XpPricingMapper.xml
New file @@ -0,0 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.xiaopeng.itinerary.mapper.XpPricingMapper"> </mapper> xp-robotaxi-itinerary-boot/src/main/resources/mapper/itinerary/XpRoutePlanMapper.xml
New file @@ -0,0 +1,10 @@ <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.xiaopeng.itinerary.mapper.XpRoutePlanMapper"> <!-- 通用查询结果列 --> <sql id="Base_Column_List"> id, open_city_id, plan_name, province_name, province_code, city_name, city_code, district_name, district_code, remarks, route_plan_rule, audit_status, audit_remarks, audit_user_id, audit_time, is_deleted, create_time, update_time, create_by, update_by </sql> </mapper> xp-robotaxi-itinerary-boot/src/main/resources/mapper/itinerary/XpSystemSettingMapper.xml
New file @@ -0,0 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.xiaopeng.itinerary.mapper.XpSystemSettingMapper"> </mapper> xp-robotaxi-itinerary-boot/src/main/resources/mapper/itinerary/XpTradingStrategyMapper.xml
New file @@ -0,0 +1,7 @@ <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.xiaopeng.itinerary.mapper.XpTradingStrategyMapper"> </mapper> xp-robotaxi-itinerary-boot/src/main/resources/mapper/itinerary/XpWhitelistMapper.xml
New file @@ -0,0 +1,10 @@ <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.xiaopeng.itinerary.mapper.XpWhitelistMapper"> <!-- 通用查询结果列 --> <sql id="Base_Column_List"> id, nick_name, account, status, is_priority_dispatch, whitelist_source, is_deleted, create_time, update_time, create_by, update_by </sql> </mapper> xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/controller/XpVehicleOperationsController.java
@@ -37,9 +37,9 @@ private XpVehicleClient xpVehicleClient; /** * 车辆详情-车辆调度-发车 * 车辆详情-车辆调度-发车 收车 */ @ApiOperation(value = "车辆运维-车辆调度-发车", tags = {"运维APP-车辆管理"}) @ApiOperation(value = "车辆运维-车辆调度-发车/收车", tags = {"运维APP-车辆管理"}) @PostMapping("/api/v1/vehicle/vehicleDispatchDeparture") public R<String> vehicleDispatchDeparture(@RequestBody VehicleDispatchDepartureDTO dto) { // 查询车辆 xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/controller/XpPointManagementController.java
@@ -72,6 +72,16 @@ return xpPointClient.edit(pointAddDto); } /** * 批量删除点位 */ @GetMapping("/deletePoint") @ResponseBody @ApiOperation(value = "删除点位") public R deletePoint(@RequestParam("ids") String ids) { xpPointClient.deletePoint(ids); return R.ok(); } /** * 编辑点位 */ @GetMapping("/queryById") xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/controller/XpPointManagementController.java
@@ -20,6 +20,7 @@ import javax.annotation.Resource; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; @@ -70,6 +71,16 @@ return R.ok(); } /** * 批量删除点位 */ @GetMapping("/deletePoint") @ResponseBody @ApiOperation(value = "删除点位") public R deletePoint(@RequestParam("ids") String ids) { xpPointService.removeBatchByIds(Arrays.stream(ids.split(",")).map(Long::parseLong).collect(Collectors.toList())); return R.ok(); } /** * 编辑点位 */ @PostMapping("/editPoint") xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/controller/XpPricingController.java
@@ -1,10 +1,14 @@ package com.xiaopeng.operation.controller; import com.alibaba.fastjson.JSONObject; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.xiaopeng.api.dto.account.UserResourcesDto; import com.xiaopeng.api.dto.map.RouteAndFareDto; import com.xiaopeng.api.dto.operation.LonLatDto; import com.xiaopeng.api.feignClient.account.UserResourcesClient; import com.xiaopeng.api.model.operation.XpPricing; import com.xiaopeng.api.model.operation.XpWhitelist; import com.xiaopeng.api.vo.operation.RouteAndFareVo; import com.xiaopeng.common.core.domain.R; import com.xiaopeng.common.core.exception.ServiceException; @@ -17,6 +21,7 @@ import com.xiaopeng.operation.dto.XpOrderDto; import com.xiaopeng.operation.query.PricingPageListQuery; import com.xiaopeng.operation.service.IXpGeoQueryEntityService; import com.xiaopeng.operation.service.IXpWhitelistService; import com.xiaopeng.operation.service.XpPricingService; import com.xiaopeng.operation.vo.PricingPageListVO; import com.xiaopeng.operation.vo.SaveOrderVo; @@ -47,11 +52,15 @@ private final XpPricingService xpPricingService; private final IXpGeoQueryEntityService xpGeoQueryEntityService; private final UserResourcesClient userResourcesClient; private final IXpWhitelistService xpWhitelistService; public XpPricingController(XpPricingService xpPricingService, IXpGeoQueryEntityService xpGeoQueryEntityService) { public XpPricingController(XpPricingService xpPricingService, IXpGeoQueryEntityService xpGeoQueryEntityService, UserResourcesClient userResourcesClient, IXpWhitelistService xpWhitelistService) { this.xpPricingService = xpPricingService; this.xpGeoQueryEntityService = xpGeoQueryEntityService; this.userResourcesClient = userResourcesClient; this.xpWhitelistService = xpWhitelistService; } @@ -102,6 +111,31 @@ /** * 获取用户是否在白名单中 */ @PostMapping("/base/v1/mini-program/pricing/isInWhiteList") @ApiOperation(value = "获取用户是否在白名单中", tags = {"小程序-获取用户是否在白名单中"}, response = Boolean.class) @XpClientCheck(checkToken = false, checkOtpToken = true) public R<Boolean> isInWhiteList(HttpServletRequest request) { String uid = request.getHeader("XP-Uid"); UserResourcesDto baseUserByUid = userResourcesClient.getBaseUserByUid(uid); if (baseUserByUid!=null) { try { Object data = baseUserByUid.getData(); String phone = JSONObject.parseObject(data.toString()).getString("mobile"); long count = xpWhitelistService.count(new LambdaQueryWrapper<XpWhitelist>().eq(XpWhitelist::getAccount, phone).eq(XpWhitelist::getStatus, 1)); return R.ok(count>0); }catch (Exception e){ log.error("获取用户信息失败: {}", e.getMessage()); return R.ok(false); } } return R.ok(false); } /** * 保存信息 * @param * @return xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/controller/XpSystemSettingController.java
@@ -32,7 +32,7 @@ @GetMapping("/base/v1/mini-program/support/phone") @ApiOperation(value = "获取客服电话",tags = {"小程序首页"},response = String.class) public AjaxResult<CustomerServicePhoneVo> getBannerList() { public AjaxResult<CustomerServicePhoneVo> getCustomerServicePhone() { Object customerServicePhone = redisService.getCacheObject(CacheConstants.CUSTOMER_SERVICE_PHONE); if(customerServicePhone!=null){ return success(new CustomerServicePhoneVo(customerServicePhone.toString())); xp-robotaxi-operation-boot/src/main/resources/i18n/messages.properties
@@ -18,3 +18,4 @@ pricing.json.invalid=价格配置JSON格式不正确 pricing.rule.empty=价格规则不能为空 no.current.point=????????? path.not.found=????? xp-robotaxi-operation-boot/src/main/resources/i18n/messages_en_US.properties
@@ -18,3 +18,4 @@ pricing.json.invalid=Invalid JSON format for pricing configuration pricing.rule.empty=Price rule cannot be empty no.current.point=The current location is not open for service path.not.found=Path does not exist xp-robotaxi-operation-boot/src/main/resources/i18n/messages_zh_CN.properties
@@ -18,3 +18,4 @@ pricing.json.invalid=价格配置JSON格式不正确 pricing.rule.empty=价格规则不能为空 no.current.point=????????? path.not.found=????? xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/model/vo/XpOrderDetailVO.java
@@ -1,6 +1,8 @@ package com.xiaopeng.order.model.vo; import com.baomidou.mybatisplus.annotation.TableField; import com.fasterxml.jackson.annotation.JsonFormat; import com.xiaopeng.api.dto.operation.LonLatDto; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; @@ -69,10 +71,34 @@ private String endAddress; @ApiModelProperty(value = "出发站点名称") private String startStationName; private String startPointName; @ApiModelProperty(value = "终点站点名称") private String endStationName; private String endPointName; @ApiModelProperty(value = "出发地经度值") private Double startLon; @ApiModelProperty(value = "出发地纬度值") private Double startLat; @ApiModelProperty(value = "目的地经度") private Double endLon; @ApiModelProperty(value = "目的地纬度") private Double endLat; @ApiModelProperty(value = "区域id") private Long xpElectronicFenceAreaId; @ApiModelProperty(value = "下单站点id") @TableField("start_point_id") private Long startPointId; @ApiModelProperty(value = "下单终点站点Id") @TableField("end_point_id") private Long endPointId; @ApiModelProperty(value = "上车点") xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/impl/XpOrderServiceImpl.java
@@ -736,8 +736,9 @@ cancelTaxiPassengerVo.setAppId(""); BaseVehicleUserDto baseVehicleUserDto = userVehicleClient.cancelTaxiPassenger(cancelTaxiPassengerVo); if (baseVehicleUserDto.getCode() != 200) { throw new RuntimeException("取消订单失败"); if (baseVehicleUserDto==null) { // TODO 本地无法调通 // throw new RuntimeException("取消订单失败"); } baseMapper.updateById(order); @@ -1439,6 +1440,11 @@ res.setKilometer(BigDecimal.valueOf(order.getMileageKilometers())); LocalDateTime createTime = order.getCreateTime(); LocalDateTime tripEnd = order.getTripEnd(); if (tripEnd==null){ tripEnd = LocalDateTime.now(); order.setTripEnd(tripEnd); this.updateById(order); } // 计算行程时间 单位分钟 // 计算时间差 Duration durationBetween = Duration.between(createTime, tripEnd); @@ -1639,7 +1645,7 @@ XpOrderDetailVO xpOrderDetailVO = new XpOrderDetailVO(); XpOrder xpOrder = this.baseMapper.selectOne(new LambdaQueryWrapper<XpOrder>().eq(XpOrder::getAppUserId, uid).in(XpOrder::getStatus, Arrays.asList(1,2,3, 4, 5, 6, 7, 8)).orderByDesc(BaseModel::getCreateTime).last("LIMIT 1")); if (xpOrder == null) { return xpOrderDetailVO; return null; } BeanUtils.copyProperties(xpOrder, xpOrderDetailVO); // 算时间