在 SpringBoot 中设计一个订单号生成系统

1278157fde13682555d2417baa4dffeb.jpeg来源:juejin.cn/post/7332708703399559209

👉 欢迎加入小哈的星球,你将获得: 专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17..., 点击查看项目介绍

  • 《从零手撸:前后端分离博客项目(全栈开发)》 2期已完结,演示链接:http://116.62.199.48/;

截止目前,累计输出 58w+ 字,讲解图 2330+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,Spring Cloud Alibaba 等等,戳我加入学习,解锁全部项目,已有2100+小伙伴加入

6672f5586d9c2e09a8c3f58a39dcb508.gif

  • 1.UUID

    • 实例代码

  • 2.数据库序列或自增ID

  • 3.时间戳+随机数/序列

    • 实例代码

  • 4.分布式唯一ID生成方案

    • Snowflake ID结构

    • 类定义和变量初始化

    • 构造函数

    • ID生成方法

    • 辅助方法


在Spring Boot中设计一个订单号生成系统,主要考虑到生成的订单号需要满足的几个要求:唯一性、可扩展性、以及可能的业务相关性。以下是几种常见的解决方案及相应的示例代码:

1. UUID

最简单的方法是使用UUID生成唯一的订单号。UUID(Universally Unique Identifier)是一种广泛使用的标识符,由128位组成,通常以32个十六进制数字表示,分为五组,形式为8-4-4-4-12的字符串,例如123e4567-e89b-12d3-a456-426614174000

UUID全球唯一,实现简单,但缺点是UUID较长,不易记忆和存储。

实例代码

Java中生成UUID的示例代码如下:

import java.util.UUID;

public class UUIDGenerator {

    public static String generateUUID() {
        // 生成一个UUID
        UUID uuid = UUID.randomUUID();
        
        // 将UUID转换为字符串
        String uuidAsString = uuid.toString();
        
        // 返回UUID字符串
        return uuidAsString;
    }

    public static void main(String[] args) {
        String uuid = generateUUID();
        System.out.println("Generated UUID: " + uuid);
    }
}

2. 数据库序列或自增ID

利用数据库的序列(如PostgreSQL的SEQUENCE)或自增ID(如MySQL的AUTO_INCREMENT)生成唯一的订单号。数据库序列或自增ID是一种常见的生成唯一标识符的方法,特别是在单体应用或非分布式系统中。

这种方法依赖于数据库的内置机制来保证每次插入新记录时自动产生一个唯一的标识符,缺点是难以在分布式环境中维护唯一性。

// 假设使用JPA
@Entity
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    
    // 其他属性
}
数据库序列(如PostgreSQL的SEQUENCE)
CREATE SEQUENCE order_id_seq START WITH 1 INCREMENT BY 1;

CREATE TABLE orders (
    order_id bigint NOT NULL DEFAULT nextval('order_id_seq'),
    order_data text
);
自增ID(如MySQL的AUTO_INCREMENT)
CREATE TABLE orders (
    order_id INT AUTO_INCREMENT,
    order_data TEXT,
    PRIMARY KEY (order_id)
);

3. 时间戳+随机数/序列

结合时间戳和随机数(或自定义序列)生成订单号,以保证唯一性和可读性。可以通过添加业务相关的前缀来增强业务相关性。

实例代码

以下是一个简单的Java示例,展示了如何结合时间戳、随机数和业务前缀生成订单号:

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ThreadLocalRandom;

public class OrderNumberGenerator {

    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
    private static final int RANDOM_NUM_BOUND = 10000; // 定义随机数范围

    public static String generateOrderNumber(String prefix) {
        // 生成时间戳部分
        String timestamp = dateFormat.format(new Date());
        
        // 生成随机数部分
        int randomNumber = ThreadLocalRandom.current().nextInt(RANDOM_NUM_BOUND);
        
        // 组合成订单号
        return prefix + timestamp + String.format("%04d", randomNumber);
    }

    public static void main(String[] args) {
        // 示例:生成订单号,假设业务前缀为"ORD"
        String orderNumber = generateOrderNumber("ORD");
        System.out.println("Generated Order Number: " + orderNumber);
    }
}

4. 分布式唯一ID生成方案

在分布式系统中,可以使用像Twitter的Snowflake算法生成唯一的ID。Snowflake算法可以生成一个64位的长整数,其中包含时间戳、数据中心ID、机器ID和序列号,以确保生成的ID既唯一又有序。

Snowflake ID结构

Snowflake生成的64位ID可以分为以下几个部分:

  • 1位符号位: 由于整数的最高位是符号位,且64位整数中的最高位为符号位,通常这一位为0,保证ID为正数。

  • 41位时间戳位: 记录时间戳的差值(相对于某个固定的时间点),单位到毫秒。41位时间戳可以使用69年。

  • 10位数据中心ID和机器ID: 通常分为5位数据中心ID和5位机器ID,最多支持32个数据中心,每个数据中心最多支持32台机器。

  • 12位序列号: 用来记录同一毫秒内生成的不同ID,12位序列号支持每个节点每毫秒产生4096个ID序号。

以下是一个简化的Snowflake算法实现示例:

public class SnowflakeIdGenerator {

    private long datacenterId; // 数据中心ID
    private long machineId;    // 机器ID
    private long sequence = 0L; // 序列号
    private long lastTimestamp = -1L; // 上一次时间戳

    private final long twepoch = 1288834974657L;
    private final long datacenterIdBits = 5L;
    private final long machineIdBits = 5L;
    private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
    private final long maxMachineId = -1L ^ (-1L << machineIdBits);
    private final long sequenceBits = 12L;

    private final long machineIdShift = sequenceBits;
    private final long datacenterIdShift = sequenceBits + machineIdBits;
    private final long timestampLeftShift = sequenceBits + machineIdBits + datacenterIdBits;
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);

    public SnowflakeIdGenerator(long datacenterId, long machineId) {
        if (datacenterId > maxDatacenterId || datacenterId < 0) {
            throw new IllegalArgumentException("datacenterId can't be greater than %d or less than 0");
        }
        if (machineId > maxMachineId || machineId < 0) {
            throw new IllegalArgumentException("machineId can't be greater than %d or less than 0");
        }
        this.datacenterId = datacenterId;
        this.machineId = machineId;
    }

    public synchronized long nextId() {
        long timestamp = System.currentTimeMillis();

        if (timestamp < lastTimestamp) {
            throw new RuntimeException("Clock moved backwards. Refusing to generate id");
        }

        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0) {
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            sequence = 0L;
        }

        lastTimestamp = timestamp;

        return ((timestamp - twepoch) << timestampLeftShift) |
                (datacenterId << datacenterIdShift) |
                (machineId << machineIdShift) |
                sequence;
    }

    private long tilNextMillis(long lastTimestamp) {
        long timestamp = System.currentTimeMillis();
        while (timestamp <= lastTimestamp) {
            timestamp = System.currentTimeMillis();
        }
        return timestamp;
    }
}

下面是对这段代码的逐行解释:

类定义和变量初始化

  • private long datacenterId; 定义数据中心ID。

  • private long machineId; 定义机器ID。

  • private long sequence = 0L; 序列号,用于同一毫秒内生成多个ID时区分这些ID。

  • private long lastTimestamp = -1L; 上一次生成ID的时间戳。

以下是Snowflake算法的一些关键参数:

  • private final long twepoch = 1288834974657L; 系统的起始时间戳,这里是Snowflake算法的作者选择的一个固定的时间点(2010-11-04 09:42:54.657 GMT)。

  • private final long datacenterIdBits = 5L; 数据中心ID所占的位数。

  • private final long machineIdBits = 5L; 机器ID所占的位数。

  • private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); 数据中心ID的最大值,这里通过位运算计算得出。

  • private final long maxMachineId = -1L ^ (-1L << machineIdBits); 机器ID的最大值,同样通过位运算得出。

  • private final long sequenceBits = 12L; 序列号占用的位数。

以下是一些用于位运算的参数,用于计算最终的ID:

  • private final long machineIdShift = sequenceBits; 机器ID的偏移位数。

  • private final long datacenterIdShift = sequenceBits + machineIdBits; 数据中心ID的偏移位数。

  • private final long timestampLeftShift = sequenceBits + machineIdBits + datacenterIdBits; 时间戳的偏移位数。

  • private final long sequenceMask = -1L ^ (-1L << sequenceBits); 用于保证序列号在指定范围内循环。

构造函数

构造函数SnowflakeIdGenerator(long datacenterId, long machineId)接收数据中心ID和机器ID作为参数,并对这些参数进行校验,确保它们在合法范围内。

ID生成方法

public synchronized long nextId()是生成ID的核心方法,使用synchronized保证线程安全。

  • 首先获取当前时间戳。

  • 如果当前时间戳小于上一次生成ID的时间戳,抛出异常,因为时钟回拨会导致ID重复。

  • 如果当前时间戳等于上一次的时间戳(即同一毫秒内),通过增加序列号生成不同的ID;如果序列号溢出(超过最大值),则等待到下一个毫秒。

  • 如果当前时间戳大于上一次的时间戳,重置序列号为0。

  • 最后,将时间戳、数据中心ID、机器ID和序列号按照各自的偏移量左移,然后进行位或运算,组合成一个64位的ID。

辅助方法

private long tilNextMillis(long lastTimestamp)是一个辅助方法,用于在序列号溢出时等待直到下一个毫秒。

👉 欢迎加入小哈的星球,你将获得: 专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17..., 点击查看项目介绍

  • 《从零手撸:前后端分离博客项目(全栈开发)》 2期已完结,演示链接:http://116.62.199.48/;

截止目前,累计输出 58w+ 字,讲解图 2330+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,Spring Cloud Alibaba 等等,戳我加入学习,解锁全部项目,已有2100+小伙伴加入

095087e2a678028ec6344999e00f26cc.gif

ef7dd9a0ab592cabff329f15a7c70079.jpeg

 
 

1209aa7fe3e488dc2cd2d436d36f0f54.gif

 
 
 
 
1. 我的私密学习小圈子~
2. 一键生成任意前端项目
3. 还在用 System.currentTimeMillis() 统计代码耗时?太 Low 啦
4. RPC 线程池被打满,1024个线程居然不够用?
 
 
最近面试BAT,整理一份面试资料《Java面试BATJ通关手册》,覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。
获取方式:点“在看”,关注公众号并回复 Java 领取,更多内容陆续奉上。
PS:因公众号平台更改了推送规则,如果不想错过内容,记得读完点一下“在看”,加个“星标”,这样每次新文章推送才会第一时间出现在你的订阅列表里。
点“在看”支持小哈呀,谢谢啦
生成订单号的思路是使用SnowFlake算法。SnowFlake算法是Twitter开源的分布式ID生成算法,它可以保证在分布式环境下生成唯一的ID。\[2\]在SpringBoot项目,可以通过引入SnowFlake组件来使用该算法。首先,需要在代码注入SnowFlakeComponent组件,并使用其nextId()方法生成订单号生成订单号可以保存到Redis,并设置过期时间。然后,将生成订单号发送到前端。具体的实现代码如下所示: ```java @Autowired SnowFlakeComponent snowFlakeComponent; @Override public String generateOrderNumber() { long orderId = snowFlakeComponent.getInstance().nextId(); String orderNumber = String.valueOf(orderId); // 保存到Redis,并设置过期时间 redisTemplate.opsForValue().set("orderNumber:" + orderNumber, "doing", 30, TimeUnit.MINUTES); return orderNumber; } ``` 以上代码,SnowFlakeComponent是一个自定义的组件,用于生成唯一的ID。通过调用其nextId()方法可以获取一个唯一的订单号。然后,将订单号保存到Redis,并设置过期时间为30分钟。最后,将订单号返回给前端。这样就实现了在SpringBoot生成订单号的功能。\[2\] #### 引用[.reference_title] - *1* *2* *3* [SpringBoot 雪花算法生成商品订单号SpringBoot系列13】](https://blog.csdn.net/zl18603543572/article/details/129613629)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值