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
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
package com.xiaopeng.operation.utils;
 
import com.xiaopeng.api.feignClient.vehicleConvoy.XpVehicleClient;
import com.xiaopeng.api.model.operation.XpElectronicFenceArea;
import com.xiaopeng.api.model.vehicleConvoy.XpVehicle;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.operation.utils.map.GeodesyUtil;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
 
import java.util.*;
import java.util.concurrent.TimeUnit;
 
/**
 * 基于RedisTemplate的排队系统工具类
 * 实现流程图中的排队校验逻辑
 */
public class QueueManager {
 
 
 
    
    private final RedisTemplate<String, Object> redisTemplate;
    private final XpVehicleClient xpVehicleClient;
    private final GeodesyUtil geodesyUtil;
    // Redis Key前缀定义
    private static final String QUEUE_PREFIX = "queue:";
    private static final String ORDER_PREFIX = "order:";
    private static final String WAITING_USERS_PREFIX = "waiting_users:";
    private static final String FREE_VEHICLES_PREFIX = "free_vehicles:";
    private static final String USER_QUEUE_POSITION_PREFIX = "user_queue:";
    private static final String TIMER_PREFIX = "timer:";
    
    // 配置参数
    private static final int DEFAULT_TIMEOUT_MINUTES = 30;
    private static final int DEFAULT_CANCEL_TIMEOUT_SECONDS = 1800; // 30分钟
    
    // 获取不同类型的操作对象
    private ValueOperations<String, Object> valueOps;
    private SetOperations<String, Object> setOps;
    private ZSetOperations<String, Object> zSetOps;
    private HashOperations<String, String, Object> hashOps;
    
    public QueueManager(RedisTemplate<String, Object> redisTemplate, XpVehicleClient xpVehicleClient, GeodesyUtil geodesyUtil) {
        this.redisTemplate = redisTemplate;
        this.valueOps = redisTemplate.opsForValue();
        this.setOps = redisTemplate.opsForSet();
        this.zSetOps = redisTemplate.opsForZSet();
        this.hashOps = redisTemplate.opsForHash();
        this.xpVehicleClient = xpVehicleClient;
        this.geodesyUtil = geodesyUtil;
    }
    
    /**
     * 初始化排队校验流程
     * @param areaId 区域ID
     * @param userId 用户ID
     * @param orderId 订单ID
     * @return 排队结果对象
     */
    public QueueResult startQueueProcess(double lon,double lat,double distance,String areaId, String userId, String orderId,Long startPointId) {
        // 步骤1: 计算范围内空闲车辆数
        int freeVehiclesCount = countFreeVehicles(areaId,lon,lat,distance);
 
        if(freeVehiclesCount==0){
            // 进入排队
            generateQueueNumber(startPointId.toString(), userId, orderId);
 
 
        }
        
        // 步骤2: 计算范围内呼叫人数
        int waitingUsersCount = countWaitingUsers(areaId);
        
        // 步骤3: 排队校验判断
        boolean needQueue = waitingUsersCount > freeVehiclesCount;
        
        QueueResult result = new QueueResult();
        result.setNeedQueue(needQueue);
        result.setQueuePosition(0);
        result.setOrderId(orderId);
        result.setUserId(userId);
        
        if (needQueue) {
            // 需要排队 - 不设置超时自动取消
            // 步骤4: 生成排队序号
            int queueNumber = generateQueueNumber(areaId, userId, orderId);
            result.setQueuePosition(queueNumber);
            
            // 步骤5: 队列管理,排序更新
            updateQueuePosition(areaId, userId, queueNumber);
            
            // 保存订单信息(不设置超时)
            saveOrderInfo(orderId, userId, areaId, queueNumber, -1);
        } else {
            // 不需要排队 - 普通倒计时30分钟
            // 保存订单信息(设置30分钟超时)
            saveOrderInfo(orderId, userId, areaId, 0, DEFAULT_CANCEL_TIMEOUT_SECONDS);
            
            // 启动30分钟倒计时
            startCountdownTimer(orderId, DEFAULT_TIMEOUT_MINUTES);
        }
        
        return result;
    }
    
    /**
     * 计算范围内空闲车辆数
     */
    private int countFreeVehicles(String areaId,double lon,double lat,double distance) {
        int count=0;
        // 获取电子围栏的车辆
        R<List<XpVehicle>> vehicles = xpVehicleClient.listByFenceId(Long.valueOf(areaId));
        List<XpVehicle> data = vehicles.getData();
        for(XpVehicle xpVehicle : data){
            Object value = redisTemplate.opsForValue().get("DRIVER" + String.valueOf(xpVehicle.getId()));
            if(null != value){
                Map<String, Double> distance1 = geodesyUtil.getDistance(lon + "," + lat, value.toString());
                double d = Double.valueOf(distance1.get("WGS84")).doubleValue();
                if(d < (distance * 1000)){
                    count++;
                }
            }
        }
        return count;
    }
    
    /**
     * 计算范围内呼叫人数
     */
    private int countWaitingUsers(String areaId) {
        String key = WAITING_USERS_PREFIX + areaId;
        Long size = zSetOps.size(key);
        return size != null ? size.intValue() : 0;
    }
    
    /**
     * 生成排队序号
     */
    private int generateQueueNumber(String areaId, String userId, String orderId) {
        String queueKey = QUEUE_PREFIX + areaId;
 
        // 判断是否白名单  // 重新排序
 
        // 使用Redis原子操作获取下一个排队序号
        // 计算分数
        Long queueNumber = valueOps.increment(queueKey);
        
        // 将用户加入等待队列,分数为排队序号
        String waitingKey = WAITING_USERS_PREFIX + areaId;
        zSetOps.add(waitingKey, userId, queueNumber.doubleValue());
        
        // 存储用户排队位置映射
        String userQueueKey = USER_QUEUE_POSITION_PREFIX + userId;
        Map<String, Object> userQueueInfo = new HashMap<>();
        userQueueInfo.put("areaId", areaId);
        userQueueInfo.put("orderId", orderId);
        userQueueInfo.put("queueNumber", String.valueOf(queueNumber));
        userQueueInfo.put("joinTime", String.valueOf(System.currentTimeMillis()));
        hashOps.putAll(userQueueKey, userQueueInfo);
        
        return queueNumber.intValue();
    }
    
    /**
     * 更新队列排序
     */
    private void updateQueuePosition(String areaId, String userId, int queueNumber) {
        // 这里可以实现更复杂的队列排序逻辑
        // 例如:根据优先级、VIP等级等重新排序
        
        // 当前实现:按加入顺序排序,queueNumber即为位置
        // 可以扩展为使用zset的分数进行动态排序
    }
    
    /**
     * 保存订单信息
     */
    private void saveOrderInfo(String orderId, String userId, 
                              String areaId, int queueNumber, long timeoutSeconds) {
        String orderKey = ORDER_PREFIX + orderId;
        
        Map<String, Object> orderInfo = new HashMap<>();
        orderInfo.put("userId", userId);
        orderInfo.put("areaId", areaId);
        orderInfo.put("queueNumber", String.valueOf(queueNumber));
        orderInfo.put("status", queueNumber > 0 ? "waiting" : "processing");
        orderInfo.put("createTime", String.valueOf(System.currentTimeMillis()));
        
        hashOps.putAll(orderKey, orderInfo);
        
        // 设置过期时间(如果需要)
        if (timeoutSeconds > 0) {
            redisTemplate.expire(orderKey, timeoutSeconds, TimeUnit.SECONDS);
        }
    }
    
    /**
     * 启动倒计时定时器
     */
    private void startCountdownTimer(String orderId, int minutes) {
        // 设置一个过期key作为倒计时标记
        String timerKey = TIMER_PREFIX + orderId;
        valueOps.set(timerKey, "countdown", minutes, TimeUnit.MINUTES);
    }
    
    /**
     * 用户取消订单
     */
    public boolean cancelOrder(String orderId, String userId) {
        String orderKey = ORDER_PREFIX + orderId;
        
        // 检查订单是否存在且属于该用户
        Object storedUserId = hashOps.get(orderKey, "userId");
        if (storedUserId == null || !storedUserId.equals(userId)) {
            return false;
        }
        
        // 获取订单信息
        Object areaIdObj = hashOps.get(orderKey, "areaId");
        Object queueNumberObj = hashOps.get(orderKey, "queueNumber");
        
        // 更新订单状态为取消
        hashOps.put(orderKey, "status", "cancelled");
        hashOps.put(orderKey, "cancelTime", String.valueOf(System.currentTimeMillis()));
        
        // 如果用户在排队中,从等待队列移除
        if (areaIdObj != null && queueNumberObj != null) {
            String areaId = (String) areaIdObj;
            int queueNumber = Integer.parseInt((String) queueNumberObj);
            
            if (queueNumber > 0) {
                String waitingKey = WAITING_USERS_PREFIX + areaId;
                zSetOps.remove(waitingKey, userId);
                
                // 清理用户排队信息
                String userQueueKey = USER_QUEUE_POSITION_PREFIX + userId;
                redisTemplate.delete(userQueueKey);
            }
        }
        
        // 清理定时器
        redisTemplate.delete(TIMER_PREFIX + orderId);
        
        return true;
    }
    
    /**
     * 获取用户排队位置
     */
    public int getQueuePosition(String areaId, String userId) {
        String waitingKey = WAITING_USERS_PREFIX + areaId;
        Long rank = zSetOps.rank(waitingKey, userId);
        
        if (rank != null) {
            // zrank返回的是从0开始的索引,排队位置需要+1
            return rank.intValue() + 1;
        }
        return -1; // 不在队列中
    }
    
    /**
     * 车辆空闲时,分配订单给排队的用户
     */
    public String allocateOrderToWaitingUser(String areaId, String vehicleId) {
        String waitingKey = WAITING_USERS_PREFIX + areaId;
        
        // 获取队列中的第一个用户(分数最小的)
        Set<Object> firstUsers = zSetOps.range(waitingKey, 0, 0);
        if (firstUsers == null || firstUsers.isEmpty()) {
            return null;
        }
        
        String userId = (String) firstUsers.iterator().next();
        
        // 使用Lua脚本保证原子性操作
        String luaScript = 
            "local waitingKey = KEYS[1]\n" +
            "local userId = ARGV[1]\n" +
            "local vehicleId = ARGV[2]\n" +
            "local userQueueKey = 'user_queue:' .. userId\n" +
            "local orderId = redis.call('hget', userQueueKey, 'orderId')\n" +
            "\n" +
            "if orderId then\n" +
            "    redis.call('zrem', waitingKey, userId)\n" +
            "    \n" +
            "    local orderKey = 'order:' .. orderId\n" +
            "    redis.call('hset', orderKey, 'status', 'allocated')\n" +
            "    redis.call('hset', orderKey, 'vehicleId', vehicleId)\n" +
            "    redis.call('hset', orderKey, 'allocateTime', ARGV[3])\n" +
            "    \n" +
            "    redis.call('del', userQueueKey)\n" +
            "end\n" +
            "\n" +
            "return orderId or ''";
        
        DefaultRedisScript<String> script = new DefaultRedisScript<>();
        script.setScriptText(luaScript);
        script.setResultType(String.class);
        
        List<String> keys = Collections.singletonList(waitingKey);
        Object[] args = {userId, vehicleId, String.valueOf(System.currentTimeMillis())};
        
        String orderId = redisTemplate.execute(script, keys, args);
        
        return orderId != null && !orderId.isEmpty() ? orderId : null;
    }
    
    /**
     * 添加空闲车辆
     */
    public void addFreeVehicle(String areaId, String vehicleId) {
        String key = FREE_VEHICLES_PREFIX + areaId;
        setOps.add(key, vehicleId);
    }
    
    /**
     * 移除空闲车辆
     */
    public void removeFreeVehicle(String areaId, String vehicleId) {
        String key = FREE_VEHICLES_PREFIX + areaId;
        setOps.remove(key, vehicleId);
    }
    
    /**
     * 获取等待队列详情
     */
    public List<Map<String, Object>> getWaitingQueueDetails(String areaId) {
        String waitingKey = WAITING_USERS_PREFIX + areaId;
        
        // 获取所有等待用户及其分数(排队序号)
        Set<ZSetOperations.TypedTuple<Object>> waitingUsers = 
            zSetOps.rangeWithScores(waitingKey, 0, -1);
        
        if (waitingUsers == null || waitingUsers.isEmpty()) {
            return Collections.emptyList();
        }
        
        List<Map<String, Object>> result = new ArrayList<>();
        int position = 1;
        
        for (ZSetOperations.TypedTuple<Object> tuple : waitingUsers) {
            String userId = (String) tuple.getValue();
            Double score = tuple.getScore();
            
            Map<String, Object> userInfo = new HashMap<>();
            userInfo.put("userId", userId);
            userInfo.put("queuePosition", position);
            userInfo.put("queueNumber", score != null ? score.intValue() : 0);
            
            // 获取用户详细信息
            String userQueueKey = USER_QUEUE_POSITION_PREFIX + userId;
            Map<String, Object> queueInfo = hashOps.entries(userQueueKey);
            if (queueInfo != null) {
                userInfo.putAll(queueInfo);
            }
            
            result.add(userInfo);
            position++;
        }
        
        return result;
    }
    
    /**
     * 获取订单状态
     */
    public Map<String, Object> getOrderStatus(String orderId) {
        String orderKey = ORDER_PREFIX + orderId;
        Map<String, Object> orderInfo = hashOps.entries(orderKey);
        
        if (orderInfo != null && !orderInfo.isEmpty()) {
            // 如果订单在排队中,计算当前排队位置
            Object queueNumberObj = orderInfo.get("queueNumber");
            Object areaIdObj = orderInfo.get("areaId");
            Object userIdObj = orderInfo.get("userId");
            
            if (queueNumberObj != null && areaIdObj != null && userIdObj != null) {
                int queueNumber = Integer.parseInt((String) queueNumberObj);
                if (queueNumber > 0) {
                    String areaId = (String) areaIdObj;
                    String userId = (String) userIdObj;
                    int currentPosition = getQueuePosition(areaId, userId);
                    orderInfo.put("currentQueuePosition", currentPosition);
                }
            }
        }
        
        return orderInfo;
    }
    
    /**
     * 清理过期数据(后台定时任务调用)
     */
    public void cleanupExpiredData() {
        // 清理超时未处理的订单(30分钟倒计时)
        // 这里可以使用Redis的keys扫描或设置定时任务
    }
    
    /**
     * 批量获取排队信息
     */
    public List<QueueResult> batchGetQueueInfo(List<String> orderIds) {
        List<QueueResult> results = new ArrayList<>();
        
        redisTemplate.executePipelined((RedisCallback<Object>) connection -> {
            for (String orderId : orderIds) {
                String orderKey = ORDER_PREFIX + orderId;
                
                Map<byte[], byte[]> orderBytes = connection.hGetAll(orderKey.getBytes());
                if (orderBytes != null && !orderBytes.isEmpty()) {
                    Map<String, String> orderInfo = new HashMap<>();
                    for (Map.Entry<byte[], byte[]> entry : orderBytes.entrySet()) {
                        orderInfo.put(
                            new String(entry.getKey()),
                            new String(entry.getValue())
                        );
                    }
                    
                    QueueResult result = new QueueResult();
                    result.setOrderId(orderId);
                    result.setUserId(orderInfo.get("userId"));
                    result.setQueuePosition(Integer.parseInt(orderInfo.getOrDefault("queueNumber", "0")));
                    result.setNeedQueue(result.getQueuePosition() > 0);
                    
                    results.add(result);
                }
            }
            return null;
        });
        
        return results;
    }
    
    /**
     * 排队结果对象
     */
    public static class QueueResult {
        private boolean needQueue;
        private int queuePosition;
        private String orderId;
        private String userId;
        private long createTime;
        private String status;
        
        // getters and setters
        public boolean isNeedQueue() {
            return needQueue;
        }
        
        public void setNeedQueue(boolean needQueue) {
            this.needQueue = needQueue;
        }
        
        public int getQueuePosition() {
            return queuePosition;
        }
        
        public void setQueuePosition(int queuePosition) {
            this.queuePosition = queuePosition;
        }
        
        public String getOrderId() {
            return orderId;
        }
        
        public void setOrderId(String orderId) {
            this.orderId = orderId;
        }
        
        public String getUserId() {
            return userId;
        }
        
        public void setUserId(String userId) {
            this.userId = userId;
        }
        
        public long getCreateTime() {
            return createTime;
        }
        
        public void setCreateTime(long createTime) {
            this.createTime = createTime;
        }
        
        public String getStatus() {
            return status;
        }
        
        public void setStatus(String status) {
            this.status = status;
        }
        
        @Override
        public String toString() {
            return "QueueResult{" +
                    "needQueue=" + needQueue +
                    ", queuePosition=" + queuePosition +
                    ", orderId='" + orderId + '\'' +
                    ", userId='" + userId + '\'' +
                    ", createTime=" + createTime +
                    ", status='" + status + '\'' +
                    '}';
        }
    }
}