订单id的设计问题探讨

news2025/1/10 23:34:06

如何设计一个订单id

设计一个订单ID系统需要考虑多个因素,包括唯一性、排序性(时间顺序)、可读性(可选)以及系统的扩展性和性能。结合这些因素,可以选择不同的方案来生成订单ID。以下是几种常见的订单ID设计方案:

1. 使用 UUID

UUID(Universally Unique Identifier)是一种标准的唯一标识符,确保了在分布式系统中的唯一性。UUID 有几种版本,其中最常用的是 UUID v4,它是基于随机数生成的。

优点:
  • 全局唯一性
  • 简单易用
缺点:
  • 不能按时间排序
  • 较长,不适合做主键(占用更多存储空间,影响索引性能)
示例:
import java.util.UUID;

public class OrderIdGenerator {
    public static String generateOrderId() {
        return UUID.randomUUID().toString();
    }

    public static void main(String[] args) {
        System.out.println(generateOrderId());
    }
}

2. 使用雪花算法(Snowflake)

雪花算法是 Twitter 开发的一种分布式 ID 生成算法,能够生成有序且唯一的 64 位整数 ID。雪花算法的 ID 结构如下:

  • 1 位符号位(始终为 0)
  • 41 位时间戳(自定义纪元后的毫秒数)
  • 10 位机器标识(5 位数据中心ID和5位机器ID)
  • 12 位序列号(同一毫秒内生成的不同ID)
优点:
  • 唯一且有序
  • 高性能,适合高并发场景
示例:
 
public class SnowflakeIdGenerator {
    private final long epoch = 1609459200000L; // 自定义纪元 (2021-01-01)
    private final long dataCenterIdBits = 5L;
    private final long workerIdBits = 5L;
    private final long sequenceBits = 12L;

    private final long maxDataCenterId = (1L << dataCenterIdBits) - 1;
    private final long maxWorkerId = (1L << workerIdBits) - 1;
    private final long sequenceMask = (1L << sequenceBits) - 1;

    private final long workerIdShift = sequenceBits;
    private final long dataCenterIdShift = sequenceBits + workerIdBits;
    private final long timestampShift = sequenceBits + workerIdBits + dataCenterIdBits;

    private long dataCenterId;
    private long workerId;
    private long sequence = 0L;
    private long lastTimestamp = -1L;

    public SnowflakeIdGenerator(long dataCenterId, long workerId) {
        if (dataCenterId > maxDataCenterId || dataCenterId < 0) {
            throw new IllegalArgumentException("dataCenterId must be between 0 and " + maxDataCenterId);
        }
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException("workerId must be between 0 and " + maxWorkerId);
        }
        this.dataCenterId = dataCenterId;
        this.workerId = workerId;
    }

    public synchronized long nextId() {
        long timestamp = timeGen();

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

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

        lastTimestamp = timestamp;

        return ((timestamp - epoch) << timestampShift) |
               (dataCenterId << dataCenterIdShift) |
               (workerId << workerIdShift) |
               sequence;
    }

    private long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    private long timeGen() {
        return System.currentTimeMillis();
    }

    public static void main(String[] args) {
        SnowflakeIdGenerator idGenerator = new SnowflakeIdGenerator(1, 1);
        for (int i = 0; i < 10; i++) {
            System.out.println(idGenerator.nextId());
        }
    }
}

3. 使用自增ID和时间戳组合

将自增ID和时间戳组合起来,可以确保订单ID的唯一性和时间顺序性。

优点:
  • 简单易实现
  • 有序且可读性好
缺点:
  • 分布式环境中需解决自增ID的生成问题
示例:
 
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.atomic.AtomicLong;

public class OrderIdGenerator {
    private static final AtomicLong counter = new AtomicLong(0);

    public static String generateOrderId() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
        String timestamp = sdf.format(new Date());
        long increment = counter.incrementAndGet();
        return timestamp + String.format("%04d", increment);
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            System.out.println(generateOrderId());
        }
    }
}

4. 基于数据库的自增ID

使用数据库的自增列来生成订单ID,可以确保ID的唯一性和有序性。但需要注意在高并发和分布式环境中的性能和扩展性问题。

示例:
 
CREATE TABLE orders (
    id BIGSERIAL PRIMARY KEY,
    user_id INT,
    status VARCHAR(20),
    total NUMERIC(10, 2),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

在插入新订单时,数据库会自动生成唯一的自增ID:

INSERT INTO orders (user_id, status, total) VALUES (1, 'completed', 100.00);

总结

设计订单ID时,可以选择不同的方案,具体选择取决于系统的需求和环境:

  • UUID 适合需要全局唯一性的场景,但不能按时间排序。
  • 雪花算法 适合分布式环境,能生成有序且唯一的ID。
  • 自增ID和时间戳组合 简单易实现,适合单机或小规模分布式环境。
  • 数据库自增ID 适合不需要高度分布式的系统,简单直接。

根据具体需求,选择合适的方案并进行实现。

雪花算法生成的ID的组成部分

雪花算法(Snowflake Algorithm)是一种分布式唯一 ID 生成算法,由 Twitter 开发。它生成的 ID 是 64 位的整型数字,通过组合多个不同部分来确保生成的 ID 唯一且有序。雪花算法生成的 ID 通常由以下几个部分组成:

  1. 符号位(1位)

    • 固定为 0,因为生成的 ID 为正数。
  2. 时间戳(41位)

    • 表示自定义纪元(通常是某个固定时间点,如 Unix 纪元)以来的毫秒数。
    • 41 位可以表示的毫秒数约为 69 年(2^41 - 1 毫秒)。
  3. 数据中心ID(5位)

    • 表示数据中心或机房的 ID。
    • 5 位可以表示 32 个不同的数据中心。
  4. 机器ID(5位)

    • 表示工作机器的 ID。
    • 5 位可以表示 32 台不同的机器。
  5. 序列号(12位)

    • 表示同一毫秒内生成的不同 ID。
    • 12 位可以表示 4096 个不同的序列号。

雪花算法生成的 ID 由符号位、时间戳、数据中心ID、机器ID 和序列号组成。它通过时间戳保证全局有序性,通过数据中心ID和机器ID保证分布式环境中的唯一性,通过序列号保证同一毫秒内生成的多个 ID 的唯一性。

其他问题考虑

并发极高情况下,序列号溢出怎么办?

前面提到,序列号部分是12位,这意味着在同一毫秒内可以生成的ID数量为2^12 = 4096个。因此,在同一毫秒内,最多可以生成4096个不同的ID。如果在极高并发的情况下(超过4096个ID每毫秒,即每秒超过4096*1000个ID),标准的雪花算法会遇到序列号溢出的问题,进而导致生成重复的ID。在这种情况下,有几种策略可以解决这一问题:

1. 扩展序列号位数

一种简单的方法是扩展序列号部分的位数,但这会减少其他部分(如时间戳、数据中心ID、机器ID)的位数,从而减少它们的容量。

2. 时间回拨

另一种方法是在同一毫秒内生成的ID数量超过序列号限制时,等待到下一个毫秒。这种方法在极端高并发情况下可能会导致性能瓶颈。

3. 使用多实例

您可以部署多个生成器实例,每个实例有独立的机器ID和数据中心ID,通过负载均衡来分配请求,以减少单个实例的负载。

4. 分布式ID生成服务

使用分布式ID生成服务来分担负载,确保生成唯一ID的同时提升系统的扩展性和可靠性。

5. 混合策略

结合多种策略,如时间回拨和多实例,来应对极高并发的需求。

实现示例:扩展序列号位数

以下是一个扩展序列号位数的示例实现:

public class SnowflakeIdGeneratorExtended {
    private final long epoch = 1609459200000L; // 自定义纪元 (2021-01-01)
    private final long dataCenterIdBits = 5L;
    private final long workerIdBits = 5L;
    private final long sequenceBits = 14L; // 扩展序列号位数到14位

    private final long maxDataCenterId = (1L << dataCenterIdBits) - 1;
    private final long maxWorkerId = (1L << workerIdBits) - 1;
    private final long sequenceMask = (1L << sequenceBits) - 1;

    private final long workerIdShift = sequenceBits;
    private final long dataCenterIdShift = sequenceBits + workerIdBits;
    private final long timestampShift = sequenceBits + workerIdBits + dataCenterIdBits;

    private long dataCenterId;
    private long workerId;
    private long sequence = 0L;
    private long lastTimestamp = -1L;

    public SnowflakeIdGeneratorExtended(long dataCenterId, long workerId) {
        if (dataCenterId > maxDataCenterId || dataCenterId < 0) {
            throw new IllegalArgumentException("dataCenterId must be between 0 and " + maxDataCenterId);
        }
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException("workerId must be between 0 and " + maxWorkerId);
        }
        this.dataCenterId = dataCenterId;
        this.workerId = workerId;
    }

    public synchronized long nextId() {
        long timestamp = timeGen();

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

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

        lastTimestamp = timestamp;

        return ((timestamp - epoch) << timestampShift) |
               (dataCenterId << dataCenterIdShift) |
               (workerId << workerIdShift) |
               sequence;
    }

    private long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    private long timeGen() {
        return System.currentTimeMillis();
    }

    public static void main(String[] args) {
        SnowflakeIdGeneratorExtended idGenerator = new SnowflakeIdGeneratorExtended(1, 1);
        for (int i = 0; i < 10000; i++) {
            System.out.println(idGenerator.nextId());
        }
    }
}

在这个示例中,序列号部分扩展到14位,这样每毫秒可以生成的ID数量增加到16384个(2^14)。这种扩展虽然增加了每毫秒可以生成的ID数量,但同时减少了其他部分的位数,可能不适用于所有场景。

使用多实例的方案

在分布式系统中,使用多实例来生成ID也是一个可行的方案。例如,将请求负载均衡到不同的实例,每个实例使用不同的数据中心ID和机器ID,这样可以大幅度减少单个实例的负载。

 
public class SnowflakeIdGeneratorMultiInstance {
    private static final int MAX_INSTANCES = 32;
    private static final SnowflakeIdGenerator[] GENERATORS = new SnowflakeIdGenerator[MAX_INSTANCES];

    static {
        for (int i = 0; i < MAX_INSTANCES; i++) {
            GENERATORS[i] = new SnowflakeIdGenerator(1, i);
        }
    }

    public static long generateId(int instanceId) {
        return GENERATORS[instanceId % MAX_INSTANCES].nextId();
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100000; i++) {
            System.out.println(generateId(i));
        }
    }
}

在这个示例中,使用32个实例,每个实例使用不同的机器ID,通过简单的负载均衡分配请求。

总结

如果系统需要在极高并发(每毫秒超过4096个ID)下生成唯一ID,可以考虑以下解决方案:

  1. 扩展序列号位数:增加序列号部分的位数,增加每毫秒可生成的ID数量。
  2. 时间回拨:在同一毫秒内生成的ID数量超过限制时,等待下一毫秒。
  3. 使用多实例:通过负载均衡将请求分配到多个ID生成实例,每个实例使用不同的机器ID和数据中心ID。
  4. 分布式ID生成服务:使用专业的分布式ID生成服务,如 Twitter的Snowflake,MongoDB的ObjectId,或Apache的Kudu等。

如何做到订单号又短又不重复?

1. 基于时间的订单号生成

使用时间戳和自增序列的组合,可以生成唯一且有序的订单号。为了确保订单号的长度限制,需要进行一些特殊处理。

方案一:短时间戳 + 自增序列
  • 时间戳:使用自定义的短时间戳,如年月日时分秒。
  • 自增序列:在高并发的情况下,使用自增序列或分布式唯一ID生成器来确保同一秒内的唯一性。
  • 数据中心和机器ID:可以嵌入一些数据中心和机器ID信息。
示例代码:
 
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.atomic.AtomicInteger;

public class OrderIdGenerator {
    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyMMddHHmmss");
    private static final AtomicInteger counter = new AtomicInteger(0);
    private static final int MAX_COUNTER = 999;

    public static synchronized String generateOrderId() {
        // 获取当前时间的短时间戳
        String timestamp = dateFormat.format(new Date());
        // 自增序列,达到最大值后重置
        int count = counter.getAndIncrement();
        if (count > MAX_COUNTER) {
            counter.set(0);
            count = counter.getAndIncrement();
        }
        // 格式化自增序列为三位
        return String.format("%s%03d", timestamp, count);
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            System.out.println(generateOrderId());
        }
    }
}

在这个方案中,订单号的格式为 yyMMddHHmmssSSS + 自增序列的组合。生成的订单号长度为12位,格式如下:

  • 时间戳:6位(如 210615 表示 2021年6月15日)。
  • 自增序列:3位(如 001002,最多999)。

2. 基于随机数和哈希的订单号生成

使用随机数生成唯一订单号,并通过哈希函数确保唯一性。

方案二:短随机数 + 哈希校验
  • 随机数:生成一个较短的随机数。
  • 哈希校验:使用哈希函数来校验和生成唯一的订单号。
示例代码:
 
import java.util.HashSet;
import java.util.Random;
import java.util.Set;

public class OrderIdGeneratorHash {
    private static final int ORDER_ID_LENGTH = 12;
    private static final Random random = new Random();
    private static final Set<String> existingOrderIds = new HashSet<>();

    public static synchronized String generateOrderId() {
        String orderId;
        do {
            orderId = generateRandomOrderId();
        } while (existingOrderIds.contains(orderId));
        existingOrderIds.add(orderId);
        return orderId;
    }

    private static String generateRandomOrderId() {
        StringBuilder orderId = new StringBuilder(ORDER_ID_LENGTH);
        for (int i = 0; i < ORDER_ID_LENGTH; i++) {
            orderId.append(random.nextInt(10)); // 生成0-9之间的随机数字
        }
        return orderId.toString();
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            System.out.println(generateOrderId());
        }
    }
}

在这个方案中,订单号完全是随机生成的,并且使用一个Set来确保唯一性。实际生产环境中,需要有更高效的去重方法(如分布式缓存)。

3. 混合使用时间和随机数

结合时间戳和随机数,既保证有序性又保证唯一性。

方案三:时间戳 + 随机数
  • 时间戳:使用短时间戳。
  • 随机数:生成一定长度的随机数。
示例代码:
 
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;

public class OrderIdGeneratorMixed {
    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyMMddHHmmss");
    private static final Random random = new Random();
    private static final int RANDOM_LENGTH = 4;

    public static synchronized String generateOrderId() {
        // 获取当前时间的短时间戳
        String timestamp = dateFormat.format(new Date());
        // 生成随机数
        String randomNumber = String.format("%04d", random.nextInt(10000)); // 生成4位随机数
        return timestamp + randomNumber;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            System.out.println(generateOrderId());
        }
    }
}

在这个方案中,订单号的格式为 yyMMddHHmmss + 4位随机数,生成的订单号长度为16位。例如:2106151200001234

总结

  • 方案一:基于时间戳和自增序列,适合有严格时间顺序要求的场景。
  • 方案二:基于随机数和哈希校验,适合没有严格时间顺序要求但需要高并发生成订单号的场景。
  • 方案三:结合时间戳和随机数,适合需要平衡有序性和唯一性的场景。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1692159.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Nodejs-多进程之Cluster】

cluster 模块是 Node.js 提供的一个用于多进程的模块&#xff0c;它可以轻松地创建一组共享同一个服务器端口的子进程&#xff08;worker进程&#xff09;。通过使用 cluster 模块&#xff0c;可以充分利用多核系统&#xff0c;提高应用程序的性能和可靠性。 基本原理 cluste…

文心智能体,零代码构建情感表达大师智能体

前言 随着智能体技术的突飞猛进&#xff0c;各行各业正迎来前所未有的变革与机遇。智能体&#xff0c;作为人工智能领域的重要分支&#xff0c;以其自主性、智能性和适应性&#xff0c;正逐步渗透到我们生活的每一个角落&#xff0c;成为推动社会进步和科技发展的新动力。 为了…

秒级达百万高并发框架Disruptor

1、起源 Disruptor最初由lmax.com开发&#xff0c;2010年在Qcon公开发表&#xff0c;并于2011年开源&#xff0c;企业应用软件专家Martin Fowler专门撰写长文介绍&#xff0c;同年它还获得了Oracle官方的Duke大奖。其官网定义为&#xff1a;“High Performance Inter-Thread M…

山东大学软件学院项目实训-创新实训-基于大模型的旅游平台(十九)- JUC(5)

synchronized优化原理 轻量级锁 如果一个对象有多个线程访问&#xff0c;但多线程访问的时间是错开的&#xff08;没有竞争&#xff09;&#xff0c;可以用轻量级锁优化 Slf4j(topic "c.ExerciseTransfer")public class Test {​static final Object obj new Obj…

利用天气API接口自己DIY一个预报小管家

天气预报查询API 是一种实用的日常工具&#xff0c;它通过编程方式为开发者提供实时的天气数据。开发者可以通过简单的代码调用&#xff0c;与天气预报服务提供商进行交互&#xff0c;获取特定地区的天气信息&#xff0c;如温度、湿度、风速、风向、降水量等&#xff0c;以及未…

【智能优化算法】粒子群优化算法(PSO)【附python实现代码】

写在前面&#xff1a; 首先感谢兄弟们的订阅&#xff0c;让我有创作的动力&#xff0c;在创作过程我会尽最大能力&#xff0c;保证作品的质量&#xff0c;如果有问题&#xff0c;可以私信我&#xff0c;让我们携手共进&#xff0c;共创辉煌。 路虽远&#xff0c;行则将至&#…

微信小程序上线必备:SSL证书申请以及安装

一、认识ssl证书 1、ssl证书是什么&#xff1f; SSL证书&#xff0c;全称Secure Socket Layer Certificate&#xff0c;是一种数字证书&#xff0c;它遵循SSL&#xff08;现在通常指TLS&#xff0c;Transport Layer Security&#xff09;协议标准&#xff0c;用于在客户端&…

ElasticSearch - 删除已经设置的认证密码(7.x)

文章目录 Pre版本号 7.x操作步骤检查当前Elasticsearch安全配置停止Elasticsearch服务修改Elasticsearch配置文件删除密码重启Elasticsearch服务验证配置 小结 Pre Elasticsearch - Configuring security in Elasticsearch 开启用户名和密码访问 版本号 7.x ES7.x 操作步骤 …

1小时从0开始搭建自己的直播平台(详细步骤)

本文讲述了如何从0开始&#xff0c;利用腾讯云的平台&#xff0c;快速搭建一个直播平台的过程。 文章目录 效果图详细步骤准备工作第一步&#xff1a;添加域名并检验cname配置1.先填加一个推流域名2. 点击完下一步&#xff0c;得到一个cname地址3. 将cname地址&#xff0c;配置…

A股重磅!史上最严减持新规,发布!

此次减持新规被市场视为A股史上最严、最全面的规则&#xff0c;“花式”减持通道被全面“封堵”。 5月24日晚间&#xff0c;证监会正式发布《上市公司股东减持股份管理暂行办法》&#xff08;以下简称《减持管理办法》&#xff09;及相关配套规则。 据了解&#xff0c;《减持…

开放式耳机哪个品牌音质好用又实惠耐用?五大公认卷王神器直入!

​在现今耳机市场&#xff0c;开放式耳机凭借其舒适的佩戴体验和独特的不入耳设计&#xff0c;备受消费者追捧。它们不仅让你在享受音乐时&#xff0c;仍能察觉周围的声音&#xff0c;确保与人交流无障碍&#xff0c;而且有利于耳朵的卫生与健康。对于运动爱好者和耳机发烧友而…

热题系列章节1

22. 括号生成 数字 n 代表生成括号的对数&#xff0c;请你设计一个函数&#xff0c;用于能够生成所有可能的并且 有效的 括号组合。 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;[“((()))”,“(()())”,“(())()”,“()(())”,“()()()”] 示例 2&#xff1a…

Ubuntu(22.04)不能上网解决办法

想必大家可能在别的贴子看到用以下指令的方法&#xff0c;但是在22版本的ubuntu是行不通的&#xff0c;问题在于22版本中网络管理器的名字压根不是network-manager&#xff0c;而是 NetworkManager. sudo service network-manager stop sudo rm /var/lib/NetworkManager/Netw…

力扣hot100学习记录(八)

206. 反转链表 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 题意 给一个链表&#xff0c;将链表进行翻转 思路 用两个指针维护相邻两个点&#xff0c;每次把后面一个点指向前一个点&#xff0c;直到后一个点指向空&#xff0c;最后把…

ESP32-C3模组上跑通OTA升级(8)

接前一篇文章&#xff1a;ESP32-C3模组上跑通OTA升级&#xff08;7&#xff09; 本文内容参考&#xff1a; 杂项系统 API - ESP32 - — ESP-IDF 编程指南 latest 文档 《ESP32-C3 物联网工程开发实战》 乐鑫科技 特此致谢&#xff01; 七、固件版本 将不同功能的固件标记为…

阿里云Linux 3.2104 LTS 64位安装SVN服务器

直接按步骤 yum install subversion 写y就行 主要是看看安装了那些文件 rpm -ql subversion 主要是为了创建版本库而准备&#xff0c;这个能一遍创建就一遍创建&#xff0c;不行就逐个创建。能创就忽略下面两个mkdir步骤。 mkdir /home/svn/groupRepos 根据新建目录作为版本…

嵌入式作业5

在函数main.c中初始化三种颜色的灯&#xff1a; gpio_init(LIGHT_BLUE, GPIO_OUTPUT, LIGHT_OFF); //蓝灯 gpio_init(LIGHT_GREEN, GPIO_OUTPUT, LIGHT_OFF); //绿灯 gpio_init(LIGHT_RED, GPIO_OUTPUT, LIGHT_OFF); //红灯 同时为了响应以上修改&#xff0c;进入isr。c…

1098: 堆的判断

解法&#xff1a; 堆是完全二叉树 用数组来存储 然后用定义判定 #include<iostream> #include<vector> using namespace std; int main() {int n;cin >> n;vector<int> vec(n);for (int i 0; i < n; i) cin >> vec[i];for (int i 0; i &…

【第1章】SpringBoot入门

文章目录 前言一、版本要求1. SpringBoot版本2. 其他2.1 System Requirements2.2 Servlet Containers2.3 GraalVM Native Images 3. 版本定型 二、新建工程1.IDEA创建 ( 推荐 ) \color{#00FF00}{(推荐)} (推荐)2. 官方创建 三、第一个SpringBoot程序1. 引入web2. 启动类3. 启动…

【Unitydemo制作】音游制作—模式玩法的实现

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;就业…