Java分布式唯一订单号生成工具
📋 概述
UniqueIdUtil 是一个基于 Redis 和 Spring Boot 的分布式唯一ID生成工具类,支持分布式环境生成不重复的各类业务ID。
✨ 核心特性
- 🔒 全局唯一性 - Redis 原子递增保证多节点环境下的绝对唯一
- 🛡️ 高安全性 - 时间戳 + 随机因子 + 盐值多重组合,防止碰撞和预测
- ⚡ 高性能 - 基于 Redisson 客户端,支持高并发场景
- 🎯 业务友好 - 支持多种业务场景的ID生成需求
- 🔧 易于配置 - 所有参数均为常量配置,便于维护
🚀 完整实现
Maven 依赖配置
<!-- Spring Boot Redis Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Hutool 工具库 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.38</version>
</dependency>
完整源码实现
import cn.hutool.core.date.DateUtil;
import cn.hutool.crypto.digest.DigestUtil;
import lombok.RequiredArgsConstructor;
import org.redisson.api.RAtomicLong;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;
import java.security.SecureRandom;
import java.util.Date;
import java.util.concurrent.TimeUnit;
/**
* 通用唯一号 / 券码生成工具(单类实现)
*
* <p>特点:
* <ul>
* <li>Redis 原子递增保证多节点全局唯一</li>
* <li>时间戳 + 随机因子 + 盐值,多重组合确保安全</li>
* <li>所有可调参数均写死为常量,满足“配置写常量”要求</li>
* <li>如需替换 Redis,可自行改写 {@link #nextSequence}</li>
* </ul>
*/
@Component
@RequiredArgsConstructor
public class UniqueIdUtil {
/* ========================== 可调常量区域 ========================== */
/** 券码盐值 */
private static final String COUPON_SALT = "default_coupon_salt_2025";
/** 券码前缀(如无可设为空) */
private static final String COUPON_PREFIX = "CP";
/** 券码主体长度(不含前缀) */
private static final int COUPON_BODY_LENGTH = 12;
/** 序列号 key 在 Redis 中的失效时间(秒) */
private static final long SEQ_EXPIRE_SECONDS = 5L;
/** 默认日期格式:yyMMddHHmmssSSS */
private static final String DATE_PATTERN = "yyMMddHHmmssSSS";
/** 安全字符集(去除 I、O、0、1) */
private static final String SAFE_CHARS = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
/* ================================================================= */
private static final SecureRandom RANDOM = new SecureRandom();
private final RedissonClient redissonClient;
/* ============================ 券码 ============================ */
/**
* 生成高安全性券码(使用默认前缀/长度)
*/
public String genCouponCode() {
return genCouponCode(COUPON_PREFIX, COUPON_BODY_LENGTH);
}
/**
* 生成高安全性券码(自定义前缀与长度)
*
* @param prefix 前缀,可传空串
* @param bodyLen 券码主体长度
*/
public String genCouponCode(String prefix, int bodyLen) {
long seq = nextSequence("coupon:seq");
// 构造哈希材料:时间戳 + 序列 + 随机因子 + 当前日期 + 盐值
String material = String.format("%d:%d:%d:%s:%s",
System.nanoTime(),
seq,
RANDOM.nextInt(1_000_000),
DateUtil.now(),
COUPON_SALT);
String hash = DigestUtil.sha256Hex(material);
// 依次取安全字符
StringBuilder sb = new StringBuilder(bodyLen);
for (int i = 0; sb.length() < bodyLen && i < hash.length(); i++) {
int idx = Character.digit(hash.charAt(i), 16); // 0‑15
sb.append(SAFE_CHARS.charAt(idx % SAFE_CHARS.length()));
}
// 长度不足时补随机字符
while (sb.length() < bodyLen) {
sb.append(SAFE_CHARS.charAt(RANDOM.nextInt(SAFE_CHARS.length())));
}
return prefix + sb;
}
/* ======================== 订单 / 各类 ID ======================== */
/** 订单号:yyMMddHHmmssSSS + 5 位序列 */
public String genOrderNo() {
return genTimestampSeq("order:seq", 5);
}
/** 供应商提单号:yyMMddHHmmssSSS + 5 位序列 */
public String genSubmitOrderNo() {
return genTimestampSeq("submit:seq", 5);
}
/** 兑换记录 ID:yyMMddHHmmssSSS + 6 位序列 */
public String genExchangeId() {
return genTimestampSeq("exchange:seq", 6);
}
/**
* 通用 ID:业务方可自定义 key,序列位数 6
*
* @param bizKey 业务标识,如 "INVOICE"
*/
public String genCommonId(String bizKey) {
return genTimestampSeq("common:" + bizKey, 6);
}
/** 退款单号:在原单号尾部追加 'R'(可自定义) */
public String genRefundNo(String originOrderNo) {
return originOrderNo + 'R';
}
/* ============================ 内部实现 ============================ */
/**
* 时间戳 + 自增序列号
*
* @param redisKey Redis 计数器 key
* @param seqDigits 序列号长度(位数)
*/
private String genTimestampSeq(String redisKey, int seqDigits) {
long seq = nextSequence(redisKey);
String dateStr = DateUtil.format(new Date(), DATE_PATTERN);
String seqStr = String.format("%0" + seqDigits + "d", seq);
return dateStr + seqStr;
}
/**
* 获取分布式递增序列
*/
private long nextSequence(String key) {
RAtomicLong counter = redissonClient.getAtomicLong(key);
long val = counter.incrementAndGet();
// 仅在第一次调用时设置过期,避免每次调用重复发送 EXPIRE 指令
if (counter.remainTimeToLive() < 0) {
counter.expire(SEQ_EXPIRE_SECONDS, TimeUnit.SECONDS);
}
return val;
}
}
📝 API 方法详解
1. 券码生成方法
genCouponCode()
生成默认配置的高安全性券码
调用示例:
@Autowired
private UniqueIdUtil uniqueIdUtil;
// 生成默认券码
String couponCode = uniqueIdUtil.genCouponCode();
返回数据格式:
CP3K7H9M2N4P
CP8F5G2J7L9X
CPAH6K3M8T5W
- 格式说明:前缀 "CP" + 12位安全字符
- 字符集:
23456789ABCDEFGHJKLMNPQRSTUVWXYZ(排除易混淆字符) - 总长度:14位(前缀2位 + 主体12位)
genCouponCode(String prefix, int bodyLen)
生成自定义前缀和长度的券码
调用示例:
// 自定义前缀和长度
String vipCode = uniqueIdUtil.genCouponCode("VIP", 8);
String noPrefix = uniqueIdUtil.genCouponCode("", 16);
String longCode = uniqueIdUtil.genCouponCode("PREMIUM", 20);
返回数据格式:
// VIP + 8位主体
"VIP3K7H9M2N"
// 无前缀 + 16位主体
"3K7H9M2N4P8F5G2J"
// PREMIUM + 20位主体
"PREMIUM3K7H9M2N4P8F5G2J7L9X"
2. 订单相关ID生成
genOrderNo()
生成订单号
调用示例:
String orderNo = uniqueIdUtil.genOrderNo();
返回数据格式:
25061312345678900001
25061312345678900002
25061312345679000003
- 格式说明:时间戳(yyMMddHHmmssSSS)+ 5位序列号
- 时间戳长度:17位
- 序列号长度:5位,从00001开始递增
- 总长度:22位
genSubmitOrderNo()
生成供应商提单号
调用示例:
String submitOrderNo = uniqueIdUtil.genSubmitOrderNo();
返回数据格式:
25061312345678900001
25061312345678900002
25061312345679000003
- 格式说明:与订单号格式相同,但使用独立的Redis计数器
- 总长度:22位
genExchangeId()
生成兑换记录ID
调用示例:
String exchangeId = uniqueIdUtil.genExchangeId();
返回数据格式:
250613123456789000001
250613123456789000002
250613123456790000003
- 格式说明:时间戳(yyMMddHHmmssSSS)+ 6位序列号
- 时间戳长度:17位
- 序列号长度:6位,从000001开始递增
- 总长度:23位
3. 通用业务ID生成
genCommonId(String bizKey)
生成通用业务ID
调用示例:
String invoiceId = uniqueIdUtil.genCommonId("INVOICE");
String contractId = uniqueIdUtil.genCommonId("CONTRACT");
String taskId = uniqueIdUtil.genCommonId("TASK");
返回数据格式:
// 发票ID
"250613123456789000001"
// 合同ID
"250613123456789000001"
// 任务ID
"250613123456789000001"
- 格式说明:时间戳(yyMMddHHmmssSSS)+ 6位序列号
- 序列号独立:每个 bizKey 使用独立的Redis计数器
- 总长度:23位
genRefundNo(String originOrderNo)
生成退款单号
调用示例:
String orderNo = uniqueIdUtil.genOrderNo();
String refundNo = uniqueIdUtil.genRefundNo(orderNo);
返回数据格式:
// 原订单号
String orderNo = "25061312345678900001";
// 退款单号
String refundNo = "25061312345678900001R";
- 格式说明:原订单号 + 后缀 "R"
- 长度:原订单号长度 + 1位
🚀 完整使用示例
@RestController
@RequiredArgsConstructor
public class IdGeneratorController {
private final UniqueIdUtil uniqueIdUtil;
@GetMapping("/generate-ids")
public Map<String, String> generateIds() {
Map<String, String> result = new HashMap<>();
// 券码生成
result.put("defaultCoupon", uniqueIdUtil.genCouponCode());
result.put("vipCoupon", uniqueIdUtil.genCouponCode("VIP", 10));
result.put("noPrefixCoupon", uniqueIdUtil.genCouponCode("", 8));
// 订单相关
String orderNo = uniqueIdUtil.genOrderNo();
result.put("orderNo", orderNo);
result.put("submitOrderNo", uniqueIdUtil.genSubmitOrderNo());
result.put("refundNo", uniqueIdUtil.genRefundNo(orderNo));
// 其他业务ID
result.put("exchangeId", uniqueIdUtil.genExchangeId());
result.put("invoiceId", uniqueIdUtil.genCommonId("INVOICE"));
result.put("contractId", uniqueIdUtil.genCommonId("CONTRACT"));
return result;
}
}
返回结果示例:
{
"defaultCoupon": "CP3K7H9M2N4P",
"vipCoupon": "VIP8F5G2J7L9X",
"noPrefixCoupon": "AH6K3M8T",
"orderNo": "25061312345678900001",
"submitOrderNo": "25061312345678900001",
"refundNo": "25061312345678900001R",
"exchangeId": "250613123456789000001",
"invoiceId": "250613123456789000001",
"contractId": "250613123456789000001"
}
🔧 配置参数说明
| 参数 | 默认值 | 说明 |
|---|---|---|
COUPON_SALT | "default_coupon_salt_2025" | 券码生成盐值 |
COUPON_PREFIX | "CP" | 默认券码前缀 |
COUPON_BODY_LENGTH | 12 | 默认券码主体长度 |
SEQ_EXPIRE_SECONDS | 5L | Redis序列号过期时间 |
DATE_PATTERN | "yyMMddHHmmssSSS" | 时间戳格式 |
SAFE_CHARS | "23456789ABCDEFGHJKLMNPQRSTUVWXYZ" | 安全字符集 |
💡 提示: 本工具类为Spring Bean,注入后直接使用。所有方法线程安全,支持高并发调用。