設計業務系統訂單號,流水號注意事項
-
唯一性:確保在分布式環境下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";