xp-robotaxi-account-boot/pom.xml
@@ -109,17 +109,17 @@ <artifactId>unified-verification</artifactId> <version>1.5.2-v2</version> </dependency> <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId> <version>5.0.0</version> </dependency> <!-- 使用 BASE 事务时,需要引入此模块 --> <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-transaction-base-seata-at</artifactId> <version>5.0.0</version> </dependency> <!-- <dependency>--> <!-- <groupId>org.apache.shardingsphere</groupId>--> <!-- <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>--> <!-- <version>5.0.0</version>--> <!-- </dependency>--> <!-- <!– 使用 BASE 事务时,需要引入此模块 –>--> <!-- <dependency>--> <!-- <groupId>org.apache.shardingsphere</groupId>--> <!-- <artifactId>shardingsphere-transaction-base-seata-at</artifactId>--> <!-- <version>5.0.0</version>--> <!-- </dependency>--> </dependencies> <build> xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/vehicleConvoy/XpFleetClientFallbackFactory.java
@@ -29,6 +29,13 @@ // 降级返回空列表 return R.ok(new ArrayList<>()); } @Override public R<XpFleet> getFleetByOperationsUserId(Long operationsUserId) { log.error("通过运营人员id查询车队信息失败,原因: {}", cause.getMessage()); // 降级返回空列表 return R.fail("通过运营人员id查询车队信息失败,"+cause.getMessage()); } }; } } xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/vehicleConvoy/XpFleetClient.java
@@ -5,8 +5,10 @@ import com.xiaopeng.common.core.domain.R; import com.xiaopeng.common.core.dto.ServiceNameConstantsEnum; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestParam; import java.util.List; @@ -28,4 +30,12 @@ */ @PostMapping("/inner/fleet/listByIds") R<List<XpFleet>> listByIds(@RequestBody List<Long> ids); /** * 通过运营人员id查询车队信息 * * @param operationsUserId 运营人员id * @return 车队信息 */ @GetMapping("/inner/v1/fleet/getFleetByOperationsUserId") R<XpFleet> getFleetByOperationsUserId(@RequestParam Long operationsUserId); } xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/maintenance/XpClockIn.java
@@ -3,9 +3,12 @@ import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import java.time.LocalDate; import java.time.LocalDateTime; import java.io.Serializable; import com.fasterxml.jackson.annotation.JsonFormat; import com.xiaopeng.common.core.web.domain.BaseModel; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; @@ -36,19 +39,33 @@ @ApiModelProperty(value = "用户id") private Long appUserId; @ApiModelProperty(value = "打卡日期") private LocalDateTime clockTime; @ApiModelProperty(value = "上班打卡日期") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8") private LocalDateTime clockInTime; @ApiModelProperty(value = "上班打卡日期") @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss",timezone = "GMT+8") private LocalDateTime clockOutTime; @ApiModelProperty(value = "班次id") private Long shiftId; @ApiModelProperty(value = "打卡结果 1=正常 2=迟到 3=缺卡") private Integer clockResult; @ApiModelProperty(value = "上班打卡结果 1=正常 2=迟到 3=缺卡") private Integer clockInResult; @ApiModelProperty(value = "打卡经度") private Double clockLon; @ApiModelProperty(value = "下班打卡结果 1=正常 2=早退 3=缺卡") private Integer clockOutResult; @ApiModelProperty(value = "打卡纬度") private Double clockLat; @ApiModelProperty(value = "上班打卡经度") private Double clockInLon; @ApiModelProperty(value = "上班打卡纬度") private Double clockInLat; @ApiModelProperty(value = "下班打卡经度") private Double clockOutLon; @ApiModelProperty(value = "下班打卡纬度") private Double clockOutLat; } xp-robotaxi-api/src/main/java/com/xiaopeng/api/model/order/XpOrderTimeoutLog.java
New file @@ -0,0 +1,44 @@ package com.xiaopeng.api.model.order; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.xiaopeng.common.core.web.domain.BaseModel; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import lombok.EqualsAndHashCode; import java.io.Serializable; import java.time.LocalDateTime; /** * <p> * 订单超时表 * </p> * * @author xiaochen * @since 2026-02-14 */ @Data @EqualsAndHashCode(callSuper = false) @TableName("xp_order_timeout_log") @ApiModel(value="XpOrderTimeoutLog对象", description="订单超时表") public class XpOrderTimeoutLog extends BaseModel { private static final long serialVersionUID = 1L; @ApiModelProperty(value = "自增id") @TableId(value = "id", type = IdType.AUTO) private Long id; @ApiModelProperty(value = "订单id") @TableField("order_id") private Long orderId; @ApiModelProperty(value = "小程序用户id") @TableField("user_id") private Long userId; } xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/map/PointByIpVo.java
New file @@ -0,0 +1,15 @@ package com.xiaopeng.api.vo.map; import com.xiaopeng.api.dto.operation.LonLatDto; import io.swagger.annotations.ApiModel; import lombok.Data; @Data @ApiModel("根据ip获取点位") public class PointByIpVo { private LonLatDto point; private String city; private String adcode; } xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/order/XpPointsListVo.java
@@ -28,4 +28,13 @@ @ApiModelProperty("电子围栏区域id") private Long electronicAreaId; private Integer effectivePeriod; private String effectiveTimePeriod; private Integer state=1; } xp-robotaxi-automatic-pilot-boot/pom.xml
@@ -106,17 +106,17 @@ <artifactId>unified-verification</artifactId> <version>1.5.2-v2</version> </dependency> <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId> <version>5.0.0</version> </dependency> <!-- 使用 BASE 事务时,需要引入此模块 --> <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-transaction-base-seata-at</artifactId> <version>5.0.0</version> </dependency> <!-- <dependency>--> <!-- <groupId>org.apache.shardingsphere</groupId>--> <!-- <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>--> <!-- <version>5.0.0</version>--> <!-- </dependency>--> <!-- <!– 使用 BASE 事务时,需要引入此模块 –>--> <!-- <dependency>--> <!-- <groupId>org.apache.shardingsphere</groupId>--> <!-- <artifactId>shardingsphere-transaction-base-seata-at</artifactId>--> <!-- <version>5.0.0</version>--> <!-- </dependency>--> </dependencies> <build> xp-robotaxi-automatic-pilot-boot/src/main/java/com/xiaopeng/automaticPilot/service/KafkaConsumerService.java
@@ -270,6 +270,56 @@ /** * XPU发布系统运行状态---车辆实时位置 * * @param msgContent */ public void systemStatusXpuAndReassign(MsgContent msgContent) { //开始处理消息对应的业务逻辑 SystemRuntimeStatus systemRuntimeStatus = msgContent.getContent_detail().getObject("systemRuntimeStatus", SystemRuntimeStatus.class); AbnormalReason abnormalReason = systemRuntimeStatus.getSystem_status().getAbnormal_reason(); // 判断是否为故障状态 if (abnormalReason == AbnormalReason.FAULT) { log.info("车辆故障,开始处理故障"); // TODO 添加异常任务 // 解绑用户与车辆关系 CancelTaxiPassengerVo cancelTaxiPassengerVo = new CancelTaxiPassengerVo(); String orderId = systemRuntimeStatus.getRunning_order_status_info().getOrder_id(); XpOrder xpOrder = xpOrderClient.queryOrderById(Long.parseLong(orderId)).getData(); XpVehicle vehicle = xpVehicleClient.queryById(xpOrder.getVehicleId()).getData(); if (vehicle != null) { // 修改车辆状态 vehicle.setOperatingStatus(4); xpVehicleClient.updateVehicleById(vehicle); cancelTaxiPassengerVo.setVin(vehicle.getVehicleVin()); cancelTaxiPassengerVo.setUid(xpOrder.getAppUserId().toString()); cancelTaxiPassengerVo.setOrderNo(xpOrder.getOrderNumber()); // TODO // cancelTaxiPassengerVo.setAppId(); 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); } } } /** * XPU向云端请求导航 * * @param msgContent xp-robotaxi-itinerary-boot/pom.xml
@@ -97,17 +97,17 @@ <artifactId>unified-verification</artifactId> <version>1.5.2-v2</version> </dependency> <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId> <version>5.0.0</version> </dependency> <!-- 使用 BASE 事务时,需要引入此模块 --> <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-transaction-base-seata-at</artifactId> <version>5.0.0</version> </dependency> <!-- <dependency>--> <!-- <groupId>org.apache.shardingsphere</groupId>--> <!-- <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>--> <!-- <version>5.0.0</version>--> <!-- </dependency>--> <!-- <!– 使用 BASE 事务时,需要引入此模块 –>--> <!-- <dependency>--> <!-- <groupId>org.apache.shardingsphere</groupId>--> <!-- <artifactId>shardingsphere-transaction-base-seata-at</artifactId>--> <!-- <version>5.0.0</version>--> <!-- </dependency>--> </dependencies> <build> xp-robotaxi-maintenance-boot/pom.xml
@@ -103,17 +103,17 @@ <artifactId>unified-verification</artifactId> <version>1.5.2-v2</version> </dependency> <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId> <version>5.0.0</version> </dependency> <!-- 使用 BASE 事务时,需要引入此模块 --> <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-transaction-base-seata-at</artifactId> <version>5.0.0</version> </dependency> <!-- <dependency>--> <!-- <groupId>org.apache.shardingsphere</groupId>--> <!-- <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>--> <!-- <version>5.0.0</version>--> <!-- </dependency>--> <!-- <!– 使用 BASE 事务时,需要引入此模块 –>--> <!-- <dependency>--> <!-- <groupId>org.apache.shardingsphere</groupId>--> <!-- <artifactId>shardingsphere-transaction-base-seata-at</artifactId>--> <!-- <version>5.0.0</version>--> <!-- </dependency>--> </dependencies> <build> xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/controller/XpCheckLocationController.java
@@ -3,6 +3,7 @@ import com.xiaopeng.api.model.maintenance.XpCheckLocation; import com.xiaopeng.common.core.domain.R; import com.xiaopeng.common.core.enums.DisabledEnum; import com.xiaopeng.common.security.service.TokenService; import com.xiaopeng.maintenance.service.IXpCheckLocationService; import io.swagger.annotations.ApiOperation; import lombok.RequiredArgsConstructor; @@ -24,6 +25,7 @@ @RequiredArgsConstructor(onConstructor_ = @Lazy) public class XpCheckLocationController { private final IXpCheckLocationService xpCheckLocationService; private final TokenService tokenService; @GetMapping("/api/v1/check-location/list") @ApiOperation(value = "获取打卡地址列表", tags = {"管理后台-车队管理"}) @@ -36,4 +38,12 @@ public R<XpCheckLocation> queryById(@RequestParam Long id) { return R.ok(xpCheckLocationService.getById(id)); } @ApiOperation(value = "根据ID查询打卡地址", tags = {"运维APP-排班与考勤"}) @GetMapping("/api/v1/check-location/queryByUserId") public R<XpCheckLocation> queryByUserId() { Long id = tokenService.getLoginOperationUser().getId(); return R.ok(xpCheckLocationService.queryByUserId(id)); } } xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/controller/XpClockInController.java
@@ -1,28 +1,58 @@ package com.xiaopeng.maintenance.controller; import com.xiaopeng.api.model.maintenance.XpErrorType; import com.xiaopeng.common.core.domain.R; import com.xiaopeng.common.security.service.TokenService; import com.xiaopeng.maintenance.dto.ClockDTO; import com.xiaopeng.maintenance.dto.ShiftAttendanceDTO; import com.xiaopeng.maintenance.dto.VerifyRangeDTO; import com.xiaopeng.maintenance.service.IXpClockInService; import com.xiaopeng.maintenance.vo.ShiftAttendanceVO; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.*; /** * <p> * 打卡明细 前端控制器 * </p> * 打卡管理 Controller * * @author xiaochen * @since 2026-02-06 * @author mitao * @date 2026-02-14 */ @Slf4j @RestController @RequestMapping("") @Api(tags = "打卡管理") public class XpClockInController { @Autowired private IXpClockInService xpClockInService; @Autowired private TokenService tokenService; @ApiOperation(value = "校验是否在打卡范围内", tags = "运维APP-排班与考勤") @PostMapping("/api/v1/clock/verifyRange") public R<Boolean> verifyRange(@RequestBody VerifyRangeDTO dto) { return R.ok(xpClockInService.verifyRange(dto)); } private IXpClockInService clockInService; @ApiOperation(value = "上班打卡", tags = "运维APP-排班与考勤") @PostMapping("/api/v1/clock/clockIn") public R<String> clockIn(@RequestBody ClockDTO dto) { Long id = tokenService.getLoginOperationUser().getId(); dto.setOperationsUserId(id); return xpClockInService.clockIn(dto); } /** * 检查用户是否上班中(内部接口) */ @@ -33,4 +63,14 @@ Boolean workFlag = clockInService.checkWorkStatus(appUserId); return R.ok(workFlag); } @ApiOperation(value = "下班打卡", tags = "运维APP-排班与考勤") @PostMapping("/api/v1/clock/clockOut") public R<String> clockOut(@RequestBody ClockDTO dto) { Long id = tokenService.getLoginOperationUser().getId(); dto.setOperationsUserId(id); return xpClockInService.clockOut(dto); } } xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/controller/XpSchedulingDetailController.java
New file @@ -0,0 +1,44 @@ package com.xiaopeng.maintenance.controller; import com.xiaopeng.api.model.maintenance.XpErrorType; import com.xiaopeng.common.core.domain.R; import com.xiaopeng.common.security.service.TokenService; import com.xiaopeng.maintenance.dto.ShiftAttendanceDTO; import com.xiaopeng.maintenance.service.IXpSchedulingDetailService; import com.xiaopeng.maintenance.vo.ShiftAttendanceVO; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; /** * <p> * 排班明细 前端控制器 * </p> * * @author xiaochen * @since 2026-02-06 */ @Api(tags = "排班明细") @RestController @RequestMapping("") public class XpSchedulingDetailController { @Autowired private IXpSchedulingDetailService xpSchedulingDetailService; @Autowired private TokenService tokenService; @ApiOperation(value = "查询个人排班与考勤信息", response = XpErrorType.class, tags = "运维APP-排班与考勤") @PostMapping("/api/v1/scheduling/shiftAttendance") public R<ShiftAttendanceVO> shiftAttendance(@RequestBody ShiftAttendanceDTO dto) { Long id = tokenService.getLoginOperationUser().getId(); dto.setOperationsUserId(id); return R.ok(xpSchedulingDetailService.shiftAttendance(dto)); } } xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/controller/XpShiftController.java
New file @@ -0,0 +1,20 @@ package com.xiaopeng.maintenance.controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * <p> * 班次管理 前端控制器 * </p> * * @author xiaochen * @since 2026-02-06 */ @RestController @RequestMapping("/xp-shift") public class XpShiftController { } xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/dto/ClockDTO.java
New file @@ -0,0 +1,28 @@ package com.xiaopeng.maintenance.dto; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import java.io.Serializable; @Data @ApiModel(value = "运维APP-打卡DTO") public class ClockDTO implements Serializable { @ApiModelProperty(value = "用户经度") private Double userLon; @ApiModelProperty(value = "用户纬度") private Double userLat; @ApiModelProperty(value = "班次id 只传上班打卡的情况") private Long shiftId; @ApiModelProperty(value = "打卡地id") private Long locationId; @ApiModelProperty(value = "运维用户id 前端忽略") private Long operationsUserId; } xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/dto/ShiftAttendanceDTO.java
New file @@ -0,0 +1,21 @@ package com.xiaopeng.maintenance.dto; import com.fasterxml.jackson.annotation.JsonFormat; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import java.io.Serializable; import java.time.LocalDate; @Data @ApiModel(value = "运维APP-查询个人排版与考勤dto") public class ShiftAttendanceDTO implements Serializable { @ApiModelProperty(value = "排班时间") @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") private LocalDate schedulingDate; @ApiModelProperty(value = "运维用户id 前端忽略") private Long operationsUserId; } xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/dto/VerifyRangeDTO.java
New file @@ -0,0 +1,22 @@ package com.xiaopeng.maintenance.dto; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import java.io.Serializable; @Data @ApiModel(value = "运维APP-验证范围DTO") public class VerifyRangeDTO implements Serializable { @ApiModelProperty(value = "打卡地id") private Long locationId; @ApiModelProperty(value = "用户经度") private Double userLon; @ApiModelProperty(value = "用户纬度") private Double userLat; } xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/IXpCheckLocationService.java
@@ -13,4 +13,11 @@ */ public interface IXpCheckLocationService extends IService<XpCheckLocation> { /** * 根据用户id查询打卡地址 * * @param id * @return */ XpCheckLocation queryByUserId(Long id); } xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/IXpClockInService.java
@@ -2,6 +2,9 @@ import com.xiaopeng.api.model.maintenance.XpClockIn; import com.baomidou.mybatisplus.extension.service.IService; import com.xiaopeng.common.core.domain.R; import com.xiaopeng.maintenance.dto.ClockDTO; import com.xiaopeng.maintenance.dto.VerifyRangeDTO; /** * <p> @@ -14,6 +17,29 @@ public interface IXpClockInService extends IService<XpClockIn> { /** * 验证打卡范围 * * @param dto * @return */ Boolean verifyRange(VerifyRangeDTO dto); /** * 打卡 * * @param dto * @return */ R<String> clockIn(ClockDTO dto); /** * 下班打卡 * * @param dto * @return */ R<String> clockOut(ClockDTO dto); /** * 检查用户是否上班中 * @param appUserId 用户ID * @return true=上班中(只打了上班卡), false=未上班或已下班 xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/IXpSchedulingDetailService.java
@@ -2,6 +2,8 @@ import com.xiaopeng.api.model.maintenance.XpSchedulingDetail; import com.baomidou.mybatisplus.extension.service.IService; import com.xiaopeng.maintenance.dto.ShiftAttendanceDTO; import com.xiaopeng.maintenance.vo.ShiftAttendanceVO; /** * <p> @@ -13,4 +15,10 @@ */ public interface IXpSchedulingDetailService extends IService<XpSchedulingDetail> { /** * 查询个人排班与考勤信息 * @param dto * @return */ ShiftAttendanceVO shiftAttendance(ShiftAttendanceDTO dto); } xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/impl/XpCheckLocationServiceImpl.java
@@ -1,10 +1,16 @@ package com.xiaopeng.maintenance.service.impl; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.xiaopeng.api.feignClient.vehicleConvoy.XpFleetClient; import com.xiaopeng.api.model.maintenance.XpCheckLocation; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.xiaopeng.api.model.vehicleConvoy.XpFleet; import com.xiaopeng.maintenance.mapper.XpCheckLocationMapper; import com.xiaopeng.maintenance.service.IXpCheckLocationService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.Objects; /** * <p> @@ -16,5 +22,15 @@ */ @Service public class XpCheckLocationServiceImpl extends ServiceImpl<XpCheckLocationMapper, XpCheckLocation> implements IXpCheckLocationService { @Autowired private XpFleetClient xpFleetClient; @Override public XpCheckLocation queryByUserId(Long id) { // 查询运维人员的车队信息 XpFleet xpFleet = xpFleetClient.getFleetByOperationsUserId(id).getData(); if (Objects.isNull(xpFleet)) { return null; } return this.getById(xpFleet.getLocationId()); } } xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/impl/XpClockInServiceImpl.java
@@ -1,11 +1,30 @@ package com.xiaopeng.maintenance.service.impl; import com.xiaopeng.api.model.maintenance.XpClockIn; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.xiaopeng.api.model.maintenance.XpCheckLocation; import com.xiaopeng.api.model.maintenance.XpClockIn; import com.xiaopeng.api.model.maintenance.XpShift; import com.xiaopeng.common.core.domain.R; import com.xiaopeng.common.core.utils.DateUtils; import com.xiaopeng.common.core.utils.GeodesyUtil; import com.xiaopeng.maintenance.dto.ClockDTO; import com.xiaopeng.maintenance.dto.VerifyRangeDTO; import com.xiaopeng.maintenance.mapper.XpCheckLocationMapper; import com.xiaopeng.maintenance.mapper.XpClockInMapper; import com.xiaopeng.maintenance.mapper.XpShiftMapper; import com.xiaopeng.maintenance.service.IXpClockInService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; import java.util.Map; import lombok.extern.slf4j.Slf4j; import java.util.Objects; import java.time.LocalDate; import java.time.LocalDateTime; @@ -24,6 +43,115 @@ @Service public class XpClockInServiceImpl extends ServiceImpl<XpClockInMapper, XpClockIn> implements IXpClockInService { @Autowired private XpCheckLocationMapper xpCheckLocationMapper; @Autowired private XpShiftMapper xpShiftMapper; @Autowired private XpClockInMapper xpClockInMapper; @Override public Boolean verifyRange(VerifyRangeDTO dto) { XpCheckLocation xpCheckLocation = xpCheckLocationMapper.selectById(dto.getLocationId()); Map<String, Double> distance = GeodesyUtil.getDistance(dto.getUserLon()+","+dto.getUserLat(), xpCheckLocation.getLocationLon()+","+xpCheckLocation.getLocationLat()); if (null != distance) { Double distance1 = distance.get("WGS84"); if (BigDecimal.valueOf(distance1).compareTo(xpCheckLocation.getLocationRange())>0) { return false; }else { return true; } } else { log.error("调用高德计算距离出错"); return false; } } @Override public R<String> clockIn(ClockDTO dto) { XpCheckLocation xpCheckLocation = xpCheckLocationMapper.selectById(dto.getLocationId()); Map<String, Double> distance = GeodesyUtil.getDistance(dto.getUserLon()+","+dto.getUserLat(), xpCheckLocation.getLocationLon()+","+xpCheckLocation.getLocationLat()); if (null != distance) { Double distance1 = distance.get("WGS84"); if (BigDecimal.valueOf(distance1).compareTo(xpCheckLocation.getLocationRange()) > 0) { return R.fail("不在打卡范围内"); } }else { return R.fail("打卡失败"); } // 查询班次 XpShift xpShift = xpShiftMapper.selectById(dto.getShiftId()); if (null == xpShift) { return R.fail("班次不存在"); } // 开始打卡时间 String startTime = xpShift.getStartTime(); LocalDateTime startLocalDateTime = DateUtils.stringToLocalDateTime(LocalDate.now() + " " + startTime + ":00"); XpClockIn xpClockIn = new XpClockIn(); xpClockIn.setAppUserId(dto.getOperationsUserId()); xpClockIn.setClockInTime(LocalDateTime.now()); xpClockIn.setClockInLon(dto.getUserLon()); xpClockIn.setClockInLat(dto.getUserLat()); xpClockIn.setShiftId(dto.getShiftId()); if (xpClockIn.getClockInTime().isAfter(startLocalDateTime)) { xpClockIn.setClockInResult(2); }else { xpClockIn.setClockInResult(1); } this.save(xpClockIn); return R.ok(); } @Override public R<String> clockOut(ClockDTO dto) { XpCheckLocation xpCheckLocation = xpCheckLocationMapper.selectById(dto.getLocationId()); Map<String, Double> distance = GeodesyUtil.getDistance(dto.getUserLon()+","+dto.getUserLat(), xpCheckLocation.getLocationLon()+","+xpCheckLocation.getLocationLat()); if (null != distance) { Double distance1 = distance.get("WGS84"); if (BigDecimal.valueOf(distance1).compareTo(xpCheckLocation.getLocationRange()) > 0) { return R.fail("不在打卡范围内"); } }else { return R.fail("打卡失败"); } // 查询上班打卡信息 XpClockIn xpClockIn = xpClockInMapper.selectOne(Wrappers.lambdaQuery(XpClockIn.class) .eq(XpClockIn::getAppUserId, dto.getOperationsUserId()) .likeRight(XpClockIn::getClockInTime, LocalDate.now()) .last("LIMIT 1")); if(Objects.isNull(xpClockIn)){ return R.fail("请先上班打卡"); } // 查询班次 XpShift xpShift = xpShiftMapper.selectById(xpClockIn.getShiftId()); if (null == xpShift) { return R.fail("班次不存在"); } // 开始打卡时间 String endTime = xpShift.getEndTime(); LocalDateTime endLocalDateTime = DateUtils.stringToLocalDateTime(LocalDate.now() + " " + endTime + ":00"); xpClockIn.setClockOutTime(LocalDateTime.now()); xpClockIn.setClockOutLon(dto.getUserLon()); xpClockIn.setClockOutLat(dto.getUserLat()); if (xpClockIn.getClockOutTime().isBefore(endLocalDateTime)) { xpClockIn.setClockInResult(2); }else { xpClockIn.setClockInResult(1); } this.updateById(xpClockIn); return R.ok(); } @Override public Boolean checkWorkStatus(Long appUserId) { if (appUserId == null) { xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/service/impl/XpSchedulingDetailServiceImpl.java
@@ -1,10 +1,25 @@ package com.xiaopeng.maintenance.service.impl; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.xiaopeng.api.feignClient.maintenance.XpCheckLocationClient; import com.xiaopeng.api.feignClient.vehicleConvoy.XpFleetClient; import com.xiaopeng.api.model.maintenance.XpCheckLocation; import com.xiaopeng.api.model.maintenance.XpClockIn; import com.xiaopeng.api.model.maintenance.XpSchedulingDetail; import com.xiaopeng.api.model.maintenance.XpShift; import com.xiaopeng.api.model.vehicleConvoy.XpFleet; import com.xiaopeng.maintenance.dto.ShiftAttendanceDTO; import com.xiaopeng.maintenance.mapper.XpCheckLocationMapper; import com.xiaopeng.maintenance.mapper.XpClockInMapper; import com.xiaopeng.maintenance.mapper.XpSchedulingDetailMapper; import com.xiaopeng.maintenance.mapper.XpShiftMapper; import com.xiaopeng.maintenance.service.IXpSchedulingDetailService; import com.xiaopeng.maintenance.vo.ShiftAttendanceVO; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.Objects; /** * <p> @@ -17,4 +32,56 @@ @Service public class XpSchedulingDetailServiceImpl extends ServiceImpl<XpSchedulingDetailMapper, XpSchedulingDetail> implements IXpSchedulingDetailService { @Autowired private XpFleetClient xpFleetClient; @Autowired private XpCheckLocationMapper xpCheckLocationMapper; @Autowired private XpShiftMapper xpShiftMapper; @Autowired private XpClockInMapper xpClockInMapper; @Override public ShiftAttendanceVO shiftAttendance(ShiftAttendanceDTO dto) { // 查询用户所属车队 XpFleet xpFleet = xpFleetClient.getFleetByOperationsUserId(dto.getOperationsUserId()).getData(); if (Objects.isNull(xpFleet)) { return null; } // 查询打卡地 XpCheckLocation xpCheckLocation = xpCheckLocationMapper.selectById(xpFleet.getLocationId()); if (Objects.isNull(xpCheckLocation)) { return null; } // 查询排班明细 XpSchedulingDetail schedulingDetail = this.getOne(Wrappers.lambdaQuery(XpSchedulingDetail.class) .likeRight(XpSchedulingDetail::getPlanTime, dto.getSchedulingDate()) .eq(XpSchedulingDetail::getFleetId, xpFleet.getId()) .last("LIMIT 1")); if (Objects.isNull(schedulingDetail)) { return null; } // 查询班次 XpShift xpShift = xpShiftMapper.selectById(schedulingDetail.getShiftId()); if (Objects.isNull(xpShift)) { return null; } ShiftAttendanceVO shiftAttendanceVO = new ShiftAttendanceVO(); shiftAttendanceVO.setXpShift(xpShift); shiftAttendanceVO.setXpCheckLocation(xpCheckLocation); // 查询当前用户打卡信息 XpClockIn xpClockIn = xpClockInMapper.selectOne(Wrappers.lambdaQuery(XpClockIn.class) .eq(XpClockIn::getAppUserId, dto.getOperationsUserId()) .likeRight(XpClockIn::getClockInTime, dto.getSchedulingDate()) .last("LIMIT 1")); if(Objects.isNull(xpClockIn)){ shiftAttendanceVO.setClockIn(false); shiftAttendanceVO.setClockOut(false); }else { shiftAttendanceVO.setClockIn(true); shiftAttendanceVO.setClockOut(xpClockIn.getClockOutTime() != null); } return shiftAttendanceVO; } } xp-robotaxi-maintenance-boot/src/main/java/com/xiaopeng/maintenance/vo/ShiftAttendanceVO.java
New file @@ -0,0 +1,26 @@ package com.xiaopeng.maintenance.vo; import com.xiaopeng.api.model.maintenance.XpCheckLocation; import com.xiaopeng.api.model.maintenance.XpShift; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import java.io.Serializable; @Data @ApiModel(value = "运维APP-排班明细返回") public class ShiftAttendanceVO implements Serializable { @ApiModelProperty(value = "班次信息") private XpShift xpShift; @ApiModelProperty(value = "打卡地信息") private XpCheckLocation xpCheckLocation; @ApiModelProperty(value = "是否上班打卡 true false") private boolean isClockIn; @ApiModelProperty(value = "是否下班打卡 true false") private boolean isClockOut; } xp-robotaxi-map-boot/pom.xml
@@ -101,17 +101,17 @@ <artifactId>unified-verification</artifactId> <version>1.5.2-v2</version> </dependency> <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId> <version>5.0.0</version> </dependency> <!-- 使用 BASE 事务时,需要引入此模块 --> <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-transaction-base-seata-at</artifactId> <version>5.0.0</version> </dependency> <!-- <dependency>--> <!-- <groupId>org.apache.shardingsphere</groupId>--> <!-- <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>--> <!-- <version>5.0.0</version>--> <!-- </dependency>--> <!-- <!– 使用 BASE 事务时,需要引入此模块 –>--> <!-- <dependency>--> <!-- <groupId>org.apache.shardingsphere</groupId>--> <!-- <artifactId>shardingsphere-transaction-base-seata-at</artifactId>--> <!-- <version>5.0.0</version>--> <!-- </dependency>--> </dependencies> <build> xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/controller/IndexController.java
@@ -1,5 +1,6 @@ package com.xiaopeng.map.controller; import com.xiaopeng.api.dto.operation.LonLatDto; import com.xiaopeng.api.dto.operation.PointMapQuery; import com.xiaopeng.api.feignClient.operation.XpPointClient; import com.xiaopeng.api.model.operation.XpPoint; @@ -8,6 +9,7 @@ import com.xiaopeng.common.core.domain.R; import com.xiaopeng.common.redis.service.RedisService; import com.xiaopeng.common.security.service.TokenService; import com.xiaopeng.map.utils.AmapWalkingRouteUtil; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; @@ -15,6 +17,8 @@ import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.util.List; /** @@ -30,6 +34,7 @@ private TokenService tokenService; @Autowired private XpPointClient pointClient; @ApiOperation(value = "地图导航") @PostMapping("/api/v1/map") public R<List<XpPoint>> pointMap(@RequestBody PointMapQuery query) { @@ -51,7 +56,6 @@ return pointClient.queryById(id); } xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/controller/XpMapController.java
@@ -4,6 +4,7 @@ import com.xiaopeng.api.dto.map.OrderRouteAndFareDto; import com.xiaopeng.api.dto.map.RouteAndFareDto; import com.xiaopeng.api.dto.operation.LonLatDto; import com.xiaopeng.api.vo.map.PointByIpVo; import com.xiaopeng.api.vo.map.XpDistanceAndDurationVo; import com.xiaopeng.api.vo.operation.RouteVo; import com.xiaopeng.common.core.constant.CacheConstants; @@ -11,17 +12,18 @@ import com.xiaopeng.common.redis.service.RedisService; import com.xiaopeng.map.mangodb.model.GeoPoint; import com.xiaopeng.map.mangodb.service.XpGeoFencingPolygonService; import com.xiaopeng.map.utils.AmapWalkingRouteUtil; import com.xiaopeng.map.utils.GDMapGeocodingUtil; import com.xiaopeng.map.utils.mangoUtils.MongoGeoFenceUtil; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.bson.Document; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @@ -119,4 +121,16 @@ /** * 根据ip获取点位 */ @ApiOperation(value = "根据ip获取点位", tags = {"小程序-站点导航"}) @GetMapping("/point/getPointByIp") public R<PointByIpVo> getPointByIp(HttpServletRequest request) throws IOException { String ip = request.getRemoteAddr(); PointByIpVo pointByIpVo = AmapWalkingRouteUtil.ipLocation(ip); return R.ok(pointByIpVo); } } xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/service/serviceimpl/XpElectronicFenceAreaServiceImpl.java
@@ -279,9 +279,8 @@ @Override public WalkTimeVo getWalkTime(WalkTimeDto dto) { WalkTimeVo walkTimeVo = new WalkTimeVo(); String apiKey = "66ae577f63898dae3016d599fda57221"; // 创建服务实例 GaodeMapService gaodeService = new GaodeMapService(apiKey); GaodeMapService gaodeService = new GaodeMapService(); AmapWalkingRouteUtil.WalkingRouteResult result = gaodeService.getWalkingRoute(dto.getStartLongitude()+","+dto.getStartLatitude(),dto.getEndLongitude()+","+dto.getEndLatitude()); if (result.isSuccess()) { System.out.println("预计时间: " + result.getFormattedDuration()); xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/utils/AmapWalkingRouteUtil.java
@@ -1,18 +1,21 @@ package com.xiaopeng.map.utils; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONObject; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.xiaopeng.api.dto.operation.LonLatDto; import com.xiaopeng.api.vo.map.PointByIpVo; import com.xiaopeng.common.core.exception.ServiceException; import com.xiaopeng.common.core.utils.HttpUtils; import com.xiaopeng.map.constant.I18nKeyConstant; import lombok.extern.slf4j.Slf4j; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; import org.springframework.stereotype.Component; import org.springframework.beans.factory.annotation.Value; import java.io.IOException; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -26,317 +29,406 @@ // 高德地图Web服务API配置 private static final String AMAP_API_BASE_URL = "https://restapi.amap.com/v3/direction/walking"; private static final String AMAP_GEOCODING_URL = "https://restapi.amap.com/v3/geocode/geo"; // 高德API基础URL private static final String IP_API_URL = "https://restapi.amap.com/v3/ip"; private static final String GEOCODE_API_URL = "https://restapi.amap.com/v3/geocode/geo"; // 你的高德地图API Key(请替换成你自己的) private final String apiKey; // HTTP客户端 private final OkHttpClient httpClient; private final ObjectMapper objectMapper; // @Value("${map.apikey}") private static String apiKey="66ae577f63898dae3016d599fda57221"; /** * 构造函数 * @param apiKey 高德地图API Key * IP定位 */ public static PointByIpVo ipLocation(String ip) throws IOException { PointByIpVo pointByIpVo = new PointByIpVo(); StringBuilder urlBuilder = new StringBuilder(IP_API_URL) .append("?key=").append(apiKey) .append("&output=json"); if (ip != null && !ip.trim().isEmpty()) { urlBuilder.append("&ip=").append(ip); } String response = HttpUtils.sendGet(urlBuilder.toString()); JSONObject jsonObject = JSONObject.parseObject(response); // 城市名称 String city = jsonObject.getString("city"); String province = jsonObject.getString("province"); if(("局域网").equals(province)){ pointByIpVo.setCity("成都市"); LonLatDto lonLatDto = new LonLatDto(104.07, 30.660); pointByIpVo.setPoint(lonLatDto); pointByIpVo.setAdcode("510100"); return pointByIpVo; } String adcode = jsonObject.getString("adcode"); if (city == null || city.trim().isEmpty() || "[]".equals(city)) { // 直辖市:adcode前两位是11,12,31,50 if (adcode != null) { String prefix = adcode.substring(0, 2); if ("11".equals(prefix)) { city = "北京市"; } if ("12".equals(prefix)) { city = "天津市"; } if ("31".equals(prefix)) { city = "上海市"; } if ("50".equals(prefix)) { city = "重庆市"; } // 如果没有city但有province,使用省份 city = province; } } LonLatDto lonLatDto = geocodeCity(city); pointByIpVo.setCity(city); pointByIpVo.setPoint(lonLatDto); pointByIpVo.setAdcode(adcode); return pointByIpVo; } private static LonLatDto geocodeCity(String cityName) throws IOException { String url = GEOCODE_API_URL + "?key=" +apiKey + "&address=" + cityName + "&output=json&city=" + cityName; String response = HttpUtils.sendGet(url); System.out.println(response); JSONObject jsonObject = JSONObject.parseObject(response); // 获取geocodes数组 JSONArray geocodes = jsonObject.getJSONArray("geocodes"); if (geocodes == null || geocodes.isEmpty()) { return null; } // 取第一个geocode JSONObject firstGeocode = geocodes.getJSONObject(0); // 获取location字段 String lonLat = firstGeocode.getString("location"); String[] lonLatArray = lonLat.split(","); double longitude = Double.parseDouble(lonLatArray[0]); double latitude = Double.parseDouble(lonLatArray[1]); return new LonLatDto(longitude, latitude); } public static void main (String[]args) throws IOException { geocodeCity("凉山彝族自治州"); } /** * 构造函数 * @param apiKey 高德地图API Key */ public AmapWalkingRouteUtil(String apiKey) { this.apiKey = apiKey; this.httpClient = new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(10, TimeUnit.SECONDS) .writeTimeout(10, TimeUnit.SECONDS) .build(); this.objectMapper = new ObjectMapper(); } /** * 位置坐标类 */ public static class Location { private final double longitude; // 经度 private final double latitude; // 纬度 public Location(double longitude, double latitude) { this.longitude = longitude; this.latitude = latitude; } public double getLongitude() { return longitude; } public double getLatitude() { return latitude; } @Override public String toString() { return String.format("%.6f,%.6f", longitude, latitude); } } /** * 步行路线结果类 */ public static class WalkingRouteResult { private int duration; // 步行时间(秒) private int distance; // 步行距离(米) private String origin; // 起点地址 private String destination; // 终点地址 private boolean success; // 是否成功 private String message; // 返回信息 // getters and setters public int getDuration() { return duration; } public void setDuration(int duration) { this.duration = duration; } public int getDistance() { return distance; } public void setDistance(int distance) { this.distance = distance; } public String getOrigin() { return origin; } public void setOrigin(String origin) { this.origin = origin; } public String getDestination() { return destination; } public void setDestination(String destination) { this.destination = destination; } public boolean isSuccess() { return success; } public void setSuccess(boolean success) { this.success = success; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; this.apiKey = apiKey; this.httpClient = new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(10, TimeUnit.SECONDS) .writeTimeout(10, TimeUnit.SECONDS) .build(); this.objectMapper = new ObjectMapper(); } /** * 获取格式化的步行时间 * @return 格式化的时间字符串(如:15分钟30秒) * 位置坐标类 */ public String getFormattedDuration() { public static class Location { private final double longitude; // 经度 private final double latitude; // 纬度 int seconds = duration; int minutes = seconds / 60; seconds = seconds % 60; return String.format("%d分%d秒", minutes, seconds); public Location(double longitude, double latitude) { this.longitude = longitude; this.latitude = latitude; } public double getLongitude() { return longitude; } public double getLatitude() { return latitude; } @Override public String toString() { return String.format("%.6f,%.6f", longitude, latitude); } } /** * 获取格式化的步行距离 * @return 格式化的距离字符串(如:1.2公里) * 步行路线结果类 */ public String getFormattedDistance() { if (distance >= 1000) { double km = distance / 1000.0; return String.format("%.1f公里", km); } else { return String.format("%d米", distance); } } } public static class WalkingRouteResult { private int duration; // 步行时间(秒) private int distance; // 步行距离(米) private String origin; // 起点地址 private String destination; // 终点地址 private boolean success; // 是否成功 private String message; // 返回信息 /** * 根据地址获取经纬度坐标 * @param address 地址信息 * @return 经纬度坐标 */ public Location getLocationByAddress(String address) throws IOException { Map<String, String> params = new HashMap<>(); params.put("key", apiKey); params.put("address", address); String url = buildUrl(AMAP_GEOCODING_URL, params); Request request = new Request.Builder().url(url).build(); try (Response response = httpClient.newCall(request).execute()) { if (!response.isSuccessful()) { throw new IOException("请求失败: " + response); // getters and setters public int getDuration() { return duration; } String responseBody = response.body().string(); JsonNode rootNode = objectMapper.readTree(responseBody); public void setDuration(int duration) { this.duration = duration; } if ("1".equals(rootNode.get("status").asText())) { JsonNode geocodes = rootNode.get("geocodes"); if (geocodes != null && geocodes.size() > 0) { String locationStr = geocodes.get(0).get("location").asText(); String[] locationArray = locationStr.split(","); double longitude = Double.parseDouble(locationArray[0]); double latitude = Double.parseDouble(locationArray[1]); return new Location(longitude, latitude); public int getDistance() { return distance; } public void setDistance(int distance) { this.distance = distance; } public String getOrigin() { return origin; } public void setOrigin(String origin) { this.origin = origin; } public String getDestination() { return destination; } public void setDestination(String destination) { this.destination = destination; } public boolean isSuccess() { return success; } public void setSuccess(boolean success) { this.success = success; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } /** * 获取格式化的步行时间 * @return 格式化的时间字符串(如:15分钟30秒) */ public String getFormattedDuration() { int seconds = duration; int minutes = seconds / 60; seconds = seconds % 60; return String.format("%d分%d秒", minutes, seconds); } /** * 获取格式化的步行距离 * @return 格式化的距离字符串(如:1.2公里) */ public String getFormattedDistance() { if (distance >= 1000) { double km = distance / 1000.0; return String.format("%.1f公里", km); } else { return String.format("%d米", distance); } } throw new IOException("地址解析失败: " + rootNode.get("info").asText()); } } /** * 获取步行路线和时间 * @param origin 起点坐标 * @param destination 终点坐标 * @return 步行路线结果 */ public WalkingRouteResult getWalkingRoute(Location origin, Location destination) { WalkingRouteResult result = new WalkingRouteResult(); try { /** * 根据地址获取经纬度坐标 * @param address 地址信息 * @return 经纬度坐标 */ public Location getLocationByAddress (String address) throws IOException { Map<String, String> params = new HashMap<>(); params.put("key", apiKey); params.put("origin", origin.toString()); params.put("destination", destination.toString()); params.put("address", address); String url = buildUrl(AMAP_API_BASE_URL, params); String url = buildUrl(AMAP_GEOCODING_URL, params); Request request = new Request.Builder().url(url).build(); try (Response response = httpClient.newCall(request).execute()) { if (!response.isSuccessful()) { result.setSuccess(false); result.setMessage("HTTP请求失败: " + response.code()); return result; throw new IOException("请求失败: " + response); } String responseBody = response.body().string(); JsonNode rootNode = objectMapper.readTree(responseBody); String status = rootNode.get("status").asText(); String info = rootNode.get("info").asText(); if ("1".equals(status)) { JsonNode route = rootNode.get("route"); JsonNode paths = route.get("paths"); if (paths != null && paths.size() > 0) { JsonNode firstPath = paths.get(0); int duration = firstPath.get("duration").asInt(); int distance = firstPath.get("distance").asInt(); result.setDuration(duration); result.setDistance(distance); result.setSuccess(true); result.setMessage("成功获取步行路线"); // 尝试获取地址信息 result.setOrigin(getAddressByLocation(origin)); result.setDestination(getAddressByLocation(destination)); } else { result.setSuccess(false); result.setMessage("未找到步行路线"); log.info("未找到步行路线"); throw new ServiceException("未找到步行路线", I18nKeyConstant.NO_WALKING_ROUTE_FOUND); if ("1".equals(rootNode.get("status").asText())) { JsonNode geocodes = rootNode.get("geocodes"); if (geocodes != null && geocodes.size() > 0) { String locationStr = geocodes.get(0).get("location").asText(); String[] locationArray = locationStr.split(","); double longitude = Double.parseDouble(locationArray[0]); double latitude = Double.parseDouble(locationArray[1]); return new Location(longitude, latitude); } } else { result.setSuccess(false); result.setMessage("API返回错误: " + info); log.info("API返回错误" + info); throw new ServiceException("未找到步行路线", I18nKeyConstant.NO_WALKING_ROUTE_FOUND); } throw new IOException("地址解析失败: " + rootNode.get("info").asText()); } } catch (Exception e) { result.setSuccess(false); result.setMessage("获取步行路线失败: " + e.getMessage()); log.info("获取步行路线失败" + e.getMessage()); throw new ServiceException("未找到步行路线", I18nKeyConstant.NO_WALKING_ROUTE_FOUND); } return result; } /** * 获取步行路线和时间 * @param origin 起点坐标 * @param destination 终点坐标 * @return 步行路线结果 */ public WalkingRouteResult getWalkingRoute (Location origin, Location destination){ WalkingRouteResult result = new WalkingRouteResult(); /** * 根据经纬度获取地址(逆地理编码) * @param location 经纬度坐标 * @return 地址信息 */ private String getAddressByLocation(Location location) { try { Map<String, String> params = new HashMap<>(); params.put("key", apiKey); params.put("location", location.toString()); try { Map<String, String> params = new HashMap<>(); params.put("key", apiKey); params.put("origin", origin.toString()); params.put("destination", destination.toString()); String url = buildUrl("https://restapi.amap.com/v3/geocode/regeo", params); Request request = new Request.Builder().url(url).build(); String url = buildUrl(AMAP_API_BASE_URL, params); Request request = new Request.Builder().url(url).build(); try (Response response = httpClient.newCall(request).execute()) { if (response.isSuccessful()) { try (Response response = httpClient.newCall(request).execute()) { if (!response.isSuccessful()) { result.setSuccess(false); result.setMessage("HTTP请求失败: " + response.code()); return result; } String responseBody = response.body().string(); JsonNode rootNode = objectMapper.readTree(responseBody); if ("1".equals(rootNode.get("status").asText())) { JsonNode regeocode = rootNode.get("regeocode"); if (regeocode != null) { return regeocode.get("formatted_address").asText(); String status = rootNode.get("status").asText(); String info = rootNode.get("info").asText(); if ("1".equals(status)) { JsonNode route = rootNode.get("route"); JsonNode paths = route.get("paths"); if (paths != null && paths.size() > 0) { JsonNode firstPath = paths.get(0); int duration = firstPath.get("duration").asInt(); int distance = firstPath.get("distance").asInt(); result.setDuration(duration); result.setDistance(distance); result.setSuccess(true); result.setMessage("成功获取步行路线"); // 尝试获取地址信息 result.setOrigin(getAddressByLocation(origin)); result.setDestination(getAddressByLocation(destination)); } else { result.setSuccess(false); result.setMessage("未找到步行路线"); log.info("未找到步行路线"); throw new ServiceException("未找到步行路线", I18nKeyConstant.NO_WALKING_ROUTE_FOUND); } } else { result.setSuccess(false); result.setMessage("API返回错误: " + info); log.info("API返回错误" + info); throw new ServiceException("未找到步行路线", I18nKeyConstant.NO_WALKING_ROUTE_FOUND); } } } catch (Exception e) { result.setSuccess(false); result.setMessage("获取步行路线失败: " + e.getMessage()); log.info("获取步行路线失败" + e.getMessage()); throw new ServiceException("未找到步行路线", I18nKeyConstant.NO_WALKING_ROUTE_FOUND); } return result; } /** * 根据经纬度获取地址(逆地理编码) * @param location 经纬度坐标 * @return 地址信息 */ private String getAddressByLocation (Location location){ try { Map<String, String> params = new HashMap<>(); params.put("key", apiKey); params.put("location", location.toString()); String url = buildUrl("https://restapi.amap.com/v3/geocode/regeo", params); Request request = new Request.Builder().url(url).build(); try (Response response = httpClient.newCall(request).execute()) { if (response.isSuccessful()) { String responseBody = response.body().string(); JsonNode rootNode = objectMapper.readTree(responseBody); if ("1".equals(rootNode.get("status").asText())) { JsonNode regeocode = rootNode.get("regeocode"); if (regeocode != null) { return regeocode.get("formatted_address").asText(); } } } } } catch (Exception e) { // 地址解析失败时返回坐标 } } catch (Exception e) { // 地址解析失败时返回坐标 return location.toString(); } return location.toString(); } /** * 构建带参数的URL * @param baseUrl 基础URL * @param params 参数Map * @return 完整的URL */ private String buildUrl(String baseUrl, Map<String, String> params) { StringBuilder urlBuilder = new StringBuilder(baseUrl).append("?"); params.forEach((key, value) -> { urlBuilder.append(key).append("=").append(value).append("&"); }); // 删除最后一个& urlBuilder.deleteCharAt(urlBuilder.length() - 1); return urlBuilder.toString(); } /** * 构建带参数的URL * @param baseUrl 基础URL * @param params 参数Map * @return 完整的URL */ private String buildUrl (String baseUrl, Map < String, String > params){ StringBuilder urlBuilder = new StringBuilder(baseUrl).append("?"); params.forEach((key, value) -> { urlBuilder.append(key).append("=").append(value).append("&"); }); // 删除最后一个& urlBuilder.deleteCharAt(urlBuilder.length() - 1); return urlBuilder.toString(); } /** * 计算两点间的直线距离(仅供参考,不是实际步行距离) * @param loc1 坐标1 * @param loc2 坐标2 * @return 直线距离(米) */ public static double calculateStraightDistance(Location loc1, Location loc2) { double earthRadius = 6371000; // 地球半径(米) double lat1 = Math.toRadians(loc1.getLatitude()); double lat2 = Math.toRadians(loc2.getLatitude()); double deltaLat = Math.toRadians(loc2.getLatitude() - loc1.getLatitude()); double deltaLon = Math.toRadians(loc2.getLongitude() - loc1.getLongitude()); /** * 计算两点间的直线距离(仅供参考,不是实际步行距离) * @param loc1 坐标1 * @param loc2 坐标2 * @return 直线距离(米) */ public static double calculateStraightDistance (Location loc1, Location loc2){ double earthRadius = 6371000; // 地球半径(米) double lat1 = Math.toRadians(loc1.getLatitude()); double lat2 = Math.toRadians(loc2.getLatitude()); double deltaLat = Math.toRadians(loc2.getLatitude() - loc1.getLatitude()); double deltaLon = Math.toRadians(loc2.getLongitude() - loc1.getLongitude()); double a = Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2) + Math.cos(lat1) * Math.cos(lat2) * Math.sin(deltaLon / 2) * Math.sin(deltaLon / 2); double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); double a = Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2) + Math.cos(lat1) * Math.cos(lat2) * Math.sin(deltaLon / 2) * Math.sin(deltaLon / 2); double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return earthRadius * c; } return earthRadius * c; } } } xp-robotaxi-map-boot/src/main/java/com/xiaopeng/map/utils/GaodeMapService.java
@@ -8,6 +8,7 @@ import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; import org.springframework.beans.factory.annotation.Value; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; @@ -18,12 +19,10 @@ private static final String WALKING_API_URL = "https://restapi.amap.com/v3/direction/walking"; // 你的高德地图API Key private String apiKey; // @Value("${map.apikey}") private String apiKey="66ae577f63898dae3016d599fda57221"; public GaodeMapService(String apiKey) { this.apiKey = apiKey; } /** * 获取步行路径规划 * @param origin 起点坐标,格式:经度,纬度 xp-robotaxi-operation-boot/pom.xml
@@ -165,16 +165,25 @@ <version>4.4.0</version> </dependency> <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId> <version>5.0.0</version> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> </dependency> <!-- 使用 BASE 事务时,需要引入此模块 --> <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-transaction-base-seata-at</artifactId> <version>5.0.0</version> </dependency> <!-- <dependency>--> <!-- <groupId>org.apache.shardingsphere</groupId>--> <!-- <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>--> <!-- <version>5.0.0</version>--> <!-- </dependency>--> <!-- <!– 使用 BASE 事务时,需要引入此模块 –>--> <!-- <dependency>--> <!-- <groupId>org.apache.shardingsphere</groupId>--> <!-- <artifactId>shardingsphere-transaction-base-seata-at</artifactId>--> <!-- <version>5.0.0</version>--> <!-- </dependency>--> <!-- <dependency>--> <!-- <groupId>org.yaml</groupId>--> <!-- <artifactId>snakeyaml</artifactId>--> <!-- <version>1.33</version>--> <!-- </dependency>--> </dependencies> xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/constant/I18nKeyConstant.java
@@ -11,6 +11,9 @@ String NO_CURRENT_REGION = "no.current.region"; String NO_CURRENT_POINT = "no.current.point"; String SYSTEM_EXCEPTION = "system.exception"; String TO_WORK = "to.work"; xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/service/XpPointService.java
@@ -18,6 +18,7 @@ import com.xiaopeng.api.dto.operation.PointMapQuery; import com.xiaopeng.operation.vo.OrderDistanceVo; import java.time.LocalTime; import java.util.List; /** @@ -35,6 +36,14 @@ * 获取附近上车站点 */ NearestVo nearest(@Valid NearestDto dto); /** * 判断点位是否时间段内 * @param now * @return */ boolean getInTime(LocalTime now, Integer effectivePeriod, String effectiveTimePeriod); List<XpPoint> pointMap(PointMapQuery query); List<XpPoint> listAll(PointPageListQuery query); xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/service/impl/XpPointServiceImpl.java
@@ -3,10 +3,12 @@ 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; @@ -17,17 +19,17 @@ import com.xiaopeng.operation.mapper.XpPointMapper; import com.xiaopeng.operation.mapper.XpSystemSettingMapper; import com.xiaopeng.operation.query.PointByNameQuery; import com.xiaopeng.api.dto.operation.PointMapQuery; import com.xiaopeng.api.query.operation.PointPageListQuery; import com.xiaopeng.operation.service.XpPointService; import com.xiaopeng.operation.utils.TimeRangeChecker; import com.xiaopeng.operation.utils.holiday.HolidayUtil; import com.xiaopeng.operation.utils.map.FastDistanceCalculator; import com.xiaopeng.operation.vo.NearestNextVo; import com.xiaopeng.operation.vo.NearestVo; import com.xiaopeng.operation.vo.OrderDistanceVo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.time.LocalTime; import java.util.ArrayList; import java.util.Comparator; import java.util.List; @@ -51,14 +53,17 @@ @Resource private RedisService redisService; @Resource private HolidayUtil holidayUtil; @Override public NearestVo nearest(NearestDto dto) { // 获取所有的站点信息 List<XpPoint> list = list(new LambdaQueryWrapper<XpPoint>().eq(XpPoint::getOperationStatus, 2).isNotNull(XpPoint::getElectronicAreaId)); List<XpPoint> list = list(new LambdaQueryWrapper<XpPoint>().eq(XpPoint::getOperationStatus, 2).eq(XpPoint::getPointType,1).isNotNull(XpPoint::getElectronicAreaId)); NearestVo nearestVo = new NearestVo(); List<NearestNextVo> nearestNextVos = new ArrayList<>(); LocalTime now = LocalTime.now(); // 走缓存 Object orderDistanceObj = redisService.getCacheObject(CacheConstants.NEAR_POINT_DISTANCE); Integer orderDistance; @@ -70,6 +75,11 @@ } for (XpPoint xpPoint : list) { // 判断是否在时间段内 boolean intime = getInTime(now, xpPoint.getEffectivePeriod(), xpPoint.getEffectiveTimePeriod()); if (!intime) { continue; } // 计算距离 小于的站点 double v = FastDistanceCalculator.distanceFast(dto.getLatitude(), dto.getLongitude(), xpPoint.getPointLat(), xpPoint.getPointLon()); if (v < orderDistance) { @@ -82,6 +92,8 @@ nearestNextVo.setAddress(xpPoint.getPointAddress()); nearestNextVo.setTime(xpPoint.getParkingTimeLimit()); nearestNextVo.setElectronicAreaId(xpPoint.getElectronicAreaId()); nearestNextVos.add(nearestNextVo); } } @@ -92,6 +104,44 @@ nearestVo.setTotal(nearestNextVos.size()); return nearestVo; } 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; } @Override @@ -153,9 +203,18 @@ return searchVo; } LocalTime now = LocalTime.now(); for (XpPointsListVo xpPointsListVo : list) { boolean inTime = getInTime(now, xpPointsListVo.getEffectivePeriod(), xpPointsListVo.getEffectiveTimePeriod()); if (!inTime) { // 不在运营段 xpPointsListVo.setState(0); } // 组装数据 并排序 List<XpPointsListVo> resultList = list.stream().peek(vo -> vo.setDistance(FastDistanceCalculator.distanceFast(query.getLat(), query.getLon(), vo.getLatitude(), vo.getLongitude()))).collect(Collectors.toList()); } // 组装数据 并排序 剔除不在运营段的 数据 List<XpPointsListVo> resultList = list.stream().peek(vo -> vo.setDistance(FastDistanceCalculator.distanceFast(query.getLat(), query.getLon(), vo.getLatitude(), vo.getLongitude()))).filter(vo -> vo.getState() == 1).collect(Collectors.toList()); // 可用点位 List<XpPointsListVo> availablePoints = resultList.stream().filter(e -> query.getElectronicAreaId() != null && e.getElectronicAreaId().equals(query.getElectronicAreaId())).sorted(Comparator.comparing(XpPointsListVo::getDistance)).collect(Collectors.toList());// xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/service/impl/XpPricingServiceImpl.java
@@ -36,6 +36,7 @@ import com.xiaopeng.operation.mapper.*; import com.xiaopeng.operation.query.PricingPageListQuery; import com.xiaopeng.operation.service.IXpGeoQueryEntityService; import com.xiaopeng.operation.service.XpPointService; import com.xiaopeng.operation.service.XpPricingService; import com.xiaopeng.operation.utils.XpPricingUtil; import com.xiaopeng.operation.utils.holiday.HolidayUtil; @@ -48,6 +49,7 @@ 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; @@ -97,9 +99,6 @@ @Resource private XpPointMapper xpPointMapper; @Resource private XpWhitelistMapper xpWhitelistMapper; @Resource @@ -113,6 +112,9 @@ @Autowired private XpElectronicFenceMapper xpElectronicFenceMapper; @Autowired private XpPointService xpPointService; // 自定义线程池用于并行执行任务 @@ -202,7 +204,7 @@ // throw new ServiceException("当前区域未开通服务", I18nKeyConstant.NO_CURRENT_REGION); // } // 2. 计算价格(需要在并行任务前完成,因为优惠券查询需要价格) // 2. 计算价格(需要在并行任务前完成,因为优惠券查询需要价格) TODO 判断是动态还是一口价 // XpPricingCardVo xpPricingCardVo = xpPricingUtil.calculationPrice( // JSON.parseObject(xpPricing.getBasicPricingJson()), // JSON.parseArray(xpPricing.getStartingFareJson()), @@ -278,7 +280,33 @@ // 8. 构建响应对象 RouteAndFareVo routeAndFareVo = new RouteAndFareVo(); // routeAndFareVo.setRoute(routeVo); // 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); @@ -370,6 +398,9 @@ @Override public Long saveOrder(XpOrderDto dto, String uid) { // 是否在运营区域时间内 XpElectronicFenceArea xpElectronicFenceArea = xpElectronicFenceAreaMapper.selectById(dto.getXpElectronicFenceAreaId()); XpElectronicFence xpElectronicFence = xpElectronicFenceMapper.selectById(xpElectronicFenceArea.getElectronicId()); @@ -388,10 +419,21 @@ } // 获取起点经纬度 XpPoint xpPoint = xpPointMapper.selectById(dto.getStartPointId()); XpPoint xpPoint = xpPointService.getById(dto.getStartPointId()); // 终点 XpPoint endXpPoint = xpPointMapper.selectById(dto.getStartPointId()); XpPoint endXpPoint = xpPointService.getById(dto.getStartPointId()); // 判断点位是否在运营区域内 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); @@ -575,6 +617,7 @@ } private Boolean isInOperationTime(String operationStrategy) { JSONArray jsonArray = JSONArray.parseArray(operationStrategy); Integer holidayToday = holidayUtil.isHolidayToday(); xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/utils/TimeRangeChecker.java
New file @@ -0,0 +1,84 @@ package com.xiaopeng.operation.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-operation-boot/src/main/java/com/xiaopeng/operation/utils/XpPricingUtil.java
@@ -64,6 +64,86 @@ /** * 计算一口价 */ 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 里程 xp-robotaxi-operation-boot/src/main/resources/bootstrap.yml
@@ -2,7 +2,6 @@ port: 6060 tomcat: basedir: /data/tomcat # Spring spring: application: # 应用名称 @@ -135,6 +134,7 @@ shared-configs: - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension} --- spring: config: activate: @@ -159,4 +159,96 @@ shared-configs: - application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension} --- #--- #spring: # shardingsphere: # props: # sql-show: true # sql-simple: true # datasource: # names: xp0 # xp0: # type: com.alibaba.druid.pool.DruidDataSource # driver-class-name: com.mysql.cj.jdbc.Driver # url: jdbc:mysql://192.168.110.34:3306/xprt_operation?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 # username: root # password: 123456 # rules: # sharding: # tables: # sys_oper_log: # actual-data-nodes: xp0.sys_oper_log # database-strategy: # standard: # sharding-column: userId # sharding-algorithm-name: sys-oper-log-inline # key-generate-strategy: # column: oper_id # key-generator-name: snowflake # xp_agreement_app_user: # actual-data-nodes: xp0.xp_agreement_app_user # database-strategy: # standard: # sharding-column: app_user_id # sharding-algorithm-name: xp-agreement-app-user-inline # key-generate-strategy: # column: id # key-generator-name: snowflake # xp_point: # actual-data-nodes: xp0.xp_point # database-strategy: # standard: # sharding-column: id # sharding-algorithm-name: xp-point-inline # key-generate-strategy: # column: id # key-generator-name: snowflake # sharding-algorithms: # sys-oper-log-inline: # type: INLINE # props: # algorithm-expression: sys_oper_log # xp-agreement-app-user-inline: # type: INLINE # props: # algorithm-expression: xp_agreement_app_user # xp-point-inline: # type: INLINE # props: # algorithm-expression: xp_point # key-generators: # snowflake: # type: SNOWFLAKE # single-table: # tables: # - xp0.sys_config # - xp0.sys_dict_data # - xp0.sys_dict_type # - xp0.sys_logininfor # - xp0.sys_menu # - xp0.xp_acticity # - xp0.xp_acticity_coupon # - xp0.xp_agreement # - xp0.xp_banner # - xp0.xp_coupon # - xp0.xp_electronic_fence # - xp0.xp_electronic_fence_area # - xp0.xp_feedback_type # - xp0.xp_frequently_asked_questions # - xp0.xp_invitation_code # - xp0.xp_notification # - xp0.xp_open_city # - xp0.xp_pricing # - xp0.xp_pricing_item # - xp0.xp_region # - xp0.xp_ride_guide # - xp0.xp_route_plan # - xp0.xp_system_setting # - xp0.xp_trading_strategy # - xp0.xp_trading_strategy_city # - xp0.xp_user_feedback # - xp0.xp_whitelist # - xp0.xp_whitelist_operlog xp-robotaxi-operation-boot/src/main/resources/i18n/messages.properties
@@ -17,3 +17,4 @@ pricing.not.exist=定价不存在 pricing.json.invalid=价格配置JSON格式不正确 pricing.rule.empty=价格规则不能为空 no.current.point=????????? xp-robotaxi-operation-boot/src/main/resources/i18n/messages_en_US.properties
@@ -17,3 +17,4 @@ pricing.not.exist=Pricing does not exist 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 xp-robotaxi-operation-boot/src/main/resources/i18n/messages_zh_CN.properties
@@ -17,3 +17,4 @@ pricing.not.exist=定价不存在 pricing.json.invalid=价格配置JSON格式不正确 pricing.rule.empty=价格规则不能为空 no.current.point=????????? xp-robotaxi-operation-boot/src/main/resources/mapper/operation/XpPointMapper.xml
@@ -91,7 +91,7 @@ <select id="queryPointByName" resultType="com.xiaopeng.api.vo.order.XpPointsListVo"> select p.id,p.electronic_area_id as electronicAreaId,p.point_name as name,p.point_address as address,p.point_lon as longitude,p.point_lat as latitude select p.id,p.electronic_area_id as electronicAreaId,p.point_name as name,p.point_address as address,p.point_lon as longitude,p.point_lat as latitude,p.effective_period,p.effective_time_period from xp_electronic_fence_area xefa left join xp_point p on p.electronic_area_id = xefa.id where p.is_deleted = 0 and p.operation_status =2 and xefa.is_deleted = 0 xp-robotaxi-order-boot/pom.xml
@@ -106,17 +106,17 @@ <artifactId>unified-verification</artifactId> <version>1.5.2-v2</version> </dependency> <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId> <version>5.0.0</version> </dependency> <!-- 使用 BASE 事务时,需要引入此模块 --> <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-transaction-base-seata-at</artifactId> <version>5.0.0</version> </dependency> <!-- <dependency>--> <!-- <groupId>org.apache.shardingsphere</groupId>--> <!-- <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>--> <!-- <version>5.0.0</version>--> <!-- </dependency>--> <!-- <!– 使用 BASE 事务时,需要引入此模块 –>--> <!-- <dependency>--> <!-- <groupId>org.apache.shardingsphere</groupId>--> <!-- <artifactId>shardingsphere-transaction-base-seata-at</artifactId>--> <!-- <version>5.0.0</version>--> <!-- </dependency>--> <!-- <dependency>--> xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/job/base/AbstractJob.java
@@ -3,6 +3,7 @@ import com.xiaopeng.api.feignClient.pushMessage.XpPushMessageClient; import com.xiaopeng.order.service.IXpOrderService; import com.xiaopeng.order.job.utils.SpringContextsUtil; import com.xiaopeng.order.service.XpOrderTimeoutLogService; import org.quartz.Job; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; @@ -17,10 +18,12 @@ protected IXpOrderService xpOrderService; protected XpPushMessageClient xpPushMessageClient; protected XpOrderTimeoutLogService xpOrderTimeoutLogService; public AbstractJob(){ this.xpOrderService = SpringContextsUtil.getBean(IXpOrderService.class); this.xpPushMessageClient = SpringContextsUtil.getBean(XpPushMessageClient.class); this.xpOrderTimeoutLogService = SpringContextsUtil.getBean(XpOrderTimeoutLogService.class); } xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/job/jobs/EndTripJob.java
@@ -1,23 +1,15 @@ package com.xiaopeng.order.job.jobs; import com.alibaba.fastjson2.JSONObject; import com.xiaopeng.api.dto.pushMessage.SendMessageDTO; import com.xiaopeng.api.model.order.XpOrder; import com.xiaopeng.common.core.enums.OrderStatusEnum; import com.xiaopeng.common.core.enums.PushMessageTypeEnum; import com.xiaopeng.api.model.order.XpOrderTimeoutLog; import com.xiaopeng.order.job.base.AbstractJob; import lombok.extern.slf4j.Slf4j; import org.quartz.JobDataMap; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.Objects; /** * 直播推送定时任务 * 推送定时任务 * @author Administrator * */ @@ -33,7 +25,14 @@ Long orderId = maps.getLong("id"); try { log.info("开始执行自动结束行程任务"); // TODO 自动结束行程任务 XpOrder xpOrder = xpOrderService.getById(orderId); xpOrderService.endTrip(xpOrder.getId(), String.valueOf(xpOrder.getAppUserId())); log.info("结束执行自动结束行程任务"); // 添加超时记录 XpOrderTimeoutLog xpOrderTimeoutLog = new XpOrderTimeoutLog(); xpOrderTimeoutLog.setOrderId(xpOrder.getId()); xpOrderTimeoutLog.setUserId(xpOrder.getAppUserId()); xpOrderTimeoutLogService.save(xpOrderTimeoutLog); }catch(Exception e){ e.printStackTrace(); } xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/job/jobs/OrderCancelJob.java
@@ -3,6 +3,7 @@ import com.alibaba.fastjson2.JSONObject; import com.xiaopeng.api.dto.pushMessage.SendMessageDTO; import com.xiaopeng.api.model.order.XpOrder; import com.xiaopeng.common.core.domain.R; import com.xiaopeng.common.core.enums.OrderStatusEnum; import com.xiaopeng.common.core.enums.PushMessageTypeEnum; import com.xiaopeng.order.job.base.AbstractJob; @@ -35,23 +36,9 @@ log.info("开始执行订单取消任务"); XpOrder xpOrder = xpOrderService.getById(orderId); if(Objects.nonNull(xpOrder)){ xpOrder.setStatus(OrderStatusEnum.CANCELED.getCode()); xpOrderService.updateById(xpOrder); log.info("订单取消开始"); xpOrderService.cancelOrder(String.valueOf(xpOrder.getId())); log.info("订单取消状态修改完成"); // 消息推送 Map<String, Object> params = new HashMap<>(); Map<String, Object> content = new HashMap<>(); content.put("orderId",xpOrder.getId()); content.put("status",OrderStatusEnum.CANCELED.getCode()); params.put("type", PushMessageTypeEnum.ORDER_STATUS.getDesc()); params.put("content", JSONObject.toJSONString(content)); params.put("timestamp", new Date().getTime()+""); String message = JSONObject.toJSONString(params); SendMessageDTO sendMessageDTO = new SendMessageDTO(); sendMessageDTO.setMessage(message); sendMessageDTO.setAppUserId(String.valueOf(xpOrder.getAppUserId())); xpPushMessageClient.sendStatus(sendMessageDTO); log.info("订单取消消息推送完毕"); } }catch(Exception e){ e.printStackTrace(); xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/mapper/XpOrderTimeoutLogMapper.java
New file @@ -0,0 +1,16 @@ package com.xiaopeng.order.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.xiaopeng.api.model.order.XpOrderTimeoutLog; /** * <p> * 订单超时表 Mapper 接口 * </p> * * @author xiaochen * @since 2026-02-14 */ public interface XpOrderTimeoutLogMapper extends BaseMapper<XpOrderTimeoutLog> { } xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/XpOrderTimeoutLogService.java
New file @@ -0,0 +1,16 @@ package com.xiaopeng.order.service; import com.baomidou.mybatisplus.extension.service.IService; import com.xiaopeng.api.model.order.XpOrderTimeoutLog; /** * <p> * 订单超时表 服务类 * </p> * * @author xiaochen * @since 2026-02-14 */ public interface XpOrderTimeoutLogService extends IService<XpOrderTimeoutLog> { } xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/impl/XpOrderTimeoutLogServiceImpl.java
New file @@ -0,0 +1,20 @@ package com.xiaopeng.order.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.xiaopeng.api.model.order.XpOrderTimeoutLog; import com.xiaopeng.order.mapper.XpOrderTimeoutLogMapper; import com.xiaopeng.order.service.XpOrderTimeoutLogService; import org.springframework.stereotype.Service; /** * <p> * 订单超时表 服务实现类 * </p> * * @author xiaochen * @since 2026-02-14 */ @Service public class XpOrderTimeoutLogServiceImpl extends ServiceImpl<XpOrderTimeoutLogMapper, XpOrderTimeoutLog> implements XpOrderTimeoutLogService { } xp-robotaxi-push-message-boot/pom.xml
@@ -97,17 +97,17 @@ <artifactId>unified-verification</artifactId> <version>1.5.2-v2</version> </dependency> <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId> <version>5.0.0</version> </dependency> <!-- 使用 BASE 事务时,需要引入此模块 --> <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-transaction-base-seata-at</artifactId> <version>5.0.0</version> </dependency> <!-- <dependency>--> <!-- <groupId>org.apache.shardingsphere</groupId>--> <!-- <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>--> <!-- <version>5.0.0</version>--> <!-- </dependency>--> <!-- <!– 使用 BASE 事务时,需要引入此模块 –>--> <!-- <dependency>--> <!-- <groupId>org.apache.shardingsphere</groupId>--> <!-- <artifactId>shardingsphere-transaction-base-seata-at</artifactId>--> <!-- <version>5.0.0</version>--> <!-- </dependency>--> </dependencies> <build> xp-robotaxi-vehicle-convoy-boot/pom.xml
@@ -97,17 +97,17 @@ <artifactId>unified-verification</artifactId> <version>1.5.2-v2</version> </dependency> <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId> <version>5.0.0</version> </dependency> <!-- 使用 BASE 事务时,需要引入此模块 --> <dependency> <groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-transaction-base-seata-at</artifactId> <version>5.0.0</version> </dependency> <!-- <dependency>--> <!-- <groupId>org.apache.shardingsphere</groupId>--> <!-- <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>--> <!-- <version>5.0.0</version>--> <!-- </dependency>--> <!-- <!– 使用 BASE 事务时,需要引入此模块 –>--> <!-- <dependency>--> <!-- <groupId>org.apache.shardingsphere</groupId>--> <!-- <artifactId>shardingsphere-transaction-base-seata-at</artifactId>--> <!-- <version>5.0.0</version>--> <!-- </dependency>--> <!-- easypoi --> <dependency> <groupId>cn.afterturn</groupId> xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/controller/XpFleetController.java
@@ -1,6 +1,8 @@ package com.xiaopeng.vehicleConvoy.controller; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.xiaopeng.api.model.vehicleConvoy.XpFleet; import com.xiaopeng.api.model.vehicleConvoy.XpFleetOperations; import com.xiaopeng.common.core.domain.R; import com.xiaopeng.common.core.web.page.PageInfo; import com.xiaopeng.vehicleConvoy.model.dto.FleetAddDTO; @@ -9,6 +11,7 @@ import com.xiaopeng.vehicleConvoy.model.query.FleetUserPageQuery; import com.xiaopeng.vehicleConvoy.model.query.FleetVehiclePageQuery; import com.xiaopeng.vehicleConvoy.model.vo.*; import com.xiaopeng.vehicleConvoy.service.IXpFleetOperationsService; import com.xiaopeng.vehicleConvoy.service.IXpFleetService; import io.swagger.annotations.*; import lombok.RequiredArgsConstructor; @@ -19,6 +22,7 @@ import javax.validation.Valid; import java.util.ArrayList; import java.util.List; import java.util.Objects; /** * <p> @@ -34,6 +38,7 @@ public class XpFleetController { private final IXpFleetService xpFleetService; private final IXpFleetOperationsService xpFleetOperationsService; /** * 根据ID列表批量查询车队信息(内部接口) @@ -49,6 +54,28 @@ } return R.ok(xpFleetService.listByIds(ids)); } /** * 通过运营人员id查询车队信息 * * @param operationsUserId 运营人员id * @return 车队信息 */ @ApiOperation(value = "通过运维人员id查询车队信息", tags = {"内部接口"}) @GetMapping("/inner/v1/fleet/getFleetByOperationsUserId") public R<XpFleet> getFleetByOperationsUserId(@RequestParam Long operationsUserId) { if (Objects.isNull(operationsUserId)) { return R.ok(); } // 查询车队人员关联关系 XpFleetOperations fleetOperations = xpFleetOperationsService.getOne(Wrappers.lambdaQuery(XpFleetOperations.class) .eq(XpFleetOperations::getOperationsUserId, operationsUserId) .last("LIMIT 1")); if (Objects.isNull(fleetOperations)) { return R.ok(); } XpFleet xpFleet = xpFleetService.getById(fleetOperations.getFleetId()); return R.ok(xpFleet); } /** * 添加车队