【JAVA】业务系统订单号,流水号生成规则工具类
设计业务系统订单号,流水号注意事项
-
唯一性:确保在分布式环境下ID不重复
-
有序性:ID随时间递增,有利于数据库索引性能
-
可读性:包含时间信息,便于人工识别
-
扩展性:支持业务前缀和类型区分
-
性能:本地生成,无网络开销
-
高并发:考虑线程安全,避免阻塞
根据实际业务需求,可以调整各部分位数分配,例如增加机器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";