puzhibing
2026-05-17 993a6e6ea8ae047b9e5fffd249ae7adfcd650d3a
xp-robotaxi-order-boot/src/main/java/com/xiaopeng/order/controller/XpOrderController.java
@@ -3,39 +3,69 @@
import cn.afterturn.easypoi.excel.ExcelExportUtil;
import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.hutool.core.bean.BeanUtil;
import com.alibaba.fastjson.JSON;
import cn.hutool.core.util.DesensitizedUtil;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.xiaopeng.api.dto.QueryOrderRouteDTO;
import com.xiaopeng.api.dto.order.AddCruiseMissionDto;
import com.xiaopeng.api.dto.operation.LonLatDto;
import com.xiaopeng.api.dto.order.GetOrderNumDto;
import com.xiaopeng.api.dto.order.PopOrderDto;
import com.xiaopeng.api.dto.statistics.OrderStatisticsDTO;
import com.xiaopeng.api.dto.statistics.StatisticsQueryDTO;
import com.xiaopeng.api.dto.vehicleConvoy.BaseVehicleUserDto;
import com.xiaopeng.api.dto.vehicleConvoy.BleInfoResponseDto;
import com.xiaopeng.api.feignClient.account.SysGroupMenuClient;
import com.xiaopeng.api.feignClient.operation.XpSystemSettingClient;
import com.xiaopeng.api.feignClient.operation.XpWhiteListClient;
import com.xiaopeng.api.feignClient.vehicleConvoy.BluetoothUnlockClient;
import com.xiaopeng.api.feignClient.vehicleConvoy.VehicleMacClient;
import com.xiaopeng.api.feignClient.vehicleConvoy.XpVehicleClient;
import com.xiaopeng.api.model.operation.XpOfficialPartner;
import com.xiaopeng.api.model.operation.XpSystemSetting;
import com.xiaopeng.api.model.operation.XpWhitelist;
import com.xiaopeng.api.model.order.XpOrder;
import com.xiaopeng.api.model.vehicleConvoy.XpVehicle;
import com.xiaopeng.api.vo.account.LoginUser;
import com.xiaopeng.api.vo.automaticPilot.VehiclePosition;
import com.xiaopeng.api.vo.operation.NavigationVo;
import com.xiaopeng.api.vo.operation.NearestNextVo;
import com.xiaopeng.api.vo.operation.RouteVo;
import com.xiaopeng.api.vo.order.*;
import com.xiaopeng.common.core.constant.CacheConstants;
import com.xiaopeng.api.vo.vehicleConvoy.GetBleInfoVo;
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.SensitiveDataCryptoUtil;
import com.xiaopeng.common.core.utils.StringUtils;
import com.xiaopeng.common.core.utils.WebUtils;
import com.xiaopeng.common.core.web.page.PageInfo;
import com.xiaopeng.common.log.annotation.AdminOperationLog;
import com.xiaopeng.common.log.enums.AdminBusinessType;
import com.xiaopeng.common.log.enums.AdminOperationType;
import com.xiaopeng.common.redis.service.RedisService;
import com.xiaopeng.order.mangodb.model.OrderTrajectory;
import com.xiaopeng.common.security.service.TokenService;
import com.xiaopeng.order.config.BluetoothDebugConfig;
import com.xiaopeng.order.dto.OrderNavigationDto;
import com.xiaopeng.api.model.order.OrderTrajectory;
import com.xiaopeng.order.mangodb.service.OrderTrajectoryService;
import com.xiaopeng.order.model.dto.XpOrderCommentDto;
import com.xiaopeng.order.model.dto.XpOrderOperateDTO;
import com.xiaopeng.order.model.dto.XpOrderPayDto;
import com.xiaopeng.order.model.dto.XpOrderReassignDTO;
import com.xiaopeng.order.model.exportExcel.XpOrderExportExcel;
import com.xiaopeng.order.model.query.XpHistoryOrderListQuery;
import com.xiaopeng.order.model.query.XpOrderExcelQuery;
import com.xiaopeng.order.model.query.XpOrderPageQuery;
import com.xiaopeng.order.model.dto.XpOrderOperateDTO;
import com.xiaopeng.order.model.dto.XpOrderReassignDTO;
import com.xiaopeng.order.model.vo.XpOrderDetailVO;
import com.xiaopeng.order.model.vo.XpOrderHistoryVO;
import com.xiaopeng.order.model.vo.XpOrderPageVO;
import com.xiaopeng.order.service.IXpOrderService;
import com.xiaopeng.order.service.impl.BootCommonService;
import com.xiaopeng.order.vo.BluetoothTokenVo;
import com.xiaopeng.order.vo.OrderDetailVo;
import com.xiaopeng.order.vo.UpdateOrderStatusVo;
import com.xiaopeng.order.vo.XpOrderInfoVo;
import com.xiaopeng.unifiedverification.interceptor.XpClientCheck;
import io.swagger.annotations.*;
import lombok.RequiredArgsConstructor;
@@ -47,17 +77,22 @@
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.List;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.*;
import java.util.stream.Collectors;
/**
 * <p>
@@ -76,8 +111,32 @@
    private final RedisService redisService;
    @Autowired
    private MongoTemplate mongoTemplate;
    @Autowired
    @Resource
    private XpVehicleClient xpVehicleClient;
    @Resource
    private  VehicleMacClient vehicleMacClient;
    @Resource
    private SysGroupMenuClient groupMenuClient;
    @Resource
    private XpWhiteListClient xpWhiteListClient;
    @Resource
    private TokenService tokenService;
    @Autowired
    private BluetoothDebugConfig bluetoothDebugConfig;
    @Resource
    private XpSystemSettingClient xpSystemSettingClient;
    @Resource
    @Lazy
    private BootCommonService bootCommonService;
    /**
     * 获取订单统计数据(内部接口)
@@ -86,7 +145,7 @@
     * @return 订单统计数据
     */
    @ApiOperation(value = "获取订单统计数据", tags = {"内部接口"})
    @PostMapping("/inner/order/statistics")
    @PostMapping("/logan/inner/order/statistics")
    public R<OrderStatisticsDTO> getOrderStatistics(@RequestBody StatisticsQueryDTO query) {
        return R.ok(xpOrderService.getOrderStatistics(query));
    }
@@ -96,14 +155,14 @@
     * 查询是否有未支付订单 或 正在进行的订单
     * 返回0 可以下单  1有进行中的订单  8有待支付的
     */
    @PostMapping("/inner/order/has-unpaid-order")
    @PostMapping("/logan/inner/order/has-unpaid-order")
    @ApiOperation(value = "查询是否有未支付订单 或 正在进行的订单", tags = {"内部接口"})
    public R<Long> hasUnPaodOrder(String uid) {
        List<XpOrder> list = xpOrderService.list(new LambdaQueryWrapper<XpOrder>().eq(XpOrder::getAppUserId, Long.parseLong(uid)).in(XpOrder::getStatus, Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8)));
        if (!list.isEmpty()) {
            return R.ok(list.get(0).getId());
        }
        return R.ok(0l);
        return R.ok(0L);
    }
@@ -113,12 +172,12 @@
     * @param orderId 订单id
     * @return 订单详情
     */
    @PostMapping("/inner/order/queryOrderById")
    @PostMapping("/logan/inner/order/queryOrderById")
    public R<XpOrder> queryOrderById(@RequestParam Long orderId) {
        return R.ok(xpOrderService.getById(orderId));
    }
    @PostMapping("/inner/order/queryOrderByOrderNo")
    @PostMapping("/logan/inner/order/queryOrderByOrderNo")
    public R<XpOrder> queryOrderByOrderNo(@RequestParam String orderNo) {
        return R.ok(xpOrderService.getOne(new LambdaQueryWrapper<XpOrder>().eq(XpOrder::getOrderNumber, orderNo)));
    }
@@ -128,16 +187,27 @@
     *
     * @return 订单数量
     */
    @PostMapping("/inner/order/getOrderNum")
    @PostMapping("/logan/inner/order/getOrderNum")
    public R<Integer> getOrderNum(@RequestBody GetOrderNumDto dto) {
        return R.ok(xpOrderService.getOrderNum(dto));
    }
    /**
     * 查询指定城市进行中的订单数量(status=1)
     *
     * @param cityId 开通城市ID
     * @return 进行中订单数量
     */
    @GetMapping("/logan/inner/order/countActiveOrders")
    public R<Integer> countActiveOrdersByCityId(@RequestParam("cityId") Long cityId) {
        return R.ok(xpOrderService.countActiveOrdersByCityId(cityId));
    }
    /**
     * 成功操作下发命令后调用 用于弹出redis队列的获取成功的数据
     */
    @PostMapping("/inner/order/popOrder")
    @PostMapping("/logan/inner/order/popOrder")
    public R<?> popOrder(@RequestBody PopOrderDto dto) {
        xpOrderService.popOrder(dto);
        return R.ok();
@@ -148,7 +218,7 @@
     *
     * @param xpOrder 订单信息
     */
    @PostMapping("/inner/order/updateOrder")
    @PostMapping("/logan/inner/order/updateOrder")
    public R<?> updateOrder(@RequestBody XpOrder xpOrder) {
        xpOrder.setAppUserId(null);
        return xpOrderService.updateById(xpOrder) ? R.ok() : R.fail();
@@ -161,7 +231,7 @@
     * @param xpOrder 订单信息
     * @return 订单ID
     */
    @PostMapping("/inner/order/addOrder")
    @PostMapping("/logan/inner/order/addOrder")
    public R<Long> addOrder(@RequestBody XpOrder xpOrder) {
        boolean save = xpOrderService.addOrder(xpOrder);
        if (!save) {
@@ -177,7 +247,7 @@
     * @param orderId 订单ID
     * @return 操作结果
     */
    @PostMapping("/inner/order/cancelOrder")
    @PostMapping("/logan/inner/order/cancelOrder")
    public R<?> cancelOrder(@RequestParam("orderId") String orderId) {
        xpOrderService.cancelOrder(orderId);
        return R.ok();
@@ -189,10 +259,15 @@
     * @param query 分页查询参数
     * @return 分页结果
     */
    @ApiOperation(value = "订单分页列表", tags = {"管理后台-交易管理-订单管理"})
    @ApiOperation(value = "订单分页列表", tags = {"管理后台-交易管理-订单管理", "管理后台-活动管理"})
    @PostMapping("/api/v1/order/pageList")
    public R<PageInfo<XpOrderPageVO>> pageList(@Validated @RequestBody XpOrderPageQuery query) {
    public R<PageInfo<XpOrderPageVO>> pageList( @RequestBody XpOrderPageQuery query) {
        LoginUser loginUser = tokenService.getLoginUser();
        PageInfo<XpOrderPageVO> pageInfo = xpOrderService.pageList(query);
        if(!loginUser.getPhoneDesensitizationExempt()){
            pageInfo.getRecords().forEach(s->s.setPhoneNumber(DesensitizedUtil.mobilePhone(s.getPhoneNumber())));
        }
        return R.ok(pageInfo);
    }
@@ -205,7 +280,12 @@
    @ApiOperation(value = "订单详情", tags = {"管理后台-交易管理-订单管理"})
    @GetMapping("/api/v1/order/queryById")
    public R<XpOrderDetailVO> queryById(@ApiParam(value = "订单id", required = true) @RequestParam Long id) {
        LoginUser loginUser = tokenService.getLoginUser();
        XpOrderDetailVO detail = xpOrderService.getOrderDetail(id);
        if(!loginUser.getPhoneDesensitizationExempt()){
            detail.setPhoneNumber(DesensitizedUtil.mobilePhone(detail.getPhoneNumber()));
        }
        return R.ok(detail);
    }
@@ -217,6 +297,12 @@
     */
    @ApiOperation(value = "删除订单", tags = {"管理后台-交易管理-订单管理"})
    @DeleteMapping("/api/v1/order/deleteById")
    @AdminOperationLog(
            businessType = AdminBusinessType.ORDER_MANAGEMENT,
            operationType = AdminOperationType.DELETE,
            contentExpression = "'删除订单,id=' + #p0",
            appendRequestData = false
    )
    public R<?> deleteById(@ApiParam(value = "订单id", required = true) @RequestParam Long id) {
        xpOrderService.deleteOrder(id);
        return R.ok();
@@ -229,6 +315,12 @@
     */
    @ApiOperation(value = "订单导出", tags = {"管理后台-交易管理-订单管理"})
    @PostMapping("/api/v1/order/exportExcel")
    @AdminOperationLog(
            businessType = AdminBusinessType.ORDER_MANAGEMENT,
            operationType = AdminOperationType.EXPORT,
            content = "订单导出",
            appendRequestData = true
    )
    public void exportExcel(@RequestBody XpOrderExcelQuery query) {
        // 1. 查询导出数据
        List<XpOrderPageVO> dataList = xpOrderService.exportList(query);
@@ -270,8 +362,14 @@
     */
    @ApiOperation(value = "取消/关闭订单", tags = {"管理后台-交易管理-订单管理"})
    @PostMapping("/api/v1/order/cancelAndCloseById")
    @AdminOperationLog(
            businessType = AdminBusinessType.ORDER_MANAGEMENT,
            operationType = AdminOperationType.EDIT,
            content = "取消/关闭订单",
            appendRequestData = true
    )
    public R<?> cancelAndCloseById(@Validated @RequestBody XpOrderOperateDTO dto) {
        xpOrderService.cancelOrCloseOrder(dto.getOrderId(), dto.getType());
        xpOrderService.cancelOrCloseOrder(dto.getOrderId(), dto.getType(),dto.getIsFree());
        return R.ok();
    }
@@ -281,8 +379,14 @@
     * @param dto 改派请求参数
     * @return 操作结果
     */
    @ApiOperation(value = "改派订单", tags = {"管理后台-交易管理-订单管理"}, notes = "车辆列表在vehicleConvoy服务")
    @ApiOperation(value = "改派订单", tags = {"管理后台-交易管理-订单管理"}, notes = "车辆列表在 vehicleConvoy.availableList 服务")
    @PostMapping("/api/v1/order/reassignById")
    @AdminOperationLog(
            businessType = AdminBusinessType.ORDER_MANAGEMENT,
            operationType = AdminOperationType.EDIT,
            content = "改派订单",
            appendRequestData = true
    )
    public R<?> reassignById(@Validated @RequestBody XpOrderReassignDTO dto) {
        xpOrderService.reassignOrder(
                dto.getOrderId(),
@@ -299,6 +403,12 @@
            @ApiImplicitParam(name = "id", value = "订单ID", required = true, dataType = "Long"),
            @ApiImplicitParam(name = "amount", value = "新的订单金额", required = true, dataType = "BigDecimal")
    })
    @AdminOperationLog(
            businessType = AdminBusinessType.ORDER_MANAGEMENT,
            operationType = AdminOperationType.EDIT,
            contentExpression = "'订单改价,id=' + #p0 + ',amount=' + #p1",
            appendRequestData = false
    )
    public R<?> changeAmount(@RequestParam("id") Long id, @RequestParam("amount") BigDecimal amount) {
        xpOrderService.changeAmount(id, amount);
        return R.ok();
@@ -307,13 +417,32 @@
    /**
     * 获取用户快捷胶囊 历史 时段 终点高点
     */
    @ApiOperation(value = "获取用户快捷胶囊 历史 时段 终点高点", tags = {"小程序-获取用户快捷胶囊 历史 时段 终点高点"})
    @ApiOperation(value = "获取首页快捷胶囊推荐站点", notes = "根据当前起点和用户近30天有效订单,按场景优先推荐目的地;无匹配时返回空数据", tags = {"小程序-首页快捷胶囊"}, response = XpPointInfoVo.class)
    @ApiImplicitParams({
            @ApiImplicitParam(name = "startPointId", value = "当前首页起点站点ID", required = true, dataType = "Long", paramType = "query")
    })
    @GetMapping("/api/v1/mini-program/order/getUserQuickCapsule")
    @XpClientCheck(checkToken = false, checkOtpToken = true)
    public R<XpPointInfoVo> getUserQuickCapsule(HttpServletRequest request) {
    public R<XpPointInfoVo> getUserQuickCapsule(@RequestParam Long startPointId, HttpServletRequest request) {
        // uid已通过拦截器otp校验,可以直接使用
        String uid = request.getHeader("XP-Uid");
        return R.ok(xpOrderService.getUserQuickCapsule(uid));
        return R.ok(xpOrderService.getUserQuickCapsule(uid, startPointId));
    }
    /**
     * 获取用户快捷胶囊 历史 时段 终点高点
     */
    @ApiOperation(value = "获取首页快捷胶囊推荐站点", notes = "根据当前起点和用户近30天有效订单,按场景优先推荐目的地;无匹配时返回空数据", tags = {"小程序-首页快捷胶囊"}, response = XpPointInfoVo.class)
    @ApiImplicitParams({
            @ApiImplicitParam(name = "startPointId", value = "当前首页起点站点ID", required = true, dataType = "Long", paramType = "query")
    })
    @GetMapping("/api/v1/mini-program/order/quick-capsule")
    @XpClientCheck(checkToken = false, checkOtpToken = true)
    public R<NearestNextVo> quickCapsule(@RequestParam Long startPointId,HttpServletRequest request) {
        String uid = request.getHeader("XP-Uid");
        NearestNextVo nearestNextVo = xpOrderService.quickCapsule(uid, startPointId);
        return R.ok(nearestNextVo);
    }
@@ -326,7 +455,14 @@
    public R<OrderDetailVo> orderDetail(@PathVariable("orderId") Long orderId, HttpServletRequest request) {
        // uid已通过拦截器otp校验,可以直接使用
        String uid = request.getHeader("XP-Uid");
        return R.ok(xpOrderService.orderDetail(orderId, uid));
        OrderDetailVo orderDetailVo = xpOrderService.orderDetail(orderId, uid);
        BigDecimal orderMoney = orderDetailVo.getOrderMoney();
        if (orderMoney == null) {
            orderDetailVo.setOrderMoneyStr("0.00");
        } else {
            orderDetailVo.setOrderMoneyStr(orderMoney.setScale(2, RoundingMode.HALF_UP).toString());
        }
        return R.ok(orderDetailVo);
    }
@@ -336,10 +472,10 @@
    @ApiOperation(value = "获取历史下单成功起点10条数据列表", tags = {"小程序-获取历史下单成功起点10条数据列表"})
    @GetMapping("/base/v1/mini-program/history/startPoints")
    @XpClientCheck(checkToken = false, checkOtpToken = true)
    public R<List<XpHistoryStartPointsVo>> getHistoryStartPoints(double lon, double lat,Long electronicAreaId, HttpServletRequest request) {
    public R<List<XpHistoryStartPointsVo>> getHistoryStartPoints(double lon, double lat, Long electronicAreaId, HttpServletRequest request) {
        // uid已通过拦截器otp校验,可以直接使用
        String uid = request.getHeader("XP-Uid");
        return R.ok(xpOrderService.getHistoryStartPoints(uid, lon, lat,electronicAreaId));
        return R.ok(xpOrderService.getHistoryStartPoints(uid, lon, lat, electronicAreaId));
    }
    /**
@@ -348,10 +484,10 @@
    @ApiOperation(value = "获取历史下单成功终点10条数据列表", tags = {"小程序-获取历史下单成功终点10条数据列表"})
    @GetMapping("/base/v1/mini-program/history/endPoints")
    @XpClientCheck(checkToken = false, checkOtpToken = true)
    public R<List<XpHistoryEndPointsVo>> getHistoryEndPoints(double lon, double lat,Long electronicAreaId, HttpServletRequest request) {
    public R<List<XpHistoryEndPointsVo>> getHistoryEndPoints(double lon, double lat, Long electronicAreaId, HttpServletRequest request) {
        // uid已通过拦截器otp校验,可以直接使用
        String uid = request.getHeader("XP-Uid");
        return R.ok(xpOrderService.getHistoryEndPoints(uid, lon, lat,electronicAreaId));
        return R.ok(xpOrderService.getHistoryEndPoints(uid, lon, lat, electronicAreaId));
    }
@@ -367,6 +503,17 @@
        return R.ok(xpOrderService.getActiveOrder(uid));
    }
    /**
     * 获取正在进行中的订单_行程管理
     */
    @ApiOperation(value = "获取正在进行中的订单_行程管理", tags = {"小程序-获取正在进行中的订单_行程管理"})
    @GetMapping("/api/v1/mini-program/order/active_itineraryManage")
    @XpClientCheck(checkToken = false, checkOtpToken = true)
    public R<XpOrderDetailVO> getActiveOrder_itineraryManage(HttpServletRequest request) {
        // uid已通过拦截器otp校验,可以直接使用
        String uid = request.getHeader("XP-Uid");
        return R.ok(xpOrderService.getActiveOrder_itineraryManage(uid));
    }
    /**
     * 查询历史订单列表
@@ -382,10 +529,21 @@
    /**
     * 1.0 获取待支付订单数量
     */
    @ApiOperation(value = "1.0 获取待支付订单数量", tags = {"小程序-1.0 获取待支付订单数量"})
    @GetMapping("/api/v1/mini-program/order/getWaitPayOrderNum")
    @XpClientCheck(checkToken = false, checkOtpToken = true)
    public R<Integer> getWaitPayOrderNum(HttpServletRequest request) {
        // uid已通过拦截器otp校验,可以直接使用
        String uid = request.getHeader("XP-Uid");
        return R.ok(xpOrderService.getWaitPayOrderNum(uid));
    }
    /**
     * +
     * 取消订单
     */
    @PutMapping("/api/v1/mini-program/order/cancel/{orderId}")
    @ApiOperation(value = "取消订单", tags = {"小程序-取消订单"})
    @XpClientCheck(checkToken = false, checkOtpToken = true)
@@ -404,15 +562,29 @@
    @PutMapping("/api/v1/mini-program/order/endTrip/{orderId}")
    @ApiOperation(value = "结束行程", tags = {"小程序-结束行程"})
    @XpClientCheck(checkToken = false, checkOtpToken = true)
    public R endTrip(@PathVariable("orderId") Long orderId, HttpServletRequest request) {
    public R<?> endTrip(@PathVariable("orderId") Long orderId, HttpServletRequest request) {
        // uid已通过拦截器otp校验,可以直接使用
        String uid = request.getHeader("XP-Uid");
        try {
            xpOrderService.endTrip(orderId, uid);
            Boolean b = xpOrderService.endTrip(orderId, uid);
            //判断是否测试免费
            XpSystemSetting xpSystemSetting = xpSystemSettingClient.getSystemSettingOne().getData();
            if (xpSystemSetting != null && xpSystemSetting.getOperateFree() == 1) {
                //将支付金额修改成0 并将状态修改成 订单状态9
                XpOrder xpOrder1 = new XpOrder();
                xpOrder1.setId(orderId);
                xpOrder1.setIsFree(true);//豁免
                xpOrder1.setPayMoney(BigDecimal.ZERO);
                xpOrder1.setStatus(OrderStatusEnum.PENDING_PAYMENT.getCode());
                xpOrder1.setUpdateTime(LocalDateTime.now());
                xpOrderService.updateById(xpOrder1);
                bootCommonService.setCache_orderStatus(xpOrder1.getId(), xpOrder1, null);
            }
            return R.ok(b);
        } catch (Exception e) {
            return R.ok();
        }
        return R.ok();
    }
@@ -420,13 +592,13 @@
     * +
     * 获取行程费用明细
     */
    @PutMapping("/api/v1/mini-program/order/fare-breakdown/{orderId}")
    @PostMapping("/api/v1/mini-program/order/fare-breakdown")
    @ApiOperation(value = "获取行程费用明细", tags = {"小程序-获取行程费用明细"})
    @XpClientCheck(checkToken = false, checkOtpToken = true)
    public R<FareBreakdownVo> fareBreakdown(@PathVariable("orderId") Long orderId, HttpServletRequest request) {
    public R<FareBreakdownVo> fareBreakdown(@RequestBody com.xiaopeng.order.vo.FareBreakdownVo vo, HttpServletRequest request) {
        // uid已通过拦截器otp校验,可以直接使用
        String uid = request.getHeader("XP-Uid");
        return xpOrderService.fareBreakdown(orderId, uid);
        return xpOrderService.fareBreakdown(vo, uid);
    }
    /**
@@ -471,10 +643,20 @@
     * 订单状态修改-车控消息推送我方调用(内部接口)
     */
    @ApiOperation(value = "订单状态推送修改")
    @PostMapping("/inner/v1/order/updateOrderStatus")
    @PostMapping("/logan/inner/v1/order/updateOrderStatus")
    public R<String> updateOrderStatus(@RequestParam Long orderId,
                                       @RequestParam Integer status) {
        return xpOrderService.updateOrderStatus(orderId, status);
    }
    /**
     * 行驶中车辆异常,订单状态修改- 修改状态=7 再修改 金额-再将状态修改成8
     */
    @ApiOperation(value = "行驶中车辆异常,订单状态修改- 修改状态=7 再修改 金额-再将状态修改成8")
    @PostMapping("/logan/inner/v1/order/carAbnormalUpdateOrderStatus")
    public R<String> carAbnormalUpdateOrderStatus(@RequestParam Long orderId) {
//        Integer status = OrderStatusEnum.SEE_OFF_GUESTS.getCode();
        return xpOrderService.carAbnormalUpdateOrderStatus(orderId);
    }
    /**
@@ -492,7 +674,7 @@
     * 订单车辆定位实时存储-订单车辆定位实时存储(内部接口)
     */
    @ApiOperation(value = "订单车辆定位实时存储")
    @PostMapping("/inner/v1/order/saveVehicleLocation")
    @PostMapping("/logan/inner/v1/order/saveVehicleLocation")
    public R<String> saveVehicleLocation(@RequestBody OrderTrajectoryVo vo) {
        OrderTrajectory orderTrajectory = new OrderTrajectory();
        BeanUtils.copyProperties(vo, orderTrajectory);
@@ -505,13 +687,14 @@
     */
    @ApiOperation(value = "订单查询实时轨迹路线")
    @PostMapping("/api/v1/mini-program/order/queryOrderRoute")
    @XpClientCheck(checkToken = false, checkOtpToken = true)
    public R<RouteVo> queryOrderRoute(@Validated @RequestBody QueryOrderRouteDTO dto, HttpServletRequest request) {
        if (dto.getOrderId() == null) {
            return R.fail("参数不能为空");
        }
        String uid = request.getHeader("XP-Uid");
        // 查询订单信息
        XpOrder xpOrder = xpOrderService.getById(dto.getOrderId());
        // 获取路线规划
//        RouteVo routeVo = redisService.getCacheObject(CacheConstants.ORDER_ROUTE_CAPTURE + "_" + dto.getOrderId() + "_" +dto.getRouteType());
        String key = "";
        //接驾情况,获取车辆位置到起点的导航
        if (xpOrder.getStatus() < 5) {
@@ -524,23 +707,306 @@
        Object cacheObject = redisService.getCacheObject(key);
        RouteVo routeVo = JSON.parseObject(cacheObject.toString(), RouteVo.class);
        XpVehicle xpVehicle = xpVehicleClient.queryById(xpOrder.getVehicleId()).getData();
        VehiclePosition vehiclePosition = mongoTemplate.findOne(new Query(Criteria.where("vin").is(xpVehicle.getVehicleVin())), VehiclePosition.class);
        String vin = SensitiveDataCryptoUtil.decryptIfPossible(xpVehicle.getVehicleVin());
        VehiclePosition vehiclePosition = mongoTemplate.findOne(new Query(Criteria.where("vin").is(vin)), VehiclePosition.class);
        routeVo.setDirection(null != vehiclePosition ? vehiclePosition.getCourse() : 90);
        routeVo.setEndLongitude(xpOrder.getEndLon());
        routeVo.setEndLatitude(xpOrder.getEndLat());
        return R.ok(routeVo);
    }
    /**
     * 订单查询轨迹路线
     */
    @ApiOperation(value = "订单查询轨迹路线")
    @PostMapping("/api/v1/mini-program/order/queryOrderTrajectory")
    public R<List<OrderTrajectory>> queryOrderTrajectory(@RequestParam String orderId, HttpServletRequest request) {
        String uid = request.getHeader("XP-Uid");
    @GetMapping("/api/v1/mini-program/order/queryOrderTrajectory")
    @XpClientCheck(checkToken = false, checkOtpToken = true)
    public R<RouteVo> queryOrderTrajectory(@RequestParam String orderId, HttpServletRequest request) {
        if (orderId == null) {
            return R.fail("参数不能为空");
        }
        List<OrderTrajectory> trajectories = mongoTemplate.find(
                new Query(Criteria.where("orderId").is(orderId))
                new Query(Criteria.where("orderId").is(Long.valueOf(orderId)))
                        .with(Sort.by(Sort.Direction.ASC, "locatetime")),
                OrderTrajectory.class
        );
        return R.ok(trajectories);
        // 封装信息
        RouteVo routeVo = new RouteVo();
        List<LonLatDto> polyline = new ArrayList<>();
        if (!CollectionUtils.isEmpty(trajectories)) {
            for (OrderTrajectory trajectory : trajectories) {
                LonLatDto lonLatDto = new LonLatDto();
                String[] split = trajectory.getLocation().split(",");
                lonLatDto.setLongitude(Double.valueOf(split[0]));
                lonLatDto.setLatitude(Double.valueOf(split[1]));
                polyline.add(lonLatDto);
            }
        }
        routeVo.setPolyline(polyline);
        XpOrder xpOrder = xpOrderService.getById(orderId);
        XpVehicle xpVehicle = xpVehicleClient.queryById(xpOrder.getVehicleId()).getData();
        String vin = SensitiveDataCryptoUtil.decryptIfPossible(xpVehicle.getVehicleVin());
        VehiclePosition vehiclePosition = mongoTemplate.findOne(new Query(Criteria.where("vin").is(vin)), VehiclePosition.class);
        routeVo.setDirection(null != vehiclePosition ? vehiclePosition.getCourse() : 0);
        return R.ok(routeVo);
    }
    /**
     * 当日取消订单次数
     *
     * @param
     * @return 订单数量
     */
    @PostMapping("/logan/inner/order/todayCancellationCount")
    public R<Integer> todayCancellationCount(@RequestParam Long userId){
        long count = xpOrderService.count(new QueryWrapper<XpOrder>().eq("app_user_id", userId).eq("status", 0)
                .eq("is_deleted", 0).last(" and DATE_FORMAT(create_time, '%Y-%m-%d') = DATE_FORMAT(NOW(), '%Y-%m-%d')"));
        return R.ok((int) count);
    }
    /**
     * 创建巡游单和推送车端报文
     * @param orderId
     * @return
     */
    @PostMapping("/logan/inner/order/addNewCruiseMission")
    public R<?> addNewCruiseMission(@RequestParam("orderId") String orderId){
        XpOrder xpOrder = xpOrderService.getById(orderId);
        xpOrderService.addCruiseMission(xpOrder.getVehicleId(), xpOrder);
        return R.ok();
    }
    @ApiOperation(value = "通过订单id获取蓝牙开锁秘钥", tags = {"小程序-业务流程"})
    @GetMapping("/api/v1/mini-program/bluetooth/getUnlocktToken/{orderId}")
    @XpClientCheck(checkToken = false, checkOtpToken = true)
    public R<BluetoothTokenVo> getBluetoothToken(@PathVariable("orderId") Long orderId, HttpServletRequest request) {
        // ========== 联调临时逻辑:通过 Nacos 配置返回固定蓝牙信息(bluetooth-debug.enabled=true 时生效) ==========
        if (bluetoothDebugConfig.isEnabled()) {
            BluetoothTokenVo vo = new BluetoothTokenVo();
            vo.setToken(bluetoothDebugConfig.getToken());
            vo.setMacAddress(bluetoothDebugConfig.getMacAddress());
            vo.setSlaveMacList(bluetoothDebugConfig.getSlaveMacList());
            return R.ok(vo);
        }
        // ========== 联调临时逻辑结束 ==========
        // 原逻辑
        String uid = request.getHeader("XP-Uid");
        XpOrder xpOrder = xpOrderService.getById(orderId);
        if (!Objects.equals(xpOrder.getAppUserId(), Long.valueOf(uid))) {
            return R.ok();
        }
        if(!Arrays.asList(4, 5, 6, 7, 8).contains(xpOrder.getStatus())){
            return R.ok();
        }
        String token = redisService.getCacheObject("rt:bluetooth:unlock:order:" + orderId);
        if (StringUtils.isBlank(token)) {
            return R.ok();
        }
        XpVehicle xpVehicle = xpVehicleClient.queryById(xpOrder.getVehicleId()).getData();
        String vin = SensitiveDataCryptoUtil.decryptIfPossible(xpVehicle.getVehicleVin());
        GetBleInfoVo getBleInfoVo = new GetBleInfoVo();
        getBleInfoVo.setVin(vin);
        BleInfoResponseDto bleInfoResponseDto = vehicleMacClient.getBleInfo(uid, getBleInfoVo);
        if (null == bleInfoResponseDto || 200 != bleInfoResponseDto.getCode()) {
            throw new ServiceException("获取蓝牙信息失败");
        }
        BluetoothTokenVo vo = new BluetoothTokenVo();
        vo.setToken(token);
        vo.setMacAddress(bleInfoResponseDto.getData().getMacAddress());
        vo.setSlaveMacList(bleInfoResponseDto.getData().getSlaveMacList().stream()
                .map(s -> s.getMac()).collect(java.util.stream.Collectors.toList()));
        return R.ok(vo);
    }
    /**
     * 判断用户类型
     * @param userId
     * @return 2=老用户,3=新用户,4=流失用户
     */
    @PostMapping("/logan/inner/order/determineUserType")
    public R<Integer> determineUserType(@RequestParam Long userId){
        //判断流失用户
        XpOrder one = xpOrderService.getOne(new QueryWrapper<XpOrder>().eq("app_user_id", userId).in("status", Arrays.asList(9, 10)).last(" order by create_time desc limit 1"));
        if(null != one){
            long time = LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8")) - one.getCreateTime().toEpochSecond(ZoneOffset.of("+8"));
            if(time >= 60 * 60 * 24 * 5){
                return R.ok(4);
            }
            return R.ok(2);
        }else{
            return R.ok(3);
        }
    }
    /**
     * 判断用户类型
     * @param userType
     * @return 2=老用户,3=新用户,4=流失用户
     */
    @PostMapping("/logan/inner/order/getUserTypeIdList")
    public R<List<Long>> getUserTypeIdList(@RequestParam Integer userType){
        List<XpWhitelist> data = xpWhiteListClient.getXpWhitelistByName("").getData();
        //判断流失用户
        if(4 == userType){
            Set<Long> collect = data.stream().map(XpWhitelist::getAppUserId).collect(Collectors.toSet());
            List<XpOrder> one = xpOrderService.list(new QueryWrapper<XpOrder>().in("app_user_id", collect).in("status", Arrays.asList(9, 10)).last(" order by create_time desc limit 1"));
            if(one.isEmpty()){
                return R.ok(new ArrayList<>());
            }
            List<Long> appUserIds = new ArrayList<>();
            for (XpOrder order : one) {
                long time = LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8")) - order.getCreateTime().toEpochSecond(ZoneOffset.of("+8"));
                if(time >= 60 * 60 * 24 * 5){
                    appUserIds.add(order.getAppUserId());
                }
            }
            return R.ok(appUserIds);
        }
        //新用户
        if(3 == userType){
            Set<Long> collect = data.stream().map(XpWhitelist::getAppUserId).collect(Collectors.toSet());
            List<XpOrder> one = xpOrderService.list(new QueryWrapper<XpOrder>().in("app_user_id", collect).in("status", Arrays.asList(9, 10)).last(" order by create_time desc limit 1"));
            if(one.isEmpty()){
                return R.ok(new ArrayList<>(collect));
            }
            collect.removeAll(one.stream().map(XpOrder::getAppUserId).collect(Collectors.toList()));
            return R.ok(new ArrayList<>(collect));
        }
        //老用户
        if(2 == userType){
            Set<Long> collect = data.stream().map(XpWhitelist::getAppUserId).collect(Collectors.toSet());
            List<XpOrder> one = xpOrderService.list(new QueryWrapper<XpOrder>().in("app_user_id", collect).in("status", Arrays.asList(9, 10)).last(" order by create_time desc limit 1"));
            if(one.isEmpty()){
                return R.ok(new ArrayList<>());
            }
            List<Long> appUserIds = new ArrayList<>();
            for (XpOrder order : one) {
                appUserIds.add(order.getAppUserId());
            }
            return R.ok(appUserIds);
        }
        return R.ok(new ArrayList<>());
    }
    /**
     * 获取用户订单数量
     * @param userId
     * @return
     */
    @PostMapping("/logan/inner/order/getUserOrderNum")
    public R<Long> getUserOrderNum(@RequestParam Long userId){
        long count = xpOrderService.count(new QueryWrapper<XpOrder>().eq("app_user_id", userId)
                .ne("status", 0).eq("is_deleted", 0));
        return R.ok(count);
    }
    @PostMapping("/drainage/v1/order/updateOrderStatus")
    @ApiOperation(value = "更新订单状态", notes = "流量接入-订单信息")
    @ApiImplicitParams({
            @ApiImplicitParam(value = "token", name = "token", required = true, paramType = "header")
    })
    public R<?> officialPartnerUpdateOrderStatus(@RequestBody UpdateOrderStatusVo vo, HttpServletRequest request){
        XpOfficialPartner officialPartner = tokenService.getOfficialPartner();
        return xpOrderService.officialPartnerUpdateOrderStatus(vo, officialPartner.getId());
    }
    @PostMapping("/drainage/v1/order/getOrderNavigation")
    @ApiOperation(value = "获取订单导航数据", notes = "流量接入-订单信息")
    @ApiImplicitParams({
            @ApiImplicitParam(value = "token", name = "token", required = true, paramType = "header")
    })
    public R<NavigationVo> getOrderNavigation(@RequestBody OrderNavigationDto dto, HttpServletRequest request){
        XpOfficialPartner officialPartner = tokenService.getOfficialPartner();
        XpOrder xpOrder = xpOrderService.getById(dto.getOrderId());
        if(!xpOrder.getOfficialPartnerId().equals(officialPartner.getId())){
            return R.fail("无权限");
        }
        NavigationVo navigationVo = null;
        if(1 == dto.getNavigationType()){
            Object cacheObject = redisService.getCacheObject("ORDER_NAVIGATION_START_DATA:" + xpOrder.getAppUserId());
            if(null != cacheObject){
                navigationVo = JSON.parseObject(cacheObject.toString(), NavigationVo.class);
            }
        }
        if(2 == dto.getNavigationType()){
            Object cacheObject = redisService.getCacheObject("ORDER_NAVIGATION_END_DATA:" + xpOrder.getAppUserId());
            if(null != cacheObject){
                navigationVo = JSON.parseObject(cacheObject.toString(), NavigationVo.class);
            }
        }
        return R.ok(navigationVo);
    }
    @GetMapping("/test/bluetooth/getBleInfo")
    @ApiOperation(value = "[测试] 直接调用蓝牙信息接口验证DTO解析", notes = "仅用于本地联调验证,生产环境请勿使用")
    public R<BleInfoResponseDto> testGetBleInfo(@RequestParam String uid, @RequestParam String vin) {
        GetBleInfoVo vo = new GetBleInfoVo();
        vo.setVin(vin);
        BleInfoResponseDto result = vehicleMacClient.getBleInfo(uid, vo);
        return R.ok(result);
    }
    @Resource
    private BluetoothUnlockClient bluetoothUnlockClient;
    @PostMapping("/test/bluetooth/deliver")
    @ApiOperation(value = "[测试] 直接调用蓝牙token下发接口验证响应", notes = "仅用于本地联调验证,生产环境请勿使用")
    public R<BaseVehicleUserDto> testDeliver(@RequestBody com.xiaopeng.api.vo.vehicleConvoy.BluetoothDeliverVo vo) {
        BaseVehicleUserDto result = bluetoothUnlockClient.deliver(vo);
        return R.ok(result);
    }
    @PostMapping("/drainage/v1/order/getOrderInfo")
    @ApiOperation(value = "获取订单详情", notes = "流量接入-订单信息")
    @ApiImplicitParams({
            @ApiImplicitParam(value = "token", name = "token", required = true, paramType = "header")
    })
    public R<XpOrderInfoVo> getOfficialPartnerOrderInfo(@PathVariable Long orderId){
        XpOfficialPartner officialPartner = tokenService.getOfficialPartner();
        XpOrder xpOrder = xpOrderService.getById(orderId);
        if(!xpOrder.getOfficialPartnerId().equals(officialPartner.getId())){
            return R.fail("无权限");
        }
        XpOrderInfoVo xpOrderInfoVo = BeanUtil.copyProperties(xpOrder, XpOrderInfoVo.class);
        return R.ok(xpOrderInfoVo);
    }
    @ApiOperation(value = "获取订单支付状态", tags = {"小程序-业务流程"})
    @GetMapping("/api/v1/mini-program/order/queryOrderStatusFromOrderId")
    @XpClientCheck(checkToken = false, checkOtpToken = true)
    public R<OrderStatusFromOrderIdVo> queryOrderStatusFromOrderId(Long orderId) {
        XpOrder order = xpOrderService.getById(orderId);
        OrderStatusFromOrderIdVo orderStatusFromOrderIdVo = new OrderStatusFromOrderIdVo();
        orderStatusFromOrderIdVo.setOrderId(order.getId());
        orderStatusFromOrderIdVo.setStatus(order.getStatus());
        return R.ok(orderStatusFromOrderIdVo);
    }
    /**
     * 查询是否有未支付订单
     */
    @PostMapping("/logan/inner/order/has-unpay-order")
    public R<Long> hasUnPayOrder(@RequestParam String uid){
        long count = xpOrderService.count(new QueryWrapper<XpOrder>().eq("app_user_id", Long.valueOf(uid))
                .eq("status", 8).eq("is_deleted", 0));
        return R.ok(count);
    }
}