puzhibing
2026-04-21 28ac22a4dd20a5abfa5fae11ce081d8120f1f476
新增加客服系统对接接口
7个文件已添加
18个文件已修改
621 ■■■■■ 已修改文件
xp-robotaxi-api/src/main/java/com/xiaopeng/api/dto/pushMessage/PushOrderRouteDTO.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/vehicleConvoy/XpVehicleClientFallbackFactory.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/vehicleConvoy/XpVehicleClient.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/automaticPilot/AppUserPosition.java 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/operation/RouteVo.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/vehicleConvoy/VehicleDoorStatusVo.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/vehicleConvoy/VehiclesCommandEnum.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-automatic-pilot-boot/src/main/java/com/xiaopeng/automaticPilot/service/KafkaConsumerService.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-automatic-pilot-boot/src/main/java/com/xiaopeng/automaticPilot/service/impl/VehicleRtInfoServiceImpl.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/service/impl/ActiveOrderServiceImpl.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/utils/TaskOrderUtil.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/controller/CustomerServiceController.java 91 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/controller/XpOrderController.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/impl/XpOrderServiceImpl.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/vo/AppUserLocationVo.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/vo/FenceVo.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/vo/VehicleFenceInformationVo.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/vo/VehicleSeatStatusVo.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-push-message-boot/src/main/java/com/xiaopeng/pushMessage/controller/PushOrderRouteController.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-push-message-boot/src/main/java/com/xiaopeng/pushMessage/webSocket/handler/WebSocketTextFrameHandler.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/controller/CarControlCallbackController.java 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/controller/XpVehicleController.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/model/dto/AirConditionerUnlockDTO.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/service/IXpVehicleService.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/service/impl/XpVehicleServiceImpl.java 119 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xp-robotaxi-api/src/main/java/com/xiaopeng/api/dto/pushMessage/PushOrderRouteDTO.java
@@ -39,4 +39,10 @@
    @ApiModelProperty(value = "消息类型")
    private String messageType;
    @ApiModelProperty("连环单上一单的终点经度")
    private Double endLongitude;
    @ApiModelProperty("连环单上一单的终点维度")
    private Double endLatitude;
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/factory/vehicleConvoy/XpVehicleClientFallbackFactory.java
@@ -7,6 +7,7 @@
import com.xiaopeng.api.model.vehicleConvoy.XpVehicle;
import com.xiaopeng.api.vo.automaticPilot.VehiclePosition;
import com.xiaopeng.api.vo.operation.VehiclePositionVo;
import com.xiaopeng.api.vo.vehicleConvoy.VehicleDoorStatusVo;
import com.xiaopeng.common.core.domain.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FallbackFactory;
@@ -114,6 +115,13 @@
                log.error("获取服务车辆列表失败,原因: {}", cause.getMessage());
                return R.fail("获取服务车辆列表失败:" + cause.getMessage());
            }
            @Override
            public R<VehicleDoorStatusVo> getVehicleDoorStatus(Long orderId) {
                log.error("获取车辆四门三盖状态失败,原因: {}", cause.getMessage());
                return R.fail("获取车辆四门三盖状态失败:" + cause.getMessage());
            }
        };
    }
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/feignClient/vehicleConvoy/XpVehicleClient.java
@@ -5,13 +5,11 @@
import com.xiaopeng.api.dto.vehicleConvoy.UpdateVehicleStateDto;
import com.xiaopeng.api.factory.vehicleConvoy.XpVehicleClientFallbackFactory;
import com.xiaopeng.api.model.vehicleConvoy.XpVehicle;
import com.xiaopeng.api.vo.vehicleConvoy.VehicleDoorStatusVo;
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 org.springframework.web.bind.annotation.*;
import java.util.List;
@@ -144,4 +142,13 @@
    @PostMapping("/logan/inner/vehicle/getServiceVehicleList")
    R<List<XpVehicle>> getServiceVehicleList(@RequestBody ServiceVehicleListDto dto);
    /**
     * 根据订单id查询四门三盖状态
     * @param orderId
     * @return
     */
    @GetMapping("/logan/inner/vehicle/getVehicleDoorStatus/{orderId}")
    R<VehicleDoorStatusVo> getVehicleDoorStatus(@PathVariable Long orderId);
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/automaticPilot/AppUserPosition.java
New file
@@ -0,0 +1,38 @@
package com.xiaopeng.api.vo.automaticPilot;
import lombok.Data;
import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
import org.springframework.data.mongodb.core.index.CompoundIndex;
import org.springframework.data.mongodb.core.index.CompoundIndexes;
import org.springframework.data.mongodb.core.index.GeoSpatialIndexType;
import org.springframework.data.mongodb.core.index.GeoSpatialIndexed;
import org.springframework.data.mongodb.core.mapping.Document;
import java.io.Serializable;
/**
 * 车辆实时位置信息
 */
@Data
@Document(collection = "app_user_position")
@CompoundIndexes({
        @CompoundIndex(name = "appUserId", def = "{'appUserId': 1}"),
        @CompoundIndex(name = "location_index", def = "{'point': '2dsphere'}")
}
)
public class AppUserPosition implements Serializable {
    /**
     * 用户id
     */
    private Long appUserId;
    /**
     * 车辆实时位置信息
     */
    @GeoSpatialIndexed(type = GeoSpatialIndexType.GEO_2DSPHERE)
    private GeoJsonPoint point;
    /**
     * 添加时间
     */
    private Long addTime;
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/operation/RouteVo.java
@@ -30,4 +30,10 @@
    @ApiModelProperty("被截取的点位,用于前端使用")
    private List<LonLatDto> served;
    @ApiModelProperty("连环单上一单的终点经度")
    private Double endLongitude;
    @ApiModelProperty("连环单上一单的终点维度")
    private Double endLatitude;
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/vehicleConvoy/VehicleDoorStatusVo.java
New file
@@ -0,0 +1,25 @@
package com.xiaopeng.api.vo.vehicleConvoy;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel
public class VehicleDoorStatusVo {
    @ApiModelProperty("前左门状态(开=1/关=0)")
    private Integer frontLeftDoor;
    @ApiModelProperty("前右门状态(开=1/关=0)")
    private Integer frontRightDoor;
    @ApiModelProperty("后左门状态(开=1/关=0)")
    private Integer rearLeftDoor;
    @ApiModelProperty("后右门状态(开=1/关=0)")
    private Integer rearRightDoor;
    @ApiModelProperty("后备箱盖状态(开=1/关=0)")
    private Integer trunk;
    @ApiModelProperty("前备箱盖状态(开=1/关=0)")
    private Integer frunk;
    @ApiModelProperty("充电口盖状态(开=1/关=0)")
    private Integer chargePort;
}
xp-robotaxi-api/src/main/java/com/xiaopeng/api/vo/vehicleConvoy/VehiclesCommandEnum.java
@@ -11,7 +11,9 @@
    DOOR_TRUNK_CLOSE("DOOR_TRUNK_CLOSE", "后备箱关闭"),
    DOOR_UNLOCK("DOOR_UNLOCK", "车门开锁"),
    HVAC_TEMP_SET_FLOAT("HVAC_TEMP_SET_FLOAT", "设定温度"),
    STATUS_REPORT_NOW("STATUS_REPORT_NOW", "上报实时车况");
    STATUS_REPORT_NOW("STATUS_REPORT_NOW", "上报实时车况"),
    HVAC_ON("HVAC_ON", "空调开启"),
    HVAC_OFF("HVAC_OFF", "空调关闭");
    /**
     * 命令
     */
xp-robotaxi-automatic-pilot-boot/src/main/java/com/xiaopeng/automaticPilot/service/KafkaConsumerService.java
@@ -450,9 +450,12 @@
                    if(!order_id.contains("TEST")){
                        xpPushMessageClient.pushOrderRoute(dto);//存储历史轨迹
                        // 给连环派单的用户推送
                        Long nextOrderId = redisService.getCacheObject(CacheConstants.nextOrder(order_id));
                        if (null != nextOrderId){
                            dto.setOrderId(nextOrderId);
                        String json = redisService.getCacheObject(CacheConstants.nextOrder(order_id));
                        if (StringUtils.isNotEmpty(json)){
                            JSONObject jsonObject = JSON.parseObject(json);
                            dto.setOrderId(jsonObject.getLong("orderId"));
                            dto.setEndLongitude(jsonObject.getDouble("endLongitude"));
                            dto.setEndLatitude(jsonObject.getDouble("endLatitude"));
                            xpPushMessageClient.pushOrderRoute(dto);
                        }
                        xpOrderClient.saveVehicleLocation(orderTrajectory);
@@ -1343,8 +1346,10 @@
                                xpOrderClient.updateOrderStatus(Long.parseLong(orderId), OrderStatusEnum.SEE_OFF_GUESTS.getCode(), true);
                                redisService.setCacheObject("ORDER_STATUS:" + orderId, OrderStatusEnum.SEE_OFF_GUESTS.getCode(), 24L, TimeUnit.HOURS);
                                // 判断当前订单的车辆是否有连环派单,然后判断是都还满足连环派单的条件,不满足则需要重新派单
                                Long nextOrderId = redisService.getCacheObject(CacheConstants.nextOrder(orderId));
                                if(null != nextOrderId){
                                String json = redisService.getCacheObject(CacheConstants.nextOrder(orderId));
                                if (StringUtils.isNotEmpty(json)){
                                    JSONObject jsonObject = JSON.parseObject(json);
                                    Long nextOrderId = jsonObject.getLong("orderId");
                                    XpOrder nextOrder = xpOrderClient.queryOrderById(nextOrderId).getData();
                                    if(null != nextOrder && nextOrder.getStatus() == OrderStatusEnum.PICK_UP.getCode()){
                                        redisService.setCacheObject(CacheConstants.nextOrder(orderId),null);
xp-robotaxi-automatic-pilot-boot/src/main/java/com/xiaopeng/automaticPilot/service/impl/VehicleRtInfoServiceImpl.java
@@ -162,16 +162,18 @@
        if (!SmsType.ACCEPT_RIDES.name().equals(smsType)) {
            return null;
        }
//        XpVehicle vehicle = xpVehicleClient.findByVehicleVin(vin).getData();
//        if (vehicle == null) {
//            return XpCommonResponse.fail(RobotaxiErrorEnum.MOBILE_VIN_MISMATCH);
//        }
//        Integer status = vehicle.getStatus();
//        Integer operatingStatus = vehicle.getOperatingStatus();
//        if (status == null || status != 2 || operatingStatus == null || operatingStatus != 1) {
//            return XpCommonResponse.fail(RobotaxiErrorEnum.VEHICLE_NOT_READY);
//        }
        XpVehicle vehicle = xpVehicleClient.findByVehicleVin(vin).getData();
        if (vehicle == null) {
            return XpCommonResponse.fail(RobotaxiErrorEnum.MOBILE_VIN_MISMATCH);
        }
        Integer status = vehicle.getStatus();
        Integer operatingStatus = vehicle.getOperatingStatus();
        if (smsType.equals(SmsType.ACCEPT_RIDES.name()) && operatingStatus != 1) {
            return XpCommonResponse.fail(RobotaxiErrorEnum.VEHICLE_NOT_READY);
        }
        if (smsType.equals(SmsType.STOP_ACCEPT_RIDES.name()) && operatingStatus != 3) {
            return XpCommonResponse.fail(RobotaxiErrorEnum.VEHICLE_NOT_READY);
        }
        return null;
    }
xp-robotaxi-itinerary-boot/src/main/java/com/xiaopeng/itinerary/service/impl/ActiveOrderServiceImpl.java
@@ -2,6 +2,7 @@
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.xiaopeng.api.dto.automaticPilot.PushOrderDto;
import com.xiaopeng.api.dto.operation.ReassignOrderDTO;
import com.xiaopeng.api.dto.order.OrderChangeDto;
@@ -25,6 +26,7 @@
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.enums.OrderStatusEnum;
import com.xiaopeng.common.core.exception.ServiceException;
import com.xiaopeng.common.core.utils.StringUtils;
import com.xiaopeng.common.redis.service.RedisService;
import com.xiaopeng.itinerary.constant.I18nKeyConstant;
import com.xiaopeng.itinerary.mapper.XpElectronicFenceAreaMapper;
@@ -208,8 +210,10 @@
        // 判断当前订单的车辆是否有连环派单,然后判断是都还满足连环派单的条件,不满足则需要重新派单
        Long nextOrderId = redisService.getCacheObject(CacheConstants.nextOrder(order.getId().toString()));
        if(null != nextOrderId){
        String json = redisService.getCacheObject(CacheConstants.nextOrder(order.getId().toString()));
        if (StringUtils.isNotEmpty(json)){
            JSONObject jsonObject = JSON.parseObject(json);
            Long nextOrderId = jsonObject.getLong("orderId");
            XpOrder nextOrder = xpOrderClient.queryOrderById(nextOrderId).getData();
            if(null != nextOrder && nextOrder.getStatus() == OrderStatusEnum.PICK_UP.getCode()){
                redisService.setCacheObject(CacheConstants.nextOrder(order.getId().toString()),null);
xp-robotaxi-operation-boot/src/main/java/com/xiaopeng/operation/utils/TaskOrderUtil.java
@@ -2,6 +2,7 @@
import cn.hutool.core.bean.BeanUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.xiaopeng.api.dto.automaticPilot.PushOrderDto;
import com.xiaopeng.api.dto.map.RouteAndFareDto;
import com.xiaopeng.api.dto.operation.LonLatDto;
@@ -1160,7 +1161,11 @@
                pushOrderDto.setTaskId(xpOrder.getXpuTaskId());
                // 订单流转
                orderMissionClient.pushOrder(pushOrderDto);
                redisService.setCacheObject(CacheConstants.nextOrder(luckyOder.getId().toString()), xpOrder.getId());
                JSONObject jsonObject = new JSONObject();
                jsonObject.put("orderId", xpOrder.getId());
                jsonObject.put("endLongitude", xpOrder.getEndLat());
                jsonObject.put("endLatitude", xpOrder.getEndLat());
                redisService.setCacheObject(CacheConstants.nextOrder(luckyOder.getId().toString()), jsonObject.toJSONString());
                redisService.setCacheObject(CacheConstants.navigationStart(xpOrder.getAppUserId().toString()), routeToOrigin);
                redisService.removeByPattern(quoteName,xpOrder.getOrderNumber());
                log.info("将订单 {} 从队列 {} 弹出去", orderNo, quoteName);
@@ -1306,7 +1311,11 @@
                    pushOrderDto.setVin(xpVehicle.getVehicleVin());
                    // 订单流转
                    orderMissionClient.pushOrder(pushOrderDto);
                    redisService.setCacheObject(CacheConstants.nextOrder(luckyOder.getId().toString()), data.getId());
                    JSONObject jsonObject = new JSONObject();
                    jsonObject.put("orderId", data.getId());
                    jsonObject.put("endLongitude", data.getEndLat());
                    jsonObject.put("endLatitude", data.getEndLat());
                    redisService.setCacheObject(CacheConstants.nextOrder(luckyOder.getId().toString()), jsonObject.toJSONString());
                    redisService.setCacheObject(CacheConstants.navigationStart(data.getAppUserId().toString()), routeToOrigin);
                    break;
                }
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/controller/CustomerServiceController.java
@@ -9,7 +9,9 @@
import com.xiaopeng.api.feignClient.vehicleConvoy.XpVehicleClient;
import com.xiaopeng.api.model.order.XpOrder;
import com.xiaopeng.api.model.vehicleConvoy.XpVehicle;
import com.xiaopeng.api.vo.automaticPilot.AppUserPosition;
import com.xiaopeng.api.vo.automaticPilot.VehiclePosition;
import com.xiaopeng.api.vo.vehicleConvoy.VehicleDoorStatusVo;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.utils.StringUtils;
import com.xiaopeng.order.dto.UserOrderListDto;
@@ -24,6 +26,7 @@
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@@ -162,6 +165,52 @@
    /**
     * 8. 查询乘客位置
     * @param orderId
     * @return
     */
    @GetMapping("/getAppUserLocation/{orderId}")
    @ApiOperation(value = "查询乘客位置", notes = "客服系统对接")
    @ApiImplicitParam(name = "orderId", value = "订单ID", required = true, dataType = "Long", paramType = "path")
    public R<AppUserLocationVo> getAppUserLocation(@PathVariable Long orderId){
        XpOrder xpOrder = xpOrderService.getById(orderId);
        if(null == xpOrder){
            return R.ok();
        }
        AppUserLocationVo vo = new AppUserLocationVo();
        AppUserPosition userPosition = mongoTemplate.findOne(Query.query(Criteria.where("appUserId").is(xpOrder.getAppUserId())), AppUserPosition.class);
        if(null != userPosition){
            vo.setPassengerLng(userPosition.getPoint().getX());
            vo.setPassengerLat(userPosition.getPoint().getY());
            return R.ok(vo);
        }
        return R.ok();
    }
    /**
     * 9. 根据 订单查询安全带及座椅传感器状态
     * @param orderId
     * @return
     */
    @GetMapping("/getVehicleSeatStatus/{orderId}")
    @ApiOperation(value = "根据 订单查询安全带及座椅传感器状态", notes = "客服系统对接")
    @ApiImplicitParam(name = "orderId", value = "订单ID", required = true, dataType = "Long", paramType = "path")
    public R<VehicleSeatStatusVo> getVehicleSeatStatus(@PathVariable Long orderId){
        XpOrder xpOrder = xpOrderService.getById(orderId);
        if(null == xpOrder){
            return R.ok();
        }
        return R.ok();
    }
    /**
     * 10. 根据 VIN 查询剩余电量及预估续航
     * @param vin
     * @return
@@ -179,4 +228,46 @@
        vo.setEstimatedRange(Double.valueOf(vehiclePositions.getMileageRange()));
        return R.ok(vo);
    }
    /**
     * 11. 根据 订单查询四门三盖状态
     * @param orderId
     * @return
     */
    @GetMapping("/getVehicleDoorStatus/{orderId}")
    @ApiOperation(value = "根据 订单查询四门三盖状态", notes = "客服系统对接")
    @ApiImplicitParam(name = "orderId", value = "订单ID", required = true, dataType = "Long", paramType = "path")
    public R<VehicleDoorStatusVo> getVehicleDoorStatus(@PathVariable Long orderId){
        VehicleDoorStatusVo data = xpVehicleClient.getVehicleDoorStatus(orderId).getData();
        return R.ok(data);
    }
    /**
     * 12. 根据 订单查询车辆位置及电子围栏信息
     * @param orderId
     * @return
     */
    @GetMapping("/getVehicleFenceInformation/{orderId}")
    @ApiOperation(value = "根据 订单查询车辆位置及电子围栏信息", notes = "客服系统对接")
    @ApiImplicitParam(name = "orderId", value = "订单ID", required = true, dataType = "Long", paramType = "path")
    public R<VehicleFenceInformationVo> getVehicleFenceInformation(@PathVariable Long orderId){
        XpOrder xpOrder = xpOrderService.getById(orderId);
        if(null == xpOrder){
            return R.ok();
        }
        XpVehicle xpVehicle = xpVehicleClient.queryById(xpOrder.getVehicleId()).getData();
        VehiclePosition vehiclePositions = mongoTemplate.findOne(Query.query(Criteria.where("vin").is(xpVehicle.getVehicleVin())), VehiclePosition.class);
        if(null == vehiclePositions){
            return R.ok();
        }
        VehicleFenceInformationVo vo = new VehicleFenceInformationVo();
        vo.setVehicleLng(vehiclePositions.getPoint().getX());
        vo.setVehicleLat(vehiclePositions.getPoint().getY());
        vo.setGeofenceBoundary(new ArrayList<>());
        vo.setIsOutOfGeofence(false);
        return R.ok(vo);
    }
}
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/controller/XpOrderController.java
@@ -594,6 +594,14 @@
        XpVehicle xpVehicle = xpVehicleClient.queryById(xpOrder.getVehicleId()).getData();
        VehiclePosition vehiclePosition = mongoTemplate.findOne(new Query(Criteria.where("vin").is(xpVehicle.getVehicleVin())), VehiclePosition.class);
        routeVo.setDirection(null != vehiclePosition ? vehiclePosition.getCourse() : 90);
        Long vehicleId = xpOrder.getVehicleId();
        XpOrder one = xpOrderService.getOne(new QueryWrapper<XpOrder>().eq("vehicle_id", vehicleId).in("status", Arrays.asList(6, 7)).eq("is_deleted", 0));
        if(null != one){
            routeVo.setEndLongitude(one.getEndLon());
            routeVo.setEndLatitude(one.getEndLat());
        }
        return R.ok(routeVo);
    }
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/service/impl/XpOrderServiceImpl.java
@@ -2033,8 +2033,10 @@
                }
                //判断是否有连环单
                Long id = redisService.getCacheObject(CacheConstants.nextOrder(xpOrder.getId().toString()));
                if(id != null){
                String json = redisService.getCacheObject(CacheConstants.nextOrder(xpOrder.getId().toString()));
                if (StringUtils.isNotEmpty(json)){
                    JSONObject jsonObject = JSON.parseObject(json);
                    Long id = jsonObject.getLong("orderId");
                    QuartzManager.addJob(
                            PushChainReactionOrderJob.class,
                            (PushChainReactionOrderJob.name + id).toUpperCase(),
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/vo/AppUserLocationVo.java
New file
@@ -0,0 +1,15 @@
package com.xiaopeng.order.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel
public class AppUserLocationVo {
    @ApiModelProperty("经度")
    private Double passengerLng;
    @ApiModelProperty("纬度")
    private Double passengerLat;
}
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/vo/FenceVo.java
New file
@@ -0,0 +1,14 @@
package com.xiaopeng.order.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel
public class FenceVo {
    @ApiModelProperty("经度")
    private Double lng;
    @ApiModelProperty("纬度")
    private Double lat;
}
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/vo/VehicleFenceInformationVo.java
New file
@@ -0,0 +1,21 @@
package com.xiaopeng.order.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@Data
@ApiModel
public class VehicleFenceInformationVo {
    @ApiModelProperty("车辆经度")
    private Double vehicleLng;
    @ApiModelProperty("车辆纬度")
    private Double vehicleLat;
    @ApiModelProperty("电子围栏经纬度数组")
    private List<FenceVo> geofenceBoundary;
    @ApiModelProperty("是否超越电子围栏边界")
    private Boolean isOutOfGeofence;
}
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/vo/VehicleSeatStatusVo.java
New file
@@ -0,0 +1,19 @@
package com.xiaopeng.order.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel
public class VehicleSeatStatusVo {
    @ApiModelProperty("主驾")
    private String frontLeft;
    @ApiModelProperty("副驾")
    private String frontRight;
    @ApiModelProperty("左后")
    private String rearLeft;
    @ApiModelProperty("右后")
    private String rearRight;
}
xp-robotaxi-push-message-boot/src/main/java/com/xiaopeng/pushMessage/controller/PushOrderRouteController.java
@@ -123,6 +123,8 @@
                content.put("routeType",routeType);
                content.put("direction",dto.getDirection());
                content.put("route",JSONObject.toJSONString(route));
                content.put("endLongitude", dto.getEndLongitude());
                content.put("endLatitude", dto.getEndLatitude());
                // 封装轨迹信息
                Map<String, Object> params = new HashMap<>();
xp-robotaxi-push-message-boot/src/main/java/com/xiaopeng/pushMessage/webSocket/handler/WebSocketTextFrameHandler.java
@@ -2,6 +2,8 @@
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.xiaopeng.api.vo.automaticPilot.AppUserPosition;
import com.xiaopeng.common.redis.service.RedisService;
import com.xiaopeng.pushMessage.webSocket.service.WebSocketUserConnectionManager;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
@@ -9,9 +11,15 @@
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.geo.GeoJsonPoint;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
@@ -33,6 +41,9 @@
    @Autowired
    private WebSocketAuthHandler authHandler;
    @Resource
    private MongoTemplate mongoTemplate;
    Map<String, BusinessMsgHandler> businessMsgHandlers;
@@ -160,8 +171,7 @@
                log.info("收到用户 {} 的实时位置消息: {}", appUserId, messageObj.toJSONString());
                Double latitude = messageObj.getDouble("latitude");
                Double longitude = messageObj.getDouble("longitude");
                String timestamp = messageObj.getString("timestamp");
                saveAppUserLocation(Long.parseLong(appUserId), latitude, longitude);
            }
            // 回复消息
@@ -213,4 +223,21 @@
        log.error("WebSocket连接异常: {}", cause.getMessage(), cause);
        ctx.channel().close();
    }
    private void saveAppUserLocation(Long appUserId, Double latitude, Double longitude) {
        AppUserPosition userPosition = mongoTemplate.findOne(Query.query(Criteria.where("appUserId").is(appUserId)), AppUserPosition.class);
        GeoJsonPoint point = new GeoJsonPoint(longitude, latitude);
        if(null != userPosition){
            mongoTemplate.updateFirst(Query.query(Criteria.where("appUserId").is(appUserId)),
                    Update.update("point", point).set("addTime", System.currentTimeMillis()), AppUserPosition.class);
        }else{
            userPosition = new AppUserPosition();
            userPosition.setAppUserId(appUserId);
            userPosition.setPoint(point);
            userPosition.setAddTime(System.currentTimeMillis());
            mongoTemplate.save(userPosition);
        }
    }
}
xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/controller/CarControlCallbackController.java
@@ -6,6 +6,7 @@
import com.xiaopeng.api.vo.vehicleConvoy.VehicleStateVo;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.exception.ServiceException;
import com.xiaopeng.common.redis.service.RedisService;
import com.xiaopeng.vehicleConvoy.model.dto.CarControlCallbackDTO;
import com.xiaopeng.vehicleConvoy.service.AsyncVehicleStateService;
import lombok.extern.slf4j.Slf4j;
@@ -17,6 +18,7 @@
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
 * 控车回调接口
@@ -28,6 +30,7 @@
    @Resource
    private AsyncVehicleStateService asyncVehicleStateService;
    private RedisService redisService;
    /**
     * 车控指令执行的回调接口
@@ -51,6 +54,8 @@
                    // 通知异步服务处理回调
                    asyncVehicleStateService.handleStateCallback(request.getMsgRef(), stateData);
                }
                // 尝试解析车辆空调数据
                parseAirConditioningSwitchFromCallback(request);
            }
        } catch (Exception e) {
            log.error("处理车辆状态回调异常: msgId={}", request.getMsgRef(), e);
@@ -94,6 +99,8 @@
            // 解析door信息
            Map<String, Object> door = (Map<String, Object>) instant.get("door");
            if (door != null) {
                //存入缓存用于后续使用
                redisService.setCacheObject("DOOR:" + callback.getMsgId(), JSON.toJSONString(door), 30L, TimeUnit.SECONDS);
                // 获取trunk_ajar_st_new值
                Object trunkAjarStNewObj = door.get("trunk_ajar_st_new");
                if (trunkAjarStNewObj != null) {
@@ -115,4 +122,52 @@
            return null;
        }
    }
    /**
     * 从回调消息中解析车辆空调状态数据
     */
    private VehicleTemperatureDto parseAirConditioningSwitchFromCallback(CarControlCallbackDTO callback) {
        try {
            if (callback.getMsgContent() == null) {
                return null;
            }
            log.debug("解析空调状态数据: msgId={}, content={}", callback.getMsgRef(), JSON.toJSONString(callback.getMsgContent()));
            // 解析msgContent为Map结构
            Map<String, Object> msgContent = (Map<String, Object>) callback.getMsgContent();
            // 检查返回码
            Integer code = (Integer) msgContent.get("code");
            if (code == null || code != 200) {
                log.warn("空调状态回调返回错误码: msgId={}, code={}", callback.getMsgRef(), code);
                return null;
            }
            // 获取instant数据
            Map<String, Object> instant = (Map<String, Object>) msgContent.get("instant");
            if (instant == null) {
                log.warn("回调数据中缺少instant字段: msgId={}", callback.getMsgRef());
                return null;
            }
            // 创建VehicleTemperatureDto对象
            VehicleTemperatureDto stateData = new VehicleTemperatureDto();
            Map<String, VehicleStateVo> stateMap = new HashMap<>();
            // 解析hvac信息
            Map<String, Object> hvac = (Map<String, Object>) instant.get("hvac");
            if (hvac != null) {
                log.info("解析到空调: msgId={}, hvac_on={}", callback.getMsgRef(), JSON.toJSONString(hvac));
                //存入缓存用于后续使用
                redisService.setCacheObject("HVAC:" + callback.getMsgId(), JSON.toJSONString(hvac), 30L, TimeUnit.SECONDS);
            }
            stateData.setStateMap(stateMap);
            return stateData;
        } catch (Exception e) {
            log.warn("解析空调状态数据失败: msgId={}", callback.getMsgRef(), e);
            return null;
        }
    }
}
xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/controller/XpVehicleController.java
@@ -25,6 +25,7 @@
import com.xiaopeng.api.model.vehicleConvoy.XpVehicle;
import com.xiaopeng.api.model.vehicleConvoy.XpVehicleAnnualRecord;
import com.xiaopeng.api.vo.automaticPilot.VehiclePosition;
import com.xiaopeng.api.vo.vehicleConvoy.VehicleDoorStatusVo;
import com.xiaopeng.api.vo.vehicleConvoy.VehicleStateVo;
import com.xiaopeng.api.vo.vehicleConvoy.VehiclesCommandCallbackVo;
import com.xiaopeng.common.core.domain.R;
@@ -1121,4 +1122,38 @@
        XpOperationsUser xpOperationsUser = tokenService.getLoginOperationUser().getXpOperationsUser();
        return R.ok(xpVehicleService.unlockDoor(dto, xpOperationsUser));
    }
    @ApiOperation(value = "通过订单id查询当前车辆空调状态", tags = {"小程序-业务流程"})
    @GetMapping("/api/v1/mini-program/order/queryVehicleAirConditionerSwitch")
    @XpClientCheck(checkToken = false, checkOtpToken = true)
    public R<Boolean> queryVehicleAirConditionerSwitch(@RequestParam Long orderId, HttpServletRequest request) {
        String uid = request.getHeader("XP-Uid");
        Boolean hasKey = xpVehicleService.queryVehicleAirConditionerSwitch(orderId, uid);
        return R.ok(hasKey);
    }
    /**
     * 业务流程-车门解锁/后备箱解锁
     */
    @ApiOperation(value = "打开/关闭车内空调", tags = {"小程序-业务流程"})
    @PostMapping("/api/v1/mini-program/order/airConditionerUnlock")
    @XpClientCheck(checkToken = false, checkOtpToken = true)
    public R<Integer> airConditionerUnlock(@RequestBody AirConditionerUnlockDTO dto, HttpServletRequest request) {
        String uid = request.getHeader("XP-Uid");
        return R.ok(xpVehicleService.airConditionerUnlock(dto, uid));
    }
    /**
     * 根据订单id查询四门三盖状态
     * @param orderId
     * @return
     */
    @GetMapping("/logan/inner/vehicle/getVehicleDoorStatus/{orderId}")
    public R<VehicleDoorStatusVo> getVehicleDoorStatus(@PathVariable Long orderId){
        VehicleDoorStatusVo vehicleDoorStatus = xpVehicleService.getVehicleDoorStatus(orderId);
        return R.ok(vehicleDoorStatus);
    }
}
xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/model/dto/AirConditionerUnlockDTO.java
New file
@@ -0,0 +1,17 @@
package com.xiaopeng.vehicleConvoy.model.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel
public class AirConditionerUnlockDTO {
    @ApiModelProperty(value = "订单id")
    private Long orderId;
    @ApiModelProperty(value = "开关控制(开=true, 关=false)")
    private Boolean open;
}
xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/service/IXpVehicleService.java
@@ -6,11 +6,13 @@
import com.xiaopeng.api.dto.vehicleConvoy.VehicleTemperatureDto;
import com.xiaopeng.api.model.account.XpOperationsUser;
import com.xiaopeng.api.model.vehicleConvoy.XpVehicle;
import com.xiaopeng.api.vo.vehicleConvoy.VehicleDoorStatusVo;
import com.xiaopeng.common.core.web.page.PageInfo;
import com.xiaopeng.vehicleConvoy.model.dto.*;
import com.xiaopeng.vehicleConvoy.model.importExcel.XpVehicleImportExcel;
import com.xiaopeng.vehicleConvoy.model.query.*;
import com.xiaopeng.vehicleConvoy.model.vo.XpVehicleVO;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
@@ -218,4 +220,30 @@
     * @return
     */
    Integer unlockDoor(UnlockAppDTO dto, XpOperationsUser xpOperationsUser);
    /**
     * 根据订单id获取空调开关状态
     * @param orderId
     * @param uid
     * @return
     */
    Boolean queryVehicleAirConditionerSwitch(Long orderId, String uid);
    /**
     * 开关空调
     * @param dto
     * @param uid
     * @return
     */
    Integer airConditionerUnlock(AirConditionerUnlockDTO dto, String uid);
    /**
     * 获取车辆车门和后备箱状态
     * @param vehicleId
     * @return
     */
    VehicleDoorStatusVo getVehicleDoorStatus(Long orderId);
}
xp-robotaxi-vehicle-convoy-boot/src/main/java/com/xiaopeng/vehicleConvoy/service/impl/XpVehicleServiceImpl.java
@@ -3,6 +3,8 @@
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
@@ -40,6 +42,7 @@
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.exception.base.BaseException;
import com.xiaopeng.common.core.utils.GeodesyUtil;
import com.xiaopeng.common.core.web.page.PageInfo;
import com.xiaopeng.common.redis.service.RedisService;
@@ -51,6 +54,7 @@
import com.xiaopeng.vehicleConvoy.model.importExcel.XpVehicleImportExcel;
import com.xiaopeng.vehicleConvoy.model.query.*;
import com.xiaopeng.vehicleConvoy.model.vo.XpVehicleVO;
import com.xiaopeng.vehicleConvoy.service.AsyncVehicleStateService;
import com.xiaopeng.vehicleConvoy.service.IXpFleetVehicleService;
import com.xiaopeng.vehicleConvoy.service.IXpVehicleAnnualRecordService;
import com.xiaopeng.vehicleConvoy.service.IXpVehicleService;
@@ -78,6 +82,7 @@
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -136,6 +141,8 @@
    @Resource
    private UserResourcesClient userResourcesClient;
    @Resource
    private AsyncVehicleStateService asyncVehicleStateService;
    @Value("${vehicle.control.distance-threshold:100}")
    private int distanceThreshold;
@@ -1329,4 +1336,116 @@
        return 0;
    }
    /**
     * 根据订单id获取空调开关状态
     * @param orderId
     * @param uid
     * @return
     */
    @Override
    public Boolean queryVehicleAirConditionerSwitch(Long orderId, String uid) {
        XpOrder xpOrder = xpOrderClient.queryOrderById(orderId).getData();
        if(null == xpOrder){
            return false;
        }
        XpVehicle vehicle = this.getById(xpOrder.getVehicleId());
        String vin = vehicle.getVehicleVin();
        // 1. 创建车辆控制指令
        VehiclesCommandVo commandVo = new VehiclesCommandVo();
        commandVo.setVin(vin);
        commandVo.setCommandCode(VehiclesCommandEnum.STATUS_REPORT_NOW.getCommand());
        // 2. 下发指令获取消息ID
        String msgId = vehicleControlUtil.vehiclesCommand(commandVo, Long.valueOf(uid));
        if (StrUtil.isEmpty(msgId)) {
            throw new BaseException("获取消息ID失败: " + msgId);
        }
        log.info("同步查询车辆空调状态成功,开始等到查询回调结果");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        String cacheObject = redisService.getCacheObject("HVAC:" + msgId);
        if(StringUtils.isEmpty(cacheObject)){
            return false;
        }
        JSONObject jsonObject = JSON.parseObject(cacheObject);
        Integer hvac_on = jsonObject.getInteger("hvac_on");
        return hvac_on == 1;
    }
    /**
     * 开关空调
     * @param dto
     * @param uid
     * @return
     */
    @Override
    public Integer airConditionerUnlock(AirConditionerUnlockDTO dto, String uid) {
        XpOrder xpOrder = xpOrderClient.queryOrderById(dto.getOrderId()).getData();
        if(null == xpOrder){
            return 0;
        }
        XpVehicle vehicle = this.getById(xpOrder.getVehicleId());
        VehiclesCommandVo vehiclesCommandVo = new VehiclesCommandVo();
        vehiclesCommandVo.setVin(vehicle.getVehicleVin());
        vehiclesCommandVo.setCommandCode(dto.getOpen() ? VehiclesCommandEnum.HVAC_ON.getCommand() : VehiclesCommandEnum.HVAC_OFF.getCommand());
        String msgId = vehicleControlUtil.vehiclesCommand(vehiclesCommandVo, Long.valueOf(uid));
        log.info("空调控制结果:{}", msgId);
        if (StringUtils.isEmpty(msgId)) {
            return 0;
        } else {
            return 1;
        }
    }
    /**
     * 获取车辆门锁状态
     * @param orderId
     * @return
     */
    @Override
    public VehicleDoorStatusVo getVehicleDoorStatus(Long orderId) {
        XpOrder xpOrder = xpOrderClient.queryOrderById(orderId).getData();
        if(null == xpOrder){
            return null;
        }
        XpVehicle vehicle = this.getById(xpOrder.getVehicleId());
        String vin = vehicle.getVehicleVin();
        // 1. 创建车辆控制指令
        VehiclesCommandVo commandVo = new VehiclesCommandVo();
        commandVo.setVin(vin);
        commandVo.setCommandCode(VehiclesCommandEnum.STATUS_REPORT_NOW.getCommand());
        // 2. 下发指令获取消息ID
        String msgId = vehicleControlUtil.vehiclesCommand(commandVo, xpOrder.getAppUserId());
        if (StrUtil.isEmpty(msgId)) {
            throw new BaseException("获取消息ID失败: " + msgId);
        }
        log.info("同步查询车辆空调状态成功,开始等到查询回调结果");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        String cacheObject = redisService.getCacheObject("DOOR:" + msgId);
        if(StringUtils.isEmpty(cacheObject)){
            return null;
        }
        // todo 待文档补充后再更新代码
        VehicleDoorStatusVo vo = new VehicleDoorStatusVo();
        JSONObject jsonObject = JSON.parseObject(cacheObject);
        Integer driver_door_ajar_st = jsonObject.getInteger("driver_door_ajar_st");
        Integer passenger_door_ajar_st = jsonObject.getInteger("passenger_door_ajar_st");
        Integer rear_left_door_ajar_st = jsonObject.getInteger("rear_left_door_ajar_st");
        Integer rear_right_door_ajar_st = jsonObject.getInteger("rear_right_door_ajar_st");
        Integer trunk_ajar_st_new = jsonObject.getInteger("trunk_ajar_st_new");
        vo.setTrunk(trunk_ajar_st_new == 5 ? 1 : 0);
        return vo;
    }
}