Mybatis-3自己实现MyBatis底层机制

MyBatis整体架构分析

一图胜千言

1、Mybatis核心框架示意图

2、对上图的解读

1)mybatis的核配置文件
mybatis-config.xml:进行全局配置,全局只能有一个这样的配置文件
XxxMapper.xml配置多个SQL,可以有多个XxxMappe.xml配置文件
2)通过mybatis-config.xml配置文件得到SqlSessionFactory
3)通过SqlSessionFactory得到SqlSession,用SqlSession就可以操作数据了
4)SqlSession.底层是Executor(执行器),有2个重要的实现类,它们有很多实现方法

        4.1基本执行器BaseExecutor

        4.2带缓存的执行器CachingExecutor

5)MappedStatement是通过XxxMapper.xml中定义,生成的statement对象
6)参数输入执行并输出结果集,无需手动判断参数类型和参数下标位置,且自动将结果集映射为Java对象

搭建MyBatis底层机制开发环境

1.创建Maven项目stein-mybatis

        Archetype还是选的quickstart

2.修改文件pom.xml

引入必要的依赖

<dependencies><!--解析配置文件,引入dom4j --><dependency><groupId>dom4j</groupId><artifactId>dom4j</artifactId><version>1.6.1</version></dependency><!--引入mysql依赖--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.26</version><!--原来使用的版本,出现异常情况,可以退回该版本--><!--<version>5.1.49</version>--></dependency><!--引入神器lombok,简化entity/pojo/javabean的开发--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.30</version><!--原来使用的版本,异常时回退--><!--<version>1.18.4</version>--></dependency><!--测试依赖--><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.2</version><!--<version>4.12</version>--><!--暂时取消了作用域--><!--<scope>test</scope>--></dependency></dependencies>

3.创建数据库和表

CREATE DATABASE stein_mybatis;
USE stein_mybatisCREATE TABLE monster(
`id` INT PRIMARY KEY AUTO_INCREMENT,
`age` INT NOT NULL,
`birthday` DATE DEFAULT NULL,
`email` VARCHAR(255)NOT NULL,
`gender` TINYINT NOT NULL,
`name` VARCHAR(255)NOT NULL,
`salary` DOUBLE NOT NULL
)CHARSET=utf8-- 测试数据
INSERT INTO `monster` VALUES(NULL,200,'2000-11-11','nmw@sohu.com',1,'牛魔王',8888.88)

自己写的Mybatis——设计思路

1.自己写Mybatis的底层实现设计

2.对上图的解读

一、传统的方式操作数据库
1)得到HspSession对象
2)调用HspExecutor的方法完成操作
3)HspExecutor的连接是从HspConfiguration获取

二、 MyBatis操作数据库的方式分析
1)得到HspSession
2)不直接调用HspExecutorl的方法完成操作
3)通过HspMapperProxy获取Mapper对象
4)调用Mapper的方法,完成数据库的操作
5)Mapper最终还是动态代理方式,使用HspExecutor的方法完成操作
6)这里比较麻烦的就是HspMapperProxy的动态代理机制的实现

自己实现Mybatis底层机制

实现任务

阶段1-完成读取配置文件,得到数据库连接

完成设计图中的这部分

1.创建配置文件

新建resource目录,然后再建src/main/resources/stein-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<database><property name="driverClassName" value="com.mysql.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/stein_mybatis?useSSL=true&amp;useUnicode=true&amp;charsetEncoding=UTF-8"/><property name="username" value="root"/><property name="password" value="root"/>
</database>

2.新建读取配置文件的类

src/main/java/com/stein/steinmybatis/sqlSession/SteinConfiguration.java

public class SteinConfiguration {public ClassLoader classLoader=ClassLoader.getSystemClassLoader();public Connection build(String xmlConfigFile) {InputStream resource = classLoader.getResourceAsStream(xmlConfigFile);Connection connection=null;try {SAXReader saxReader = new SAXReader();Document document = saxReader.read(resource);Element rootElement = document.getRootElement();System.out.println("rootElement="+rootElement);connection = evalDataSource(rootElement);} catch (DocumentException e) {throw new RuntimeException(e);}return connection;}public Connection evalDataSource(Element node) {if(!"database".equals(node.getName())) {throw new RuntimeException("root节点应当是<database>");}String driverClassName = null;String url = null;String username = null;String password = null;for (Object element : node.elements()) {Element ele = (Element) element;String name = ele.attributeValue("name");String value = ele.attributeValue("value");//当获取标签之间的文本的时候才用它//String value = ele.getText();if(name==null || value==null) {throw new RuntimeException("节点内容获取失败");}//java注意版本switch(name) {case "driverClassName":driverClassName = value;break;case "url":url = value;break;case "username":username = value;break;case "password":password = value;break;default:throw new RuntimeException("元素异常"+name+";"+value);}}Connection connection =null;try {//自动注册,这儿的反射也可以不写了//Class.forName(driverClassName);connection = DriverManager.getConnection(url, username, password);} catch (Exception e) {throw new RuntimeException("获取连接失败");}return connection;}
}

3.测试

可以在写完读取xml文档,获得root节点时测试一次。再在获得connection后测试一次。这儿两个测试合并一起了。

public class SteinConfigurationTest {public static void main(String[] args) {Connection connection = new SteinConfiguration().build("stein-config.xml");System.out.println("connection = " + connection);}
}

阶段2-编写执行器,输入SQL语句,完成操作

穿插:lombok的使用

1)引入pom.xml

    <!--引入神器lombok,简化entity/pojo/javabean的开发--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.30</version><!--演示的版本,异常时回退到该版本--><!--<version>1.18.4</version>--></dependency>

2)常用注解

@NoArgsConstructor 生成无参构造器
@AllArgsConstructor 生成全参构造器
@data 相当于:

@Getter
@Setter
@RequiredArgsConstructor 为每个需要特殊处理的字段生成一个带有1个参数的构造函数。
@ToString
@EqualsAndHashCode
lombok.Value 这个是啥?欢迎评论区指点

        虽然没有指定无参构造器,但是在没有构造器的时候,它会默认生成一个无参构造器。但是指定有构造器了,就不会默认生成构造器。

3)常用组合

@Setter
@Getter
@ToString
@NoArgsConstructor
@AllArgsConstructor

或者

@Data
@AllArgsConstructor 因为指定了全参构造器
@NoArgsConstructor  就必须显式的给出无参构造器,否则不会生成

1.创建Entity

完成Monster.java的创建

@Setter
@Getter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Monster {private Integer id;private Integer age;private String name;private String email;private Date birthday;private Double salary;private String gender;
}

2.创建执行器

接下来完成图中的这一步:

1)创建接口

com/stein/steinmybatis/sqlSession/Executor.java

public interface Executor {<T> T query(String statement, Object parameter);
}

2)实现接口

com/stein/steinmybatis/sqlSession/SteinExecutor.java

public class SteinExecutor implements Executor {private SteinConfiguration config=new SteinConfiguration();@Overridepublic <T> T query(String sql, Object parameter) {Connection connection = getConnection();Monster monster = new Monster();try {PreparedStatement preparedStatement = connection.prepareStatement(sql);//设置参数,如果参数多,可以使用数组处理//preparedStatement.setString(1, parameter.toString());//这两种写法都可以preparedStatement.setObject(1,parameter);ResultSet resultSet = preparedStatement.executeQuery();//这儿的结果集是如何完全循环的?//实际上只执行了一次循环就将monster赋值完成了while (resultSet.next()) {monster.setId(resultSet.getInt("id"));monster.setName(resultSet.getString("name"));monster.setAge(resultSet.getInt("age"));monster.setGender(resultSet.getString("gender"));monster.setSalary(resultSet.getDouble("salary"));monster.setEmail(resultSet.getString("email"));monster.setBirthday(resultSet.getDate("birthday"));}//关闭资源if(resultSet!=null){resultSet.close();}if(preparedStatement!=null){preparedStatement.close();}if(connection!=null){connection.close();}} catch (Exception e) {throw new RuntimeException(e);}return (T)monster;}public Connection getConnection() {Connection connection = config.build("stein-config.xml");return connection;}
}

3)测试

    @Testpublic void query(){Executor executor = new SteinExecutor();Monster monster = executor.query("select * from monster where id=?", 1);System.out.println("monster = " + monster);}

阶段3-将Sqlsession封装到执行器

接下来要做的事情:

        搭建Configuration(连接)Executor之间的桥梁

1.创建SteinSession

com/stein/steinmybatis/sqlSession/SteinSession.java

public class SteinSession {//属性//执行器private SteinExecutor executor=new SteinExecutor();//配置类private SteinConfiguration config=new SteinConfiguration();//编写方法,返回一条记录-即一个对象public <T> T selectOne(String sql, Object parameter) {return executor.query(sql, parameter);}
}

2.测试

    @Testpublic void selectOne(){SteinSession steinSession = new SteinSession();Monster monster = steinSession.selectOne("select * from monster where id=?", 1);System.out.println("monster = " + monster);}

阶段4-开发Mapper接口和Mapper.xml(重点)

本次完成的任务:

1.创建Mapper接口

com/stein/mapper/MonsterMapper.java

public interface MonsterMapper {Monster getMonsterById(Integer id);
}

2.配置实现Mapper接口

src\main\resources\MonsterMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<mapper namespace="com.stein.mapper.MonsterMapper" ><select id="getMonsterById" resultType="com.stein.entity.Monster"><!--这儿为了简化,没有写成#{id}的形式-->select * from monster where id=?</select>
</mapper>

阶段5-开发和Mapper接口相映射的MapperBean

接下来完成,MapperBean,它关联了接口与实现类的组件/对象

1.创建Function记录Mapper.xml的方法信息

com/stein/config/Function.java

@Setter
@Getter
public class Function {//sql类型private String sqlType;//方法名private String functionName;//结果类型private Object resultType;//具体的sql语句private String sql;//参数类型,xml里没有设置,是个空的字段private String parameterType;
}

2.创建MapperBean记录XXXMapper接口的信息,和Function

com/stein/config/MapperBean.java

@Getter
@Setter
public class MapperBean {private String interfaceName;private Function[] functions;
}


阶段6-读取解析XxxMapper.xml,装填MappperBean对象

1.添加读取方法

        com/stein/steinmybatis/sqlSession/SteinConfiguration.java

 /*** @param xmlFile 传入文件名,resource文件夹会在target目录中重建,直接获取即可* @return*/public MapperBean readMapperBean(String xmlFile) {MapperBean mapperBean = new MapperBean();InputStream stream = classLoader.getResourceAsStream(xmlFile);SAXReader saxReader = new SAXReader();try {Document document = saxReader.read(stream);Element rootElement = document.getRootElement();System.out.println("rootElement="+rootElement);/**<mapper namespace="com.stein.mapper.MonsterMapper" ><select id="getMonsterById" resultType="com.stein.entity.Monster">select * from monster where id=?</select></mapper>*///获取namespace的方法名if(!"mapper".equals(rootElement.getName())) {throw new RuntimeException("xml文件找不到<mapper>标签");}String interfaceName = rootElement.attributeValue("namespace");System.out.println("interfaceName="+interfaceName);mapperBean.setInterfaceName(interfaceName.trim());List<Function> functions = new ArrayList<>();/**<select id="getMonsterById" resultType="com.stein.entity.Monster">select * from monster where id=?</select>*///获取select的内容List elements = rootElement.elements();for (Object element : elements) {//System.out.println("进入元素遍历");Element ele = (Element) element;System.out.println("ele="+ele);Function function = new Function();//这儿取值为null,可以看出"select"并不是属性"attribute"//String sqlType = ele.attributeValue("select");String sqlType = ele.getName().trim();function.setSqlType(sqlType);System.out.println("sqlType="+sqlType);String functionName = ele.attributeValue("id");function.setFunctionName(functionName);String resultType = ele.attributeValue("resultType");//因为resultType是一个Object类型,这儿返回一个实例Object newInstance = Class.forName(resultType).newInstance();function.setResultType(newInstance);//sql语句也明显不是"attribute"//String sql = ele.attributeValue("sql");String sql = ele.getTextTrim();function.setSql(sql);//这儿的getFunctions并没有new ArrayList,所以是个null,导致出bug的罪魁祸首//mapperBean.getFunctions().add(function);functions.add(function);System.out.println("第一次遍历结束");}mapperBean.setFunctions(functions);} catch (Exception e) {throw new RuntimeException("解析填充内容时遇到异常");}System.out.println("mapperBean="+mapperBean);return mapperBean;}

2.进行测试

随时进行一些测试

        com/stein/SteinConfigurationTest.java

    @Testpublic void readMapper(){SteinConfiguration steinConfiguration = new SteinConfiguration();MapperBean mapperBean = steinConfiguration.readMapperBean("MonsterMapper.xml");System.out.println("ok~~");}

输出结果

mapperBean=MapperBean(interfaceName=com.stein.mapper.MonsterMapper, functions=[Function(sqlType=select, functionName=getMonsterById, resultType=Monster(id=null, age=null, name=null, email=null, birthday=null, salary=null, gender=null), sql=select * from monster where id=?, parameterType=null)])


阶段7-实现动态代理Mapper的方法

完成动态代理生成Mapper对象

1.创建SteinMapperProxy

com/stein/steinmybatis/sqlSession/SteinMapperProxy.java

public class SteinMapperProxy implements InvocationHandler {private SteinConfiguration steinConfiguration;private String mapperFile;private SteinSession steinSession;public SteinMapperProxy(SteinConfiguration steinConfiguration,SteinSession steinSession,Class clazz) {this.steinConfiguration = steinConfiguration;this.mapperFile = clazz.getSimpleName();this.steinSession = steinSession;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {MapperBean mapperBean = steinConfiguration.readMapperBean(mapperFile + ".xml");String mapperBeanInterfaceName = mapperBean.getInterfaceName();System.out.println("mapperBeanInterfaceName = " + mapperBeanInterfaceName);if(!method.getDeclaringClass().getName().equals(mapperBeanInterfaceName)){return null;}List<Function> functions = mapperBean.getFunctions();//判断内容是否为空if(functions!=null && 0!=functions.size()){for (Function function : functions) {//判断是同一个方法时,就能执行对应的方法if(method.getName().equals(function.getFunctionName())){//如果执行的类型是select,就执行selectOne()方法if("select".equalsIgnoreCase(function.getSqlType())){return steinSession.selectOne(function.getSql(), String.valueOf(args[0]));}}}}return null;}
}

2.完善SteinConfiguration的readMapperBean()方法

/*** @param xmlFile 传入文件名,resource文件夹会在target目录中重建,直接获取即可* @return*/public MapperBean readMapperBean(String xmlFile) {MapperBean mapperBean = new MapperBean();InputStream stream = classLoader.getResourceAsStream(xmlFile);SAXReader saxReader = new SAXReader();try {Document document = saxReader.read(stream);Element rootElement = document.getRootElement();System.out.println("rootElement="+rootElement);/**<mapper namespace="com.stein.mapper.MonsterMapper" ><select id="getMonsterById" resultType="com.stein.entity.Monster">select * from monster where id=?</select></mapper>*///获取namespace的方法名if(!"mapper".equals(rootElement.getName())) {throw new RuntimeException("xml文件找不到<mapper>标签");}String interfaceName = rootElement.attributeValue("namespace");System.out.println("interfaceName="+interfaceName);mapperBean.setInterfaceName(interfaceName.trim());List<Function> functions = new ArrayList<>();/**<select id="getMonsterById" resultType="com.stein.entity.Monster">select * from monster where id=?</select>*///获取select的内容List elements = rootElement.elements();for (Object element : elements) {//System.out.println("进入元素遍历");Element ele = (Element) element;System.out.println("ele="+ele);Function function = new Function();//这儿取值为null,可以看出"select"并不是属性"attribute"//String sqlType = ele.attributeValue("select");String sqlType = ele.getName().trim();function.setSqlType(sqlType);System.out.println("sqlType="+sqlType);String functionName = ele.attributeValue("id");function.setFunctionName(functionName);String resultType = ele.attributeValue("resultType");//因为resultType是一个Object类型,这儿返回一个实例Object newInstance = Class.forName(resultType).newInstance();function.setResultType(newInstance);//sql语句也明显不是"attribute"//String sql = ele.attributeValue("sql");String sql = ele.getTextTrim();function.setSql(sql);//这儿的getFunctions并没有new ArrayList,所以是个null,导致出bug的罪魁祸首//mapperBean.getFunctions().add(function);functions.add(function);System.out.println("第一次遍历结束");}mapperBean.setFunctions(functions);} catch (Exception e) {throw new RuntimeException("解析填充内容时遇到异常");}System.out.println("mapperBean="+mapperBean);return mapperBean;}

3.测试

    @Testpublic void mapperProxy(){SteinSession steinSession = new SteinSession();MonsterMapper mapperProxy = steinSession.getMapperProxy(MonsterMapper.class);Monster monster = mapperProxy.getMonsterById(1);System.out.println("monster = " + monster);}

以上便是手动实现Mybaits的过程代码。

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

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

相关文章

Uniapp 之renderjs解决swiper+多个video卡顿问题

一、效果图二、示例代码 test.vue<template><view style"" :style"{height: windowHeightpx}"><swiper class"video-swiper" vertical change"swiperChange" :current"current" animationfinish"swiper…

设计模式之【快速通道模式】,享受VIP的待遇

文章目录一、快速通道模式简介1、简介2、适用场景二、示例1、JDK源码&#xff1a;ArrayList构造方法2、String.intern()方法3、缓存系统设计&#xff08;典型&#xff09;三、注意事项1、核心设计原则2、避坑指南参考资料一、快速通道模式简介 1、简介 快速通道模式是一种基于…

NineData云原生智能数据管理平台新功能发布|2025年7月版

本月发布 23 项更新&#xff0c;其中重点发布 8 项、功能优化 15 项。重点发布数据库 DevOps - 非表对象调试新增存储过程、函数、包的调试功能&#xff0c;支持对象编译、断点设置、执行控制&#xff08;continue/step into/step over&#xff09;、变量调试等全流程操作。数据…

APM32芯得 EP.29 | 基于APM32F103的USB键盘与虚拟串口复合设备配置详解

如遇开发技术问题&#xff0c;欢迎前往开发者社区&#xff0c;极海技术团队将在线为您解答~ 极海官方开发者社区​https://community.geehy.cn/ 《APM32芯得》系列内容为用户使用APM32系列产品的经验总结&#xff0c;均转载自21ic论坛极海半导体专区&#xff0c;全文未作任何修…

css过渡属性

前言 该属性用于元素各种 “改变” 后的过渡效果动画&#xff0c;包括但不限于颜色、宽高、缩放等。 如下图所示&#xff0c;使用过渡属性便可轻松完成。 示例代码 您可以直接复制运行&#xff0c;查看效果。 <div>demo</div>div {width:100px; height:100px;/* …

云计算核心技术之云存储技术

一、云存储技术1.1、云存储是什么从狭义上来说&#xff0c;云存储是指通过虚拟化、分布式技术、集群应用、网格技术、负载均衡等技术&#xff0c;将网络中大量的存储设备通过软件集合起来高效协同工作&#xff0c;共同对外提供低成本、高扩展性的数据存储服务。从广义上来讲&am…

在Ubuntu上安装并使用Vue2的基本教程

我也准备要尝试一些前端开发了&#xff01;发现网上有些教程写得挺好&#xff0c;但是还是有点老&#xff08;并且有点错误&#xff09;&#xff0c;所以这里更新一下&#xff1a; 主要参考了这篇教程&#xff1a;Vue2——1. 安装教程_vue2 cdn-CSDN博客 并且使用NPM方式进行…

任务十九 打包部署

一、本地打包部署 首先在自己的电脑上,下载一个nginx nginx: download 之后再vscode中,进行打包 输入命令 npm run build 打包过后,会在项目的根目录下,生成一个dist的文件夹

《飞算Java AI使用教程:从安装入门到实践项目》

前引&#xff1a;在当今人工智能技术飞速发展的时代&#xff0c;Java作为企业级开发的主流语言&#xff0c;正与AI技术深度融合。飞算Java AI是一款强大的工具集&#xff0c;旨在帮助开发者轻松构建和部署智能应用&#xff0c;涵盖机器学习、自然语言处理等核心功能。本教程将带…

NestJS 依赖注入方式全解

一、基础注入方式 1. 构造函数注入&#xff08;Constructor Injection&#xff09; 适用场景&#xff1a;模块间依赖传递&#xff0c;服务初始化时必须存在的依赖 实现方式&#xff1a;通过构造函数参数声明依赖&#xff0c;NestJS 自动解析并注入 Injectable() class UserServ…

完整源码+技术文档!基于Hadoop+Spark的鲍鱼生理特征大数据分析系统免费分享

&#x1f393; 作者&#xff1a;计算机毕设小月哥 | 软件开发专家 &#x1f5a5;️ 简介&#xff1a;8年计算机软件程序开发经验。精通Java、Python、微信小程序、安卓、大数据、PHP、.NET|C#、Golang等技术栈。 &#x1f6e0;️ 专业服务 &#x1f6e0;️ 需求定制化开发源码提…

云原生俱乐部-shell知识点归纳(1)

shell的内容也挺多的&#xff0c;虽然云原生课程主要是讲grep、sed、awk三剑客&#xff0c;但是还有结合循环结构&#xff0c;判断语句&#xff0c;以及函数等内容。还是有点复杂的&#xff0c;并且我对shell的掌握并不多&#xff0c;所以写的可能并不全。当然&#xff0c;如果…

设计模式(四)——责任链模式

1. 责任链模式的定义 责任链模式&#xff08;Chain of Responsibility&#xff0c;简称 CoR&#xff0c;也叫职责链模式&#xff09;是一种行为型设计模式&#xff0c;允许一个请求在一系列处理器&#xff08;handlers&#xff09;中传递。每个处理器可以选择自己处理该请求&am…

MyBatis-Plus基础篇详解

文章目录前言一、简单介绍MyBatis-Plus1.1 特性1.2 架构二、SpringBoot集成MyBatis-Plus2.1 项目搭建2.2 导入所需依赖2.3 配置application.yml2.4 创建实体类2.5 创建Mapper接口2.6 启动类配置三、DQL操作3.1 基础查询3.2 QueryWrapper查询3.3 LambdaQueryWrapper查询3.4 分页…

基于W55MH32Q-EVB 实现 HTTP 服务器配置 OLED 滚动显示信息

目录 1 前言 2 项目环境 2.1 硬件准备 2.2 软件环境 3.硬件连接和方案 3.1 硬件连接 3.2 方案图示 4.例程修改 1 前言 HTTP&#xff08;超文本传输协议&#xff0c;HyperText Transfer Protocol&#xff09;是一种用于分布式、协作式、超媒体信息系统的应用层协议&#xff0c; …

YggJS RLogin暗黑霓虹主题登录注册页面 版本:v0.1.1

项目介绍 yggjs_rlogin 是一个专注于 React 登录/注册页面的组件库。本文档介绍“暗黑霓虹”主题&#xff1a;#111 暗色背景 青蓝霓虹描边输入框 赛博朋克光效按钮。 安装说明 安装&#xff1a;pnpm add yggjs_rlogin react react-dom使用&#xff1a;从 yggjs_rlogin 引入组…

大数据毕业设计选题推荐:护肤品店铺运营数据可视化分析系统详解

&#x1f34a;作者&#xff1a;计算机毕设匠心工作室 &#x1f34a;简介&#xff1a;毕业后就一直专业从事计算机软件程序开发&#xff0c;至今也有8年工作经验。擅长Java、Python、微信小程序、安卓、大数据、PHP、.NET|C#、Golang等。 擅长&#xff1a;按照需求定制化开发项目…

【github-action 如何为github action设置secrets/environment】

Using secrets in GitHub Actions 在 GitHub Actions 中使用密钥 Learn how to create secrets at the repository, environment, and organization levels for GitHub Actions workflows. 学习如何在仓库、环境和组织级别为 GitHub Actions 工作流创建密钥。 Creating secre…

宝塔面板Docker安装n8n汉化中文

一、Docker安装N8N 安装配置默认即可&#xff0c;如果端口已被使用&#xff0c;可以自行更改 当状态为运行中时&#xff0c;就可以点击端口&#xff1a;访问N8N 填写完信息后&#xff0c;点击下一步&#xff08;邮箱要能接收邮件&#xff1a;接收密钥&#xff09; 点开始 点击发…

F003疫情传染病数据可视化vue+flask+mysql

编号:F003 文章结尾有CSDN官方提供的学长的联系方式&#xff01;&#xff01; 欢迎关注B站 ✅ vue flask 前后端分离架构 ✅ 实现中国地图、柱状图、折线图、水地图、环图等多种图形的echarts可视化分析 视频 vueflask爬虫 新冠疫情大屏实现 python 可视化分析项目源码1 系统…