liujie
2026-06-09 c96fbe6b0aeb413a17c429133ee6d81d138144b9
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
package com.xiaopeng.maintenance.schedule;
 
import cn.hutool.core.lang.Snowflake;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.xiaopeng.api.feignClient.automaticPilot.OrderMissionClient;
import com.xiaopeng.api.feignClient.operation.XpCruisePlanClient;
import com.xiaopeng.api.feignClient.order.XpCruiseOrderClient;
import com.xiaopeng.api.feignClient.vehicleConvoy.XpVehicleClient;
import com.xiaopeng.api.model.maintenance.XpTaskRule;
import com.xiaopeng.api.model.order.XpCruiseOrder;
import com.xiaopeng.api.model.vehicleConvoy.XpVehicle;
import com.xiaopeng.api.vo.automaticPilot.VehiclePosition;
import com.xiaopeng.api.vo.automaticPilot.protobuf.TaskActionType;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.utils.SensitiveDataCryptoUtil;
import com.xiaopeng.maintenance.mapper.XpTaskRuleMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
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.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
 
import javax.annotation.Resource;
import java.time.LocalTime;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
 
/**
 * 车辆收车定时任务
 * <p>
 * 根据任务规则配置,在指定时间自动向所有车辆发起收车指令
 * </p>
 *
 * @author mitao
 * @since 2026-02-27
 */
@Component
public class VehicleCollectionScheduleTask {
 
    private static final Logger log = LoggerFactory.getLogger(VehicleCollectionScheduleTask.class);
 
    /**
     * 任务名称
     */
    private static final String TASK_NAME = "车辆收车定时任务";
 
    @Autowired
    private XpTaskRuleMapper taskRuleMapper;
 
    @Autowired
    private XpVehicleClient xpVehicleClient;
 
    @Autowired
    private XpCruiseOrderClient xpCruiseOrderClient;
 
    @Autowired
    private MongoTemplate mongoTemplate;
 
    @Autowired
    private OrderMissionClient orderMissionClient;
 
    @Autowired
    private XpCruisePlanClient xpCruisePlanClient;
 
    @Resource(name = "hutoolSnowflake")
    private Snowflake snowflake;
 
    /**
     * 每分钟检查一次是否需要执行收车任务
     * <p>
     * 检查逻辑:
     * 1. 获取任务规则配置
     * 2. 判断收车功能是否启用
     * 3. 判断当前时间是否为配置的收车时间
     * 4. 获取所有运营中的车辆
     * 5. 向每辆车发送收车指令
     * </p>
     */
    @Scheduled(cron = "0 * * * * ?")
    public void executeCollectionTask() {
        log.info("========== 开始执行车辆收车定时任务检查 ==========");
 
        // 1. 获取任务规则配置
        XpTaskRule taskRule = getTaskRule();
        if (taskRule == null) {
            log.info("未找到任务规则配置,跳过执行");
            return;
        }
 
        // 2. 判断收车功能是否启用
        if (!Boolean.TRUE.equals(taskRule.getVehicleCollection())) {
            log.info("车辆收车功能未启用,跳过执行");
            return;
        }
 
        // 3. 判断当前时间是否为收车时间
        if (!isExecuteTime(taskRule.getVehicleCollectionTime())) {
            log.debug("当前时间不是收车时间,跳过执行");
            return;
        }
 
        log.info("开始执行车辆收车任务");
 
        try {
            // 执行收车逻辑
            int processedCount = executeVehicleCollection();
 
            log.info("========== 车辆收车定时任务执行完成,处理车辆数: {} ==========", processedCount);
 
        } catch (Exception e) {
            log.error("车辆收车定时任务执行失败", e);
        }
    }
 
    /**
     * 获取任务规则配置
     *
     * @return 任务规则
     */
    private XpTaskRule getTaskRule() {
        return taskRuleMapper.selectOne(new LambdaQueryWrapper<XpTaskRule>().last("limit 1"));
    }
 
    /**
     * 判断当前时间是否为收车时间
     * <p>
     * 精确到分钟级别的时间匹配
     * </p>
     *
     * @param executeTime 配置的执行时间
     * @return 是否应该执行
     */
    private boolean isExecuteTime(LocalTime executeTime) {
        if (executeTime == null) {
            log.warn("收车时间未配置");
            return false;
        }
 
        LocalTime now = LocalTime.now();
 
        // 精确到分钟匹配
        return executeTime.getHour() == now.getHour() && executeTime.getMinute() == now.getMinute();
    }
 
    /**
     * 执行车辆收车逻辑
     *
     * @return 处理的车辆数量
     */
    private int executeVehicleCollection() {
        // 1. 获取所有运营中的车辆
        List<XpVehicle> vehicles = getOperatingVehicles();
        if (CollectionUtils.isEmpty(vehicles)) {
            log.info("没有运营中的车辆,无需执行收车");
            return 0;
        }
 
        log.info("找到 {} 辆运营中的车辆需要收车", vehicles.size());
 
        // 2. 向每辆车发送收车指令
        int processedCount = 0;
        for (XpVehicle vehicle : vehicles) {
            try {
                sendCollectionCommand(vehicle);
                processedCount++;
            } catch (Exception e) {
                log.error("向车辆[{}]发送收车指令失败", vehicle.getPlateNumber(), e);
            }
        }
 
        return processedCount;
    }
 
    /**
     * 获取所有运营中的车辆
     *
     * @return 车辆列表
     */
    private List<XpVehicle> getOperatingVehicles() {
        try {
            R<List<XpVehicle>> result = xpVehicleClient.listVehicle();
            if (result != null && result.getData() != null) {
                // 过滤运营中的车辆 (vehicleType=2 表示运营, operatingStatus=1 表示运营中)
                return result.getData().stream()
                        .filter(v -> v.getVehicleType() != null && v.getVehicleType() == 2)
                        .filter(v -> v.getOperatingStatus() != null && v.getOperatingStatus() == 1)
                        .filter(v -> v.getState() != null && v.getState() == 0)
                        .collect(Collectors.toList());
            }
        } catch (Exception e) {
            log.error("获取车辆列表失败", e);
        }
        return Collections.emptyList();
    }
 
    /**
     * 向车辆发送收车指令
     *
     * @param xpVehicle 车辆信息
     */
    private void sendCollectionCommand(XpVehicle xpVehicle) {
        String vin = SensitiveDataCryptoUtil.decryptIfPossible(xpVehicle.getVehicleVin());
        // 收车 关闭当前巡游单子 生成新的返程单子  找到收车站点
        XpCruiseOrder oldCruiseOrder = xpCruiseOrderClient.cancelCruiseOrderByVin(vin).getData();
 
        // mongodb 获取当前最新经纬度位置
        VehiclePosition vehiclePosition = mongoTemplate.findOne(new Query(Criteria.where("vin").is(vin)), VehiclePosition.class);
        XpCruiseOrder xpCruiseOrder = new XpCruiseOrder();
        if(vehiclePosition!=null){
            double x = vehiclePosition.getPoint().getX();
            double y = vehiclePosition.getPoint().getY();
            xpCruiseOrder.setStartLon(x);
            xpCruiseOrder.setStartLat(y);
        }
 
        xpCruiseOrder.setVehicleId(xpVehicle.getId());
        xpCruiseOrder.setFleetId(xpVehicle.getFleetId());
        xpCruiseOrder.setOrderType(2);
        xpCruiseOrder.setStatus(1);
        xpCruiseOrder.setOrderReverse(1);
        xpCruiseOrder.setLastOrderId(oldCruiseOrder.getId());
        xpCruiseOrder.setOrderReverse(1);
        xpCruiseOrder.setXpuTaskId(snowflake.nextId() + "");
 
        // 找到收车站点
        R<XpCruiseOrder> returnCruisePoint = xpCruisePlanClient.findReturnCruisePoint(xpCruiseOrder);
        if (returnCruisePoint.getData() != null && 200 == returnCruisePoint.getCode()) {
            XpCruiseOrder cruiseOrder = returnCruisePoint.getData();
            //不停车替换新任务
            cruiseOrder.setLastOrderId1("XU" + oldCruiseOrder.getId());
            cruiseOrder.setLastXpuTaskId(oldCruiseOrder.getXpuTaskId());
            cruiseOrder.setTaskActionType(TaskActionType.kTerminateForNewTask);
            xpCruiseOrderClient.addCruiseOrder(cruiseOrder);
        }
        log.info("向车辆[{}]发送收车指令", xpVehicle.getPlateNumber());
    }
}