从零手搓一个【消息队列】BrokerServer 创建核心类, 数据库设计与实现

news2024/11/28 23:52:03

文章目录

  • 一、创建核心类
    • 1, 交换机
    • 2, 交换机类型
    • 3, 队列
    • 4, 绑定
    • 5, 交换机转发 & 绑定规则
    • 6, 消息
    • 7, 消息属性
  • 二、数据库设计
    • 1, 使用 SQLite
    • 2, 使用 MyBatis
      • 2.1, 创建 Interface
      • 2.2, 创建 xml 文件
  • 三、硬盘管理 -- 数据库
    • 1, 创建 DataBaseManager 类
    • 2, init() 初始化数据库
    • 3, insertDefaultData() 插入默认数据
    • 4, createTable() 创建数据表
    • 5, isDBExists() 数据库是否存在
    • 6, deleteTables() 删除数据表
    • 7, 封装数据库的增删查操作
  • 四、小结


创建 Spring Boot 项目, Spring Boot 2 系列版本, Java 8 , 引入 MyBatis, Lombok 依赖

提示:是正在努力进步的小菜鸟一只,如有大佬发现文章欠佳之处欢迎批评指点~ 废话不多说,直接上干货!

整体目录结构 :
在这里插入图片描述

本文主要实现 server 包


一、创建核心类

上篇文章 分析了项目需求, 介绍了项目中重要的核心概念和核心 API, 以及重要板块

一个消息队列中需要的交换机, 队列, 绑定, 消息等核心概念, 以面向对象的思想, 在server.core 包下创建出来对应的类
在这里插入图片描述


1, 交换机

@Data
public class Exchange {
    // 身份标识(唯一, RabbitMQ 就是以 name 作为身份标识的)
    private String name;

    // 三种交换机类型
    private ExchangeTypeEnum type = ExchangeTypeEnum.DIRECT;

    // 是否需要持久化存储
    private boolean durable = false;

    // 是否(交换机没人使用时)自动删除 ------------------>先不实现
    private boolean autoDelete = false;

    // 创建交换机时, 指定的参数选项 ------------------>先不实现
    // 数据库中存储 String 类型, 需要序列化
    private Map<String, Object> arguments = new HashMap<>();

    /**
     * 实现序列化, 修改 getter()和 setter(), 供数据库使用
     */
    public String getArguments(){
        ObjectMapper objectMapper = new ObjectMapper();
        // 序列化 往数据库里写
        try {
            return objectMapper.writeValueAsString(arguments);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return "{}";
    }

    public void setArguments(String arguments) {
        ObjectMapper objectMapper = new ObjectMapper();
        // 反序列化 从数据库里读
        try {
            this.arguments = objectMapper.readValue(arguments, new TypeReference<HashMap<String, Object>>() {});
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
    }


    /**
     * 便于 测试/ 代码内部调用 时使用
     */
    public Object getArguments(String key) {
        return arguments.get(key);
    }

    public void setArguments(String key, Object value) {
        arguments.put(key, value);
    }

    public void setArguments(Map<String, Object> arguments) {
        this.arguments = arguments;
    }
}

本项目未实现 autoDelete 和 arguments


2, 交换机类型

这是一个枚举类, 包含直接交换机, 扇出交换机, 主题交换机

public enum ExchangeTypeEnum {
    DIRECT(0),
    FANOUT(1),
    TOPIC(2);
    private final int type;

    ExchangeTypeEnum(int type) {
        this.type = type;
    }

    public int getType() {
        return type;
    }
}

3, 队列

类名不设为 Queue, 防止和标准库中的 Queue 冲突

@Data
public class MessageQueue {
    // 唯一标识
    private String name;

    // 是否需要持久化存储
    private boolean durable = false;

    // 是否为独有(如果是独有, 只能被一个消费者使用) ------------------>先不实现
    private boolean exclusive = false;

    // 是否(队列没人使用时)自动删除 ------------------>先不实现
    private boolean autoDelete = false;

    // 创建队列时, 指定的参数选项 ------------------>先不实现
    // 数据库中存储 String 类型, 需要序列化
    private Map<String, Object> arguments = new HashMap<>();

    /**
     * 实现序列化, 修改 getter()和 setter(), 供数据库使用
     */
    public String getArguments(){
        ObjectMapper objectMapper = new ObjectMapper();
        // 序列化 往数据库里写
        try {
            return objectMapper.writeValueAsString(arguments);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
        return "{}";
    }

    public void setArguments(String arguments) {
        ObjectMapper objectMapper = new ObjectMapper();
        // 反序列化 从数据库里读
        try {
            this.arguments = objectMapper.readValue(arguments, new TypeReference<HashMap<String, Object>>() {});
        } catch (JsonProcessingException e) {
            e.printStackTrace();
        }
    }

    /**
     * 便于 测试/ 代码内部调用 时使用
     */
    public Object getArguments(String key) {
        return arguments.get(key);
    }

    public void setArguments(String key, Object value) {
        arguments.put(key, value);
    }

    public void setArguments(Map<String, Object> arguments) {
        this.arguments = arguments;
    }
}

暂不实现 exclusive, autoDelete, arguments


4, 绑定

@Data
public class Binding {
    // 绑定的消息队列标识
    private String queueName;

    // 绑定的交换机标识
    private String exchangeName;

    // 绑定的 key
    private String bindingKey;
}

bindingKey 是在创建交换机和队列的绑定时指定的, 生产者发布消息时, 需额外指定一个 routingKey
如果是直接交换机, routingKey 作为队列的唯一标识
如果是扇出交换机, routingKey 为 null, 无需使用
如果是主题交换机, routingKey 需和 bindingKey 匹配


5, 交换机转发 & 绑定规则

在此先不展示, 在后续文章中对应的部分再展示 防止思路混淆


6, 消息

@Data
public class Message implements Serializable {
    // 属性
    private BasicProperties basicProperties = new BasicProperties();

    // 正文
    private byte[] body;

    // 消息存储在文件中的偏移量(字节, 约定 "[,)" 区间 )
    private transient long offsetBegin = 0;
    private transient long offsetEnd = 0;

    // 是否合法(逻辑删除的标记, 0x1 有效, 0x0 无效)
    private byte isValid = 0x1;


    // 提供工厂方法, 封装 Message 类的创建过程
    public static Message createMessage(String routingKey, BasicProperties basicProperties, byte[] body) {
        Message message = new Message();
        if (basicProperties != null) {
            message.setBasicProperties(basicProperties);
        }
        message.setMessageId("M$" + UUID.randomUUID().toString().replace("-", ""));
        message.setRoutingKey(routingKey);
        message.setBody(body);
        return message;
    }

	// 下面这些方法是为了封装 basicProperties 中的 getter()和 setter()
    public String getMessageId() {
        return basicProperties.getMessageId();
    }

    public void setMessageId(String id) {
        basicProperties.setMessageId(id);
    }

    public String getRoutingKey() {
        return basicProperties.getRoutingKey();
    }

    public void setRoutingKey(String key) {
        basicProperties.setRoutingKey(key);
    }

    public int getDeliverMode() {
        return basicProperties.getDeliverMode();
    }

    public void setDeliverMode(int value) {
        basicProperties.setDeliverMode(value);
    }
}
  • Message 需要实现 Serializable 接⼝. 后续需要把 Message 写⼊⽂件以及进⾏⽹络传输.
  • basicProperties 是消息的属性信息. body 是消息体.
  • offsetBeg 和 offsetEnd 表⽰消息在消息⽂件中所在的起始位置和结束位置. 这⼀块具体的设计后⾯再详细介绍. 使⽤ transient 关键字避免属性被序列化.
  • isValid ⽤来表⽰消息在⽂件中是否有效. 这⼀块具体的设计后⾯再详细介绍.
  • createMessage() 相当于⼀个⼯⼚⽅法, ⽤来创建⼀个 Message 实例. messageId 通过UUID 的⽅式⽣成.

文件中的数据不相当于一个顺序表, 如果要真正删除一条消息, 是不是需要把后面的数据整体往前挪动? 这无疑是个低效操作, 因此, 对于 “删除消息” 这种高频操作, 逻辑删除显然是更优解, 但也不能让消息无限制的堆在文件中, 所以后面会参考 JVM 的 GC , 自主实现清理文件的功能


7, 消息属性

这个类作为Message 类的 引用类型的成员属性, 也需要实现 Serializable 接⼝, 否则 message 对象不能被序列化

@Data
public class BasicProperties implements Serializable {
    // 消息的唯一标识(UUID)
    private String messageId;

    // 和 bindingKey 匹配(如果交换机为 DIRECT, 该值就是队列名, 如果交换机为 FANOUT, 该值为 null )
    private String routingKey;

    // 是否要消息持久化( RabbitMQ 就是使用 1 表示不持久化, 2 表示持久化)
    private int deliverMode = 2;

    // ... 其他属性暂不考虑
}

二、数据库设计


1, 使用 SQLite

对于 Exchange, MSGQueue, Binding, 我们使⽤数据库进⾏持久化保存.

此处我们使⽤的数据库是 SQLite, 是⼀个更轻量的数据库

SQLite 只是⼀个动态库(当然, 官⽅也提供了可执⾏程序 exe), 我们在 Java 中直接引⼊ SQLite 依赖, 即可直接使⽤, 不必安装其他的软件.

MySQL 是一个客户端服务器结构的程序, SQLite 相当于直接操作本地的硬盘文件

  • 在pom.xml文件中的 “dependencies” 标签中拷贝 :
		<dependency>
			<groupId>org.xerial</groupId>
			<artifactId>sqlite-jdbc</artifactId>
			<version>3.41.0.1</version>
		</dependency>
  • 在 resource 目录下创建 application.yml 文件配置SQLite数据源, 拷贝:
spring:
  datasource:
    url: jdbc:sqlite:./data/meta.db # 注意这个路径
    username:# 不需要
    password:# 不需要
    driver-class-name: org.sqlite.JDBC

mybatis:
  mapper-locations: classpath:mapper/**Mapper.xml

数据库文件的位置就是 ./data/meta.db, 数据库的数据就在这里


2, 使用 MyBatis

实现 mapper 包
在这里插入图片描述


2.1, 创建 Interface

在 server.mapper 包下定义一个 MetaMapper 接口, 需要提供 交换机, 队列, 绑定 的建表, 插入, 删除 , 查询的 API (抽象方法)

不需要使用sql 语句建库, 创建出 ./data/meta.db 这个文件就相当于建库了, 后面再写创建文件的操作

@Mapper
public interface MetaMapper {
    /**
     * 建表
     */
    void createExchangeTable();

    void createQueueTable();

    void createBindingTable();

    /**
     * exchange 表
     */
    void insertExchange(Exchange exchange);

    void deleteExchange(String exchangeName);

    List<Exchange> selectAllExchanges();

    /**
     * queue 表
     */
    void insertQueue(MessageQueue queue);

    void deleteQueue(String queueName);

    List<MessageQueue> selectAllQueues();

    /**
     * binding 表
     */
    void insertBinding(Binding binding);

    void deleteBinding(Binding binding);

    List<Binding> selectAllBindings();
}

2.2, 创建 xml 文件

在 resource 目录下, 新建一个 mapper 包, 创建 MetaMapper.xml 文件, 在这个文件中编写 sql 语句, 实现上述在 MetaMapper 接口中的抽象方法

在 xml 文件中拷贝:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.server.mapper.MetaMapper">

</mapper>

其中 namespace 这个字段的值要对应刚才定义的 MetaMapper 接口 的路径


  • 建表(使用 update 标签即可)
    <update id="createExchangeTable">
        create table if not exists exchange (
            name varchar(50) primary key,
            type int,
            durable boolean,
            autoDelete boolean,
            arguments varchar(1024)
        );
    </update>

    <update id="createQueueTable">
        create table if not exists queue (
        name varchar(50) primary key,
        durable boolean,
        exclusive boolean,
        autoDelete boolean,
        arguments varchar(1024)
        );
    </update>

    <update id="createBindingTable">
        create table if not exists binding (
        exchangeName varchar(50),
        queueName varchar(50),
        bindingKey varchar(256)
        );
    </update>

  • exchange 表的增删查的 sql
    <insert id="insertExchange" parameterType="com.example.demo.server.core.Exchange">
        insert into exchange values(#{name}, #{type}, #{durable}, #{autoDelete}, #{arguments});
    </insert>

    <select id="selectAllExchanges" resultType="com.example.demo.server.core.Exchange">
        select * from exchange;
    </select>

    <delete id="deleteExchange" parameterType="java.lang.String">
        delete from exchange where name = #{exchangeName};
    </delete>

  • queue 表的增删查的 sql
<insert id="insertQueue" parameterType="com.example.demo.server.core.MessageQueue">
        insert into queue values(#{name}, #{durable}, #{exclusive}, #{autoDelete}, #{arguments});
    </insert>

    <select id="selectAllQueues" resultType="com.example.demo.server.core.MessageQueue">
        select * from queue;
    </select>

    <delete id="deleteQueue" parameterType="java.lang.String">
        delete from queue where name = #{queueName};
    </delete>

  • queue 表的增删查的 sql
	<insert id="insertBinding" parameterType="com.example.demo.server.core.Binding">
        insert into binding values(#{exchangeName}, #{queueName}, #{bindingKey});
    </insert>

    <select id="selectAllBindings" resultType="com.example.demo.server.core.Binding">
        select * from binding;
    </select>

    <delete id="deleteBinding" parameterType="com.example.demo.server.core.Binding">
        delete from binding where exchangeName = #{exchangeName} and queueName = #{queueName};
    </delete>

三、硬盘管理 – 数据库

实现 datacenter 包中的 DataBaseManager 类
在这里插入图片描述

datacenter 这个包中整合硬盘上的数据管理 + 内存上的数据管理
硬盘上的数据管理又整合了 数据库中的数据管理 + 文件中的数据管理


1, 创建 DataBaseManager 类

成员属性需要 MetaMapper 的对象, 用来封装刚才编写的数据库的 API

但并不使用 SpringBoot 的依赖注入 (@AutoWired), 而是使用以来查找的方式获取到 metaMapper

public class DataBaseManager {
    private MetaMapper metaMapper;
  
}

在启动类中初始化容器

@SpringBootApplication
public class DemoApplication {
	public static ConfigurableApplicationContext context;
	public static void main(String[] args) throws IOException {
		context = SpringApplication.run(DemoApplication.class, args);
	}
}

2, init() 初始化数据库

对于 DataBaseManager 类的初始化工作, 不仅仅是对成员属性的初始化, 而是需要一些额外的业务逻辑, 这种情况就不使用构造方法了, 而是单独定义一个方法

初始化工作: metaMapper + 建库建表 + 插入默认数据

如果是第一次启动服务器, 没有数据库则建库建表
如果是重启服务器, 已有数据库则不做处理

MyBatis 在第一次创建数据表的时候就会创建出 ./data/meta.db 这个文件, 但前提是要有 ./data 这个目录, 所以要先手动创建

    public void init() {
        this.metaMapper = DemoApplication.context.getBean(MetaMapper.class);

        if(!isDBExists()) {
            // 创建目录
            File file = new File("./data");
            file.mkdirs();
            // 创建数据表
            createTable();
            // 插入数据
            insertDefaultData();
        }
    }

3, insertDefaultData() 插入默认数据

创建一个默认的交换机(直接交换机)

    public void insertDefaultData() {
        Exchange exchange = new Exchange();
        exchange.setName("");
        exchange.setType(ExchangeTypeEnum.DIRECT);
        exchange.setDurable(false);
        exchange.setAutoDelete(false);
        metaMapper.insertExchange(exchange);
    }

4, createTable() 创建数据表

    public void createTable() {
        metaMapper.createExchangeTable();
        metaMapper.createQueueTable();
        metaMapper.createBindingTable();
    }

5, isDBExists() 数据库是否存在

SQLite 是一个轻量级数据库, 操作 SQLite 相当于操作本地的硬盘文件, 所以检查数据库是否存在就是检查数据库文件是否存在

    public boolean isDBExists() {
        File file = new File("./data/meta.db");
        return file.exists();
    }

6, deleteTables() 删除数据表

同上, 删除数据库就是删除文件, 先删文件再删目录

    public void deleteTables(){
        File file = new File("./data/meta.db");
        file.delete();

        File dir = new File("./data");
        dir.delete();
    }

7, 封装数据库的增删查操作

public void insertExchange(Exchange exchange) {
        metaMapper.insertExchange(exchange);
    }

    public List<Exchange> selectAllExchanges() {
        return metaMapper.selectAllExchanges();
    }

    public void deleteExchange(String exchangeName) {
        metaMapper.deleteExchange(exchangeName);
    }

    public void insertQueue(MessageQueue queue) {
        metaMapper.insertQueue(queue);
    }

    public List<MessageQueue> selectAllQueues() {
        return metaMapper.selectAllQueues();
    }

    public void deleteQueue(String queueName) {
        metaMapper.deleteQueue(queueName);
    }

    public void insertBinding(Binding binding) {
        metaMapper.insertBinding(binding);
    }

    public List<Binding> selectAllBindings() {
        return metaMapper.selectAllBindings();
    }

    public void deleteBinding(Binding binding) {
        metaMapper.deleteBinding(binding);
    }

四、小结

本文主要实现了 3 点 :

  • 1, 根据面向对象思想, 创建出了交换机, 队列, 绑定, 消息, 等核心概念的类
  • 2, 持久化存储 --> 硬盘管理 --> 数据库
    • 2.1, 数据库设计, 使用 SQLite, 并结合 MyBatis 编写了交换机, 队列, 绑定的建表, 增, 删, 查的 sql
    • 2.2, 使用 DataBaseManager 这个类管理数据库中的数据, 因为仅仅有sql语句不足以支撑所有的业务逻辑, 还需要对数据库的初始化, 判断存在, 删除等做进一步的封装

篇幅有限, 目前为止, 持久化存储 --> 硬盘管理 --> 文件 这个板块还没实现

下篇会实现消息在文件上的存储( 文件管理 : MessageFileManager 类), 以及对数据库文件的进一步封装(硬盘数据管理 : DiskDataCenter 类)


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

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

相关文章

【ONE·Linux || 多线程(一)】

总言 多线程&#xff1a;进程线程基本概念、线程控制、互斥与同步。 文章目录 总言1、基本概念1.1、补充知识1.1.1、堆区细粒度划分1.1.2、虚拟地址到物理空间的转化 1.2、如何理解线程、进程1.2.1、如何理解线程&#xff1f;1.2.2、如何理解进程&#xff1f; 1.3、实践操作1.…

职业规划,什么是职业兴趣 - 我喜欢做什么?

能够在工作岗位上面做出成绩的人&#xff0c;都是结合自身兴趣&#xff0c;对职业进行合理规划的那一类。尤其是步入中年以后&#xff0c;能够创造出巨大价值的人&#xff0c;无一例外都是喜欢自己职业的人。没有将兴趣融入工作的人&#xff0c;只能够忍受默默无闻地活着&#…

liunx的攻击

1.场景和分析 2.病毒分析 3.解决步骤

Linux学习记录——삼십일 socket编程---TCP套接字

文章目录 TCP套接字简单通信1、服务端1、基本框架2、获取连接 2、客户端3、多进程4、多线程5、线程池6、简单的日志系统7、守护进程8、其它 TCP套接字简单通信 本篇gitee 学习完udp套接字通信后&#xff0c;再来看TCP套接字。 四个文件tcp_server.hpp&#xff0c; tcp_serve…

黑豹程序员-放大招-架构师学习路线图

文章目录 全栈软件架构师技术路线六环能力图一、开发基础二、增强软件三、海量数据四、软件智能五、并发增强六、桌面开发 全栈软件架构师技术路线 六环能力图 作为软件开发&#xff0c;我们的任务就是开发软件业务系统。 如果要做好一个软件系统需要的技能是非常多的。我归纳…

分布式链路追踪--SkyWalking7.0.0+es7.0.0

分布式链路追踪–SkyWalking ​ 微服务的出现&#xff0c;的确解决了一些业务痛点&#xff0c;但是也造成了新的问题比如随着调用链的拉长&#xff0c;如果想要知道请求为什么这么慢&#xff0c;这个请求到底经历了哪些环节&#xff0c;又依赖了哪些东西&#xff0c;在微服务架…

基于Java的婚纱影楼管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…

Bluespec SytemVerilog 握手协议接口转换

01、引言 由于接口控制信号上的差异&#xff0c;要实现Bluespec SystemVerilog(BSV)生成的代码和外部Verilog代码之间的正确交互是一件比较麻烦同时容易出错的事情。在BSV中, 模块之间的交互都是基于Action或ActionValue这两类method完成。下图展示了使用BSV设计的某一模块的接…

综合应用QGIS软件,实现商场选址分析

一、实验要求 ①离城市主要交通道路50米内&#xff0c;保证商场交通的便利性。 ②在居民区100米内&#xff0c;便于居民步行到商场。 ③距离停车场100米内&#xff0c;便于顾客停车。 ④距离其他商场500米范围之外&#xff0c;减少竞争压力。 二、实验数据 ①城市地区主要…

机器学习算法基础--层次聚类法

文章目录 1.层次聚类法原理简介2.层次聚类法基础算法演示2.1.Single-linkage的计算方法演示2.2.Complete-linkage的计算方法演示2.3.Group-average的计算方法演示 3.层次聚类法拓展算法介绍3.1.质心法原理介绍3.2.基于中点的质心法3.3.Ward方法 4.层次聚类法应用实战4.1.层次聚…

Java21 新特性

文章目录 1. 概述2. JDK21 安装与配置3. 新特性3.1 switch模式匹配3.2 字符串模板3.3 顺序集合3.4 记录模式&#xff08;Record Patterns&#xff09;3.5 未命名类和实例的main方法&#xff08;预览版&#xff09;3.6 虚拟线程 1. 概述 2023年9月19日 &#xff0c;Oracle 发布了…

【Linux】完美解决ubuntu18.04下vi不能使用方向键和退格键

今天在刚安装完ubuntu18.04&#xff0c;发现在使用vi命令配置文件时使用方向键并不能移动光标&#xff0c;而是出现一堆奇怪的英文字母&#xff0c;使用退格键也不能正常地删除内容&#xff0c;用惯了CentOS的我已经感觉到ubuntu没有centos用着丝滑&#xff0c;但是没办法&…

C++ -- 学习系列 std::deque 的原理与使用

一 deque 是什么? std::deque 是 c 一种序列式容器&#xff0c;其与 vector 类似&#xff0c;其底层内存都是连续的&#xff0c;不同的地方在于&#xff0c; vector 是一端开口&#xff0c;在一端放入数据与扩充空间&#xff0c;而 deque 是双端均开口&#xff0c;都可以放…

lv5 嵌入式开发-10 信号机制(下)

目录 1 信号集、信号的阻塞 2 信号集操作函数 2.1 自定义信号集 2.2 清空信号集 2.3 全部置1 2.4 将一个信号添加到集合中 2.5 将一个信号从集合中移除 2.6 判断一个信号是否在集合中 2.7 设定对信号集内的信号的处理方式(阻塞或不阻塞) 2.8 使进程挂起&#xff08;…

NLP 01(介绍)

一、NLP 自然语言处理 (Natural Language rrocessing,简称NLP) 是计算机科学与语言学中关注于计算机与人类语言间转换的领域。 1.1 发展 规则&#xff1a;基于语法 自然语言处理的应用场景: 语音助手 机器翻译 搜索引擎 智能问答

Windows下安装MySQL8详细教程

Windows下安装MySQL8详细教程 因为需要在Windows下安装MySQL8的数据库&#xff0c;做一个临时数据库环境。 1.准备软件 使用社区版本&#xff0c;下载地址如下&#xff1a; https://dev.mysql.com/downloads/mysql/ 使用8.0.16版本&#xff0c;需要在归档中查找 选择版本&a…

pysimpleGui 使用之sg.SaveAs使用

SaveAs与FileBrowse使用一样需要给指定target参数&#xff0c;保存路径 layout [ [ sg.FileBrowse( button_text“请选择单个文件”, # 按钮文本 target“single_path”, # 把选择后的路径保存到key为input_path的对象 # file_types((“All Files”, “.”),), # 默认筛选全部…

Python+Yolov8路面桥梁墙体裂缝识别

程序示例精选 PythonYolov8路面桥梁墙体裂缝识别 如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对《PythonYolov8路面桥梁墙体裂缝识别》编写代码&#xff0c;代码整洁&#xff0c;规则&#…

如何应用MBTI职业性格测试来做职业规划

想要有一个不错的职业发展&#xff0c;需要做好职业规划。通常来说&#xff0c;职业规划可以分为三个组成&#xff0c;即定位、目标和路径。应用MBTI职业性格测试&#xff0c;可以对上述三个组成有更清晰的认识&#xff0c;帮助人们完成适合自己的职业规划。 职业性格和职业定…

Pytorch之ResNet图像分类

&#x1f482; 个人主页:风间琉璃&#x1f91f; 版权: 本文由【风间琉璃】原创、在CSDN首发、需要转载请联系博主&#x1f4ac; 如果文章对你有帮助、欢迎关注、点赞、收藏(一键三连)和订阅专栏哦 目录 前言 一、ResNet网络结构 1.residual结构 2.BN(Batch Normalization)层…