1563 字
8 分钟
Java分布式唯一订单号生成工具
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@RequiredArgsConstructorpublic 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()
生成默认配置的高安全性券码
调用示例:
@Autowiredprivate UniqueIdUtil uniqueIdUtil;
// 生成默认券码String couponCode = uniqueIdUtil.genCouponCode();返回数据格式:
CP3K7H9M2N4PCP8F5G2J7L9XCPAH6K3M8T5W- 格式说明:前缀 “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();返回数据格式:
250613123456789000012506131234567890000225061312345679000003- 格式说明:时间戳(yyMMddHHmmssSSS)+ 5位序列号
- 时间戳长度:17位
- 序列号长度:5位,从00001开始递增
- 总长度:22位
genSubmitOrderNo()
生成供应商提单号
调用示例:
String submitOrderNo = uniqueIdUtil.genSubmitOrderNo();返回数据格式:
250613123456789000012506131234567890000225061312345679000003- 格式说明:与订单号格式相同,但使用独立的Redis计数器
- 总长度:22位
genExchangeId()
生成兑换记录ID
调用示例:
String exchangeId = uniqueIdUtil.genExchangeId();返回数据格式:
250613123456789000001250613123456789000002250613123456790000003- 格式说明:时间戳(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@RequiredArgsConstructorpublic 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,注入后直接使用。所有方法线程安全,支持高并发调用。