当前位置: 首页 > news >正文

【JAVA】业务系统订单号,流水号生成规则工具类

设计业务系统订单号,流水号注意事项

  1. 唯一性:确保在分布式环境下ID不重复

  2. 有序性:ID随时间递增,有利于数据库索引性能

  3. 可读性:包含时间信息,便于人工识别

  4. 扩展性:支持业务前缀和类型区分

  5. 性能:本地生成,无网络开销

  6. 高并发:考虑线程安全,避免阻塞

根据实际业务需求,可以调整各部分位数分配,例如增加机器ID位数或减少序列号位数。

下面是一个完整的订单号和流水号生成工具类实现,考虑了高并发、分布式环境下的唯一性需求。

订单号/流水号生成工具类

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicLong;

/**
 * 业务系统订单号/流水号生成工具
 */
public class SerialNumberGenerator {

    // 日期时间格式化器(线程安全)
    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
    
    // 自增序列(单机环境下使用)
    private static final AtomicLong SEQUENCE = new AtomicLong(0);
    
    // 机器ID(分布式环境下使用)
    private static final String MACHINE_ID = initMachineId();
    
    /**
     * 生成订单号(格式: ORDER + 时间 + 机器ID + 序列号)
     * 示例: ORDER2024051215230100110001
     */
    public static String generateOrderNo() {
        return generateSerialNumber("ORDER");
    }
    
    /**
     * 生成交易流水号(格式: TRANS + 时间 + 机器ID + 序列号)
     * 示例: TRANS2024051215230100110001
     */
    public static String generateTransNo() {
        return generateSerialNumber("TRANS");
    }
    
    /**
     * 通用序列号生成方法
     * @param prefix 前缀(如ORDER, TRANS等)
     */
    private static String generateSerialNumber(String prefix) {
        StringBuilder sb = new StringBuilder(prefix);
        
        // 1. 添加时间部分(14位)
        sb.append(DATE_TIME_FORMATTER.format(LocalDateTime.now()));
        
        // 2. 添加机器ID(4位)
        sb.append(MACHINE_ID);
        
        // 3. 添加序列号(6位)
        sb.append(String.format("%06d", SEQUENCE.incrementAndGet() % 999999));
        
        // 4. 添加随机后缀(2位)防止猜测
        sb.append(String.format("%02d", ThreadLocalRandom.current().nextInt(0, 99)));
        
        return sb.toString();
    }
    
    /**
     * 初始化机器ID(实际项目中可以从配置中心获取)
     */
    private static String initMachineId() {
        // 这里简单演示,实际项目中可以从配置文件或配置中心获取
        String machineId = System.getProperty("machine.id", "0001");
        return String.format("%04d", Integer.parseInt(machineId));
    }
    
    /**
     * 带业务类型的订单号生成
     * @param bizType 业务类型(2位字母)
     */
    public static String generateBizOrderNo(String bizType) {
        if(bizType == null || bizType.length() != 2) {
            throw new IllegalArgumentException("业务类型必须为2位字母");
        }
        return generateSerialNumber("ORD" + bizType.toUpperCase());
    }
}

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicLong;/*** 业务系统订单号/流水号生成工具*/
public class SerialNumberGenerator {// 日期时间格式化器(线程安全)private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");// 自增序列(单机环境下使用)private static final AtomicLong SEQUENCE = new AtomicLong(0);// 机器ID(分布式环境下使用)private static final String MACHINE_ID = initMachineId();/*** 生成订单号(格式: ORDER + 时间 + 机器ID + 序列号)* 示例: ORDER2024051215230100110001*/public static String generateOrderNo() {return generateSerialNumber("ORDER");}/*** 生成交易流水号(格式: TRANS + 时间 + 机器ID + 序列号)* 示例: TRANS2024051215230100110001*/public static String generateTransNo() {return generateSerialNumber("TRANS");}/*** 通用序列号生成方法* @param prefix 前缀(如ORDER, TRANS等)*/private static String generateSerialNumber(String prefix) {StringBuilder sb = new StringBuilder(prefix);// 1. 添加时间部分(14位)sb.append(DATE_TIME_FORMATTER.format(LocalDateTime.now()));// 2. 添加机器ID(4位)sb.append(MACHINE_ID);// 3. 添加序列号(6位)sb.append(String.format("%06d", SEQUENCE.incrementAndGet() % 999999));// 4. 添加随机后缀(2位)防止猜测sb.append(String.format("%02d", ThreadLocalRandom.current().nextInt(0, 99)));return sb.toString();}/*** 初始化机器ID(实际项目中可以从配置中心获取)*/private static String initMachineId() {// 这里简单演示,实际项目中可以从配置文件或配置中心获取String machineId = System.getProperty("machine.id", "0001");return String.format("%04d", Integer.parseInt(machineId));}/*** 带业务类型的订单号生成* @param bizType 业务类型(2位字母)*/public static String generateBizOrderNo(String bizType) {if(bizType == null || bizType.length() != 2) {throw new IllegalArgumentException("业务类型必须为2位字母");}return generateSerialNumber("ORD" + bizType.toUpperCase());}
}

第二种。雪花算法

对于分布式系统,推荐使用雪花算法(Snowflake)改进版:

import java.time.Instant;

/**
 * 分布式ID生成器(基于雪花算法改进)
 */
public class DistributedIdGenerator {
    // 起始时间戳(2024-01-01)
    private final static long START_TIMESTAMP = 1704067200000L;
    
    // 机器ID所占位数
    private final static long WORKER_ID_BITS = 5L;
    
    // 数据中心ID所占位数
    private final static long DATACENTER_ID_BITS = 5L;
    
    // 序列号所占位数
    private final static long SEQUENCE_BITS = 12L;
    
    // 最大机器ID
    private final static long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS);
    
    // 最大数据中心ID
    private final static long MAX_DATACENTER_ID = ~(-1L << DATACENTER_ID_BITS);
    
    // 机器ID左移位数
    private final static long WORKER_ID_SHIFT = SEQUENCE_BITS;
    
    // 数据中心ID左移位数
    private final static long DATACENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;
    
    // 时间戳左移位数
    private final static long TIMESTAMP_LEFT_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATACENTER_ID_BITS;
    
    // 序列号掩码
    private final static long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS);
    
    private final long workerId;
    private final long datacenterId;
    private long sequence = 0L;
    private long lastTimestamp = -1L;
    
    public DistributedIdGenerator(long workerId, long datacenterId) {
        if (workerId > MAX_WORKER_ID || workerId < 0) {
            throw new IllegalArgumentException("workerId不合法");
        }
        if (datacenterId > MAX_DATACENTER_ID || datacenterId < 0) {
            throw new IllegalArgumentException("datacenterId不合法");
        }
        this.workerId = workerId;
        this.datacenterId = datacenterId;
    }
    
    public synchronized long nextId() {
        long timestamp = timeGen();
        
        if (timestamp < lastTimestamp) {
            throw new RuntimeException("时钟回拨异常");
        }
        
        if (timestamp == lastTimestamp) {
            sequence = (sequence + 1) & SEQUENCE_MASK;
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L;
        }
        
        lastTimestamp = timestamp;
        
        return ((timestamp - START_TIMESTAMP) << TIMESTAMP_LEFT_SHIFT)
                | (datacenterId << DATACENTER_ID_SHIFT)
                | (workerId << WORKER_ID_SHIFT)
                | sequence;
    }
    
    private long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }
    
    private long timeGen() {
        return Instant.now().toEpochMilli();
    }
    
    /**
     * 将生成的ID转换为订单号
     */
    public static String convertToOrderNo(long id) {
        return "ORD" + id;
    }
}

import java.time.Instant;/*** 分布式ID生成器(基于雪花算法改进)*/
public class DistributedIdGenerator {// 起始时间戳(2024-01-01)private final static long START_TIMESTAMP = 1704067200000L;// 机器ID所占位数private final static long WORKER_ID_BITS = 5L;// 数据中心ID所占位数private final static long DATACENTER_ID_BITS = 5L;// 序列号所占位数private final static long SEQUENCE_BITS = 12L;// 最大机器IDprivate final static long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS);// 最大数据中心IDprivate final static long MAX_DATACENTER_ID = ~(-1L << DATACENTER_ID_BITS);// 机器ID左移位数private final static long WORKER_ID_SHIFT = SEQUENCE_BITS;// 数据中心ID左移位数private final static long DATACENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;// 时间戳左移位数private final static long TIMESTAMP_LEFT_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATACENTER_ID_BITS;// 序列号掩码private final static long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS);private final long workerId;private final long datacenterId;private long sequence = 0L;private long lastTimestamp = -1L;public DistributedIdGenerator(long workerId, long datacenterId) {if (workerId > MAX_WORKER_ID || workerId < 0) {throw new IllegalArgumentException("workerId不合法");}if (datacenterId > MAX_DATACENTER_ID || datacenterId < 0) {throw new IllegalArgumentException("datacenterId不合法");}this.workerId = workerId;this.datacenterId = datacenterId;}public synchronized long nextId() {long timestamp = timeGen();if (timestamp < lastTimestamp) {throw new RuntimeException("时钟回拨异常");}if (timestamp == lastTimestamp) {sequence = (sequence + 1) & SEQUENCE_MASK;if (sequence == 0) {timestamp = tilNextMillis(lastTimestamp);}} else {sequence = 0L;}lastTimestamp = timestamp;return ((timestamp - START_TIMESTAMP) << TIMESTAMP_LEFT_SHIFT)| (datacenterId << DATACENTER_ID_SHIFT)| (workerId << WORKER_ID_SHIFT)| sequence;}private long tilNextMillis(long lastTimestamp) {long timestamp = timeGen();while (timestamp <= lastTimestamp) {timestamp = timeGen();}return timestamp;}private long timeGen() {return Instant.now().toEpochMilli();}/*** 将生成的ID转换为订单号*/public static String convertToOrderNo(long id) {return "ORD" + id;}
}

使用

// 修改日期格式
private static final String DATE_FORMAT = "yyyyMMddHHmmss";// 修改序列号长度
private static final int SERIAL_NUMBER_LENGTH = 4;// 修改订单前缀
private static final String ORDER_PREFIX = "ORD";

http://www.xdnf.cn/news/405397.html

相关文章:

  • python练习-20250512
  • C++23 views::slide (P2442R1) 深入解析
  • AnaTraf:深度解析网络性能分析(NPM)
  • C语言:深入理解指针(3)
  • 基于 Nexus 在 Dockerfile 配置 yum, conda, pip 仓库的方法和参考
  • T2000云腾边缘计算盒子在数猪场景中的应用|YOLOv8+NodeRED
  • 湖北理元理律师事务所:企业债务危机的“止血”与“造血”平衡术
  • 01背包和完全背包
  • 基于Qt6 + MuPDF在 Arm IMX6ULL运行的PDF浏览器——MuPDF Tools
  • 大项目k8s集群有多大规模,多少节点,有多少pod
  • 智能指针入门:深入理解 C++ 的 shared_ptr
  • AI中的MCP是什么?MCP的作用及未来方向预测 (使用go-zero 快速搭建MCP服务器)
  • 2025年北京市积分落户申报
  • 经典案例 | 智能眼镜中瞳距调节和近视调节的应用
  • web 自动化之 Unittest 四大组件
  • 【NextPilot日志移植】ULog
  • 文档外发安全:企业数据防护的最后一道防线
  • RabbitMQ 工作模式
  • JWT的介绍与在Fastapi框架中的应用
  • 单片机-STM32部分:13-1、蜂鸣器
  • 常用依赖文件库
  • kubernetes服务自动伸缩-VPA
  • ESP32开发入门(九):HTTP服务器开发实践
  • Day22打卡-复习
  • 【K8S学习之探针】详细了解就绪探针 readinessProbe 和存活探针 livenessProbe 的配置
  • Kotlin 异步初始化值
  • JVM类加载
  • 生产环境怎么移除console
  • WebSocket集成方案对比
  • 中华春节符号全球推广委员会——“金文形意书《易经》成果展”研学之旅