mitao
2025-11-17 75d2873f20f352321b52cae45596f141474882a5
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
package com.stylefeng.guns.core.util;
 
import java.security.SecureRandom;
import java.util.concurrent.atomic.AtomicLong;
 
/**
 * 请求流水号生成工具类
 *
 * <p>用于生成32位唯一请求流水号,格式:[前缀2位][日期8位][时间6位][毫秒3位][序列号13位]</p>
 * <p>示例:rQ202511141540078404299875651</p>
 *
 * <p>特性:</p>
 * <ul>
 *   <li>线程安全:使用AtomicLong保证高并发环境下的唯一性</li>
 *   <li>高性能:避免重复创建对象,优化内存使用</li>
 *   <li>可配置:支持自定义前缀适应不同业务场景</li>
 *   <li>高可靠:时间戳+随机数+序列号三重保障唯一性</li>
 * </ul>
 *
 * @author harley
 * @since 2025-11-14
 */
public class RequestSeqUtil {
 
    /**
     * 默认前缀
     */
    private static final String DEFAULT_PREFIX = "RQ";
 
    /**
     * 序列号生成器,保证线程安全
     */
    private static final AtomicLong SEQUENCE_GENERATOR = new AtomicLong(0);
 
    /**
     * 安全随机数生成器
     */
    private static final SecureRandom SECURE_RANDOM = new SecureRandom();
 
    /**
     * 流水号总长度
     */
    private static final int TOTAL_LENGTH = 32;
 
    /**
     * 前缀长度
     */
    private static final int PREFIX_LENGTH = 2;
 
    /**
     * 日期长度 (yyyyMMdd)
     */
    private static final int DATE_LENGTH = 8;
 
    /**
     * 时间长度 (HHmmss)
     */
    private static final int TIME_LENGTH = 6;
 
    /**
     * 毫秒长度
     */
    private static final int MILLIS_LENGTH = 3;
 
    /**
     * 序列号长度
     */
    private static final int SEQUENCE_LENGTH = 13;
 
    /**
     * 私有构造函数,防止实例化
     */
    private RequestSeqUtil() {
        throw new UnsupportedOperationException("工具类不允许实例化");
    }
 
    /**
     * 生成默认前缀的32位请求流水号
     *
     * @return 32位请求流水号,格式:RQ + 时间信息 + 序列号
     */
    public static String generate() {
        return generate(DEFAULT_PREFIX);
    }
 
    /**
     * 生成指定前缀的32位请求流水号
     *
     * <p>流水号格式:[前缀2位][日期8位][时间6位][毫秒3位][序列号13位]</p>
     * <p>示例:rQ202511141540078404299875651</p>
     *
     * @param prefix 自定义前缀,建议2位字符
     * @return 32位请求流水号
     * @throws IllegalArgumentException 当前缀为空或长度不符合要求时抛出
     */
    public static String generate(String prefix) {
        // 参数校验
        if (prefix == null || prefix.isEmpty()) {
            throw new IllegalArgumentException("前缀不能为空");
        }
 
        if (prefix.length() != PREFIX_LENGTH) {
            throw new IllegalArgumentException("前缀长度必须为" + PREFIX_LENGTH + "位");
        }
 
        long currentTime = System.currentTimeMillis();
 
        // 构建流水号各个部分
        String datePart = DateUtil.format(new java.util.Date(currentTime), "yyyyMMdd");
        String timePart = DateUtil.format(new java.util.Date(currentTime), "HHmmss");
        String millisPart = String.format("%03d", currentTime % 1000);
 
        // 生成序列号(时间戳后7位 + 随机数6位)
        long sequence = SEQUENCE_GENERATOR.incrementAndGet();
        String sequencePart = String.format("%013d", Math.abs(sequence % 1000000000000L));
 
        // 组装完整的流水号
        StringBuilder seqBuilder = new StringBuilder(TOTAL_LENGTH);
        seqBuilder.append(prefix)
                  .append(datePart)
                  .append(timePart)
                  .append(millisPart)
                  .append(sequencePart);
 
        return seqBuilder.toString();
    }
 
    /**
     * 生成带业务标识的流水号
     *
     * <p>常用的业务前缀:</p>
     * <ul>
     *   <li>rQ - 汇付请求</li>
     *   <li>OR - 订单业务</li>
     *   <li>PY - 支付业务</li>
     *   <li>RF - 退款业务</li>
     * </ul>
     *
     * @param businessCode 业务代码前缀
     * @return 32位业务流水号
     */
    public static String generateForBusiness(String businessCode) {
        return generate(businessCode);
    }
 
    /**
     * 生成汇付相关的请求流水号
     *
     * @return 以"rQ"为前缀的32位流水号
     */
    public static String generateForHuiFu() {
        return generate("rQ");
    }
 
    /**
     * 验证流水号格式是否正确
     *
     * @param sequence 要验证的流水号
     * @return 验证结果
     */
    public static boolean isValid(String sequence) {
        if (sequence == null || sequence.length() != TOTAL_LENGTH) {
            return false;
        }
 
        // 验证时间部分格式(第3-16位应该是数字)
        String timePart = sequence.substring(2, 16);
        if (!timePart.matches("\\d+")) {
            return false;
        }
 
        // 验证最后13位序列号是否为数字
        String sequencePart = sequence.substring(19);
        if (!sequencePart.matches("\\d+")) {
            return false;
        }
 
        return true;
    }
 
    /**
     * 从流水号中提取前缀
     *
     * @param sequence 流水号
     * @return 流水号前缀
     */
    public static String extractPrefix(String sequence) {
        if (sequence == null || sequence.length() < PREFIX_LENGTH) {
            return null;
        }
        return sequence.substring(0, PREFIX_LENGTH);
    }
 
    /**
     * 从流水号中提取时间信息
     *
     * @param sequence 流水号
     * @return 格式化的时间字符串,格式:yyyy-MM-dd HH:mm:ss.SSS
     */
    public static String extractDateTime(String sequence) {
        if (!isValid(sequence)) {
            return null;
        }
 
        String dateStr = sequence.substring(2, 10);  // yyyyMMdd
        String timeStr = sequence.substring(10, 16); // HHmmss
        String millisStr = sequence.substring(16, 19); // 毫秒
 
        return dateStr.substring(0, 4) + "-" +
               dateStr.substring(4, 6) + "-" +
               dateStr.substring(6, 8) + " " +
               timeStr.substring(0, 2) + ":" +
               timeStr.substring(2, 4) + ":" +
               timeStr.substring(4, 6) + "." +
               millisStr;
    }
}