MrShi
8 小时以前 9eeb62c02a7b3c7b95c20678b6a9c74e7f12f943
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
package com.doumee.core.track;
 
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
 
import java.util.concurrent.TimeUnit;
 
/**
 * 电车「车辆 → 活跃骑行订单」Redis 缓存,收口读写删,供开锁 / 还车 / 位置上报三处统一调用。
 * <p>用途:位置上报(JT/T 808,秒级高频)时,用 bikeCode O(1) 取活跃订单,
 * 替代每次查 member_rides,避免高频 DB 查询。
 * <p>生命周期:
 * <ul>
 *   <li>电车开锁成功(→ 骑行中):{@link #set} 写入,带 24h TTL</li>
 *   <li>电车还车成功(→ 已还车):{@link #remove} 删除(覆盖 backBike / autoBackBike / forceBack 多入口)</li>
 *   <li>临停(→ 临停中):不删,期间照常写轨迹</li>
 * </ul>
 * <p>TTL 仅为兜底:正常还车会主动删;防还车异常分支漏删导致映射泄漏 → 已还车的车继续被当活跃写脏轨迹。
 * <p>降级:本类方法不吞异常,由调用方 try-catch 降级(缓存是优化、非业务正确性来源,失败不得阻断主流程)。
 * <p>value 以 JSON 字符串存取(与抖音 token 缓存一致,避开 RedisTemplate 序列化器对 POJO 的依赖)。
 *
 * @author rk
 * @date 2026/06/25
 */
@Slf4j
@Component
public class RideActiveCache {
 
    /** Redis key 前缀:车辆维度活跃订单映射 */
    private static final String KEY_PREFIX = "ride:active:";
 
    /** 缓存 TTL(秒):24 小时,覆盖最长骑行;兜底防还车漏删导致映射泄漏 */
    private static final long TTL_SECONDS = 24L * 60L * 60L;
 
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
 
    /** 拼 Redis key:ride:active:{bikeCode} */
    private static String key(String bikeCode) {
        return KEY_PREFIX + bikeCode;
    }
 
    /**
     * 开锁成功时写入活跃订单映射(带 24h TTL)。
     *
     * @param bikeCode 车辆编码(缓存维度)
     * @param ridesId  骑行订单主键 member_rides.id
     * @param orderId  支付订单主键 member_rides.ordre_id → goodsorder.id(可 null)
     */
    public void set(String bikeCode, String ridesId, String orderId) {
        if (StringUtils.isBlank(bikeCode)) {
            return;
        }
        RideActiveInfo info = new RideActiveInfo();
        info.setRidesId(ridesId);
        info.setOrderId(orderId);
        redisTemplate.opsForValue().set(key(bikeCode), JSON.toJSONString(info), TTL_SECONDS, TimeUnit.SECONDS);
    }
 
    /**
     * 位置上报时读取活跃订单映射。
     *
     * @param bikeCode 车辆编码
     * @return 活跃订单载荷;无映射 / 非 String 值返回 null(调用方据此跳过轨迹写入)
     */
    public RideActiveInfo get(String bikeCode) {
        if (StringUtils.isBlank(bikeCode)) {
            return null;
        }
        Object cached = redisTemplate.opsForValue().get(key(bikeCode));
        if (cached instanceof String) {
            return JSON.parseObject((String) cached, RideActiveInfo.class);
        }
        return null;
    }
 
    /**
     * 还车成功时删除活跃订单映射。
     *
     * @param bikeCode 车辆编码
     */
    public void remove(String bikeCode) {
        if (StringUtils.isBlank(bikeCode)) {
            return;
        }
        redisTemplate.delete(key(bikeCode));
    }
}