Java分布式唯一订单号生成工具

📋 概述

UniqueIdUtil 是一个基于 RedisSpring 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_LENGTH12默认券码主体长度
SEQ_EXPIRE_SECONDS5LRedis序列号过期时间
DATE_PATTERN"yyMMddHHmmssSSS"时间戳格式
SAFE_CHARS"23456789ABCDEFGHJKLMNPQRSTUVWXYZ"安全字符集

💡 提示: 本工具类为Spring Bean,注入后直接使用。所有方法线程安全,支持高并发调用。