文档章节

被关系数据库建表和升级折磨?因为你没用大道至简的Flyway

chentao106
 chentao106
发布于 02/29 19:43
字数 4138
阅读 8K
收藏 18

前言

    六年前Flyway已经是我TDD开发、持续集成工具栈中的重要一环了,作为早期用户,我早就应该为它做个”广告“,可惜对创业者来说时间太宝贵了,现在趁着疫情才有机会在家里总结点东西。虽然现在Flyway已经是Spring-Boot集成工具的一环,但是我发现还是少有人了解它的威力。

关系数据库之殇

    你在使用关系数据库的过程中,是否曾经遇到以下情况,甚至因此一度想要放弃或已经放弃关系数据库?

场景一:开发环境,多人共用一套数据库

开发正调试着,忽然代码报错“XX字段不存在”:谁TMD又把表结构给改了…

场景二:开发环境,每个人各自搭建自己的数据库

开发完一个功能,提交代码、更新,重启准备调试下,代码报错“XX表不存在”
吼一嗓子:谁又改表结构了?什么?每个人都要把xxx.sql执行一遍?
...
新员工:我要搭一套开发数据库,到底应该执行哪些SQL脚本?

场景三:开发转测试

测试:你看这个功能是不是有个Bug?
开发1:哦,你要执行一下这个SQL脚本。
测试:嗯,现在没问题了,但是怎么保证这个脚本没有Bug,我能再重现、测试一遍吗?
开发:额~,你重新搭一遍数据库吧...

场景四:搭建一套演示环境

执行SQL脚本1、SQL脚本2、SQL脚本3…启动服务失败!
什么?这个脚本N是测试版本的,war包是已经上线的版本?
删库再来一遍...

场景五:放弃关系数据库的坑

受不了关系数据库了,我们切MongoDB吧…嗯,控制台果然清静了
...
几个版本后:
生产环境某些数据查不到了、还有类型不匹配,神马?
A字段改过名,B字段换了个类型?

    如果上面的问题,你一个都没遇到过,要么是你所做的项目太简单,要么你们的开发流程非常规范,或者变更控制得太好了,本文你大可跳过了。

    如果你正被上面的问题困扰,你可能会因此想要入另一个坑:NoSQL,那么恭喜你将遇到更多的坑,比如关联查询问题、数据版本问题...

注:这里并不否定NoSQL的价值,各种NoSQL是关系数据库的良好补充。
但是如果想将NoSQL做为关系数据库的替代,那么你将会陷入比关系数据库还多的线上问题之中。

    如果你看到这里,说明你不想逃避问题,那么让我们一起来认识这个关系数据库升级管理的利器——Flyway

 

Flyway原理介绍

Flyway是什么?一句话概括,Flyway就是一个数据库版本管理组件。它的原理非常简单

  1. 项目启动时拉起Flyway,先检查数据库里面有没有Flyway元数据表,没有则创建;
  2. 检查Flyway元数据表中的记录,哪些脚本已经执行过,当前版本是什么;
  3. 查找代码中的(名称满足规则的)数据库升级脚本,找出版本号大于(Flyway元数据)当前版本的脚本,逐个执行并记录执行结果到Flyway元数据表。

    你没看错,以上三点就是Flyway最核心的功能,我深信熟练掌握我另一篇博客《TDD两小时实现自定义表达式模板解析器》同学不出一天,就能自己实现这三点功能,对非JVM开发者我推荐在理解以上思想的基本上自己开发一套。

    大道至简,最简单的设计往往是最有效的。通过以上功能,我们可以很容易做到:

  1. 代码与数据库建表&升级脚本放在一起同步管理,通过代码(SQL)就可以了解到表结构;
  2. 无须人工执行任何脚本,运行代码或服务即可完成(数据库表结构的)环境搭建;
  3. 从任一版本的环境(表结构),都可以通过运行指定(新)版本的代码或服务来自动升级到指定新版本;
  4. (配合内存数据库/Docker/清库脚本)数据库搭建&升级脚本很容易与代码一起反复测试。

总之,用上Flyway之后,关系数据库的”关系“,不再是限制你开发效率的瓶颈,反而成为开发&测试的必要约定,提升版本质量的重要保障

快速上手

不管工具多强大,如何用起来,是我们首要关心的,让我们以各种Java项目环境,来看一下如何在代码中将Flyway用起来,再由各位自己去细品Flyway对关系数据库版本管理带来的巨大改变。

注:所有示例都基于Maven,用Gradle的自己翻译下依赖,二者都不用的嘛...先研究下POM的依赖关系,再自己去下载jar包吧

1. 原生Java项目(不用Spring、Spring-Boot)

pom.xml文件中增加flyway的依赖:

    <dependency>
      <groupId>org.flywaydb</groupId>
      <artifactId>flyway-core</artifactId>
      <!--通常情况下推荐最新发布版,此处是从旧代码中复制-->
      <version>3.2.1</version>
    </dependency>

java代码拉起Flyway:

DataSource dataSource = ...
...
//在数据连接创建之后,其它代码运行之前,先调用Flyway升级
Flyway flyway = new Flyway();
flyway.setDataSource(dataSource);
flyway.migrate();
...

编写建表脚本和数据初始化脚本

src
|-main
  |-java
  |-resources
    |-db
      |-migration
        |-V0.0.1__init-schema.sql
        |-V0.0.2__init-data.sql
注:脚本中的内容,就是正常的建表脚本,或者对上一版本的表结构变更、数据升级。

2. Spring项目

pom.xml文件中增加flyway的依赖(与原生java项目一样):

    <dependency>
      <groupId>org.flywaydb</groupId>
      <artifactId>flyway-core</artifactId>
      <!--通常情况下推荐最新发布版,此处是从旧代码中复制-->
      <version>3.2.1</version>
    </dependency>

Spring配置文件拉起Flyway:

<!-- 创建Flyway的bean,并调用其migrate方法 -->
<bean id="flyway" class="org.flywaydb.core.Flyway" init-method="migrate">
  <!-- 脚本文件校验和验证默认开启,可以防止脚本被修改。请视情况关闭校验 -->
  <property name="validateOnMigrate" value="false" />
  <property name="dataSource" ref="dataSource" />
</bean>

编写建表脚本和数据初始化脚本(与原生java项目一致)

src
|-main
  |-java
  |-resources
    |-db
      |-migration
        |-V0.0.1__init-schema.sql
        |-V0.0.2__init-data.sql
注:脚本中的内容,就是正常的建表脚本,或者对上一版本的表结构变更、数据升级。

3. Spring-Boot项目

Flyway已经被Spring-Boot整合,成为Spring标准的数据库升级工具,在Spring-Boot中使用Flyway更简单,只需添加依赖、编写数据库脚本即可,省去了拉起这一步。

pom.xml添加依赖(Spring-Boot已经整合,无须版本号)

    <dependency>
      <groupId>org.flywaydb</groupId>
      <artifactId>flyway-core</artifactId>
    </dependency>

如果你使用IDEA,其还为你提供了创建Flyway升级脚本的功能,直接以当时日期时间为你生成SQL升级脚本:

生成的脚本名称如下:

src
|-main
  |-java
  |-resources
    |-db
      |-migration
        |-V20190315174656__init-schema.sql
        |-V20190315201742__init-data.sql
        |-V20191225205157__update-userid-to-bitint.sql

4. 升级脚本示例

为Flyway编写的SQL脚本并没有什么特殊的要求,与正常SQL并无二致,只不过每个脚本编写时考虑的永远是对前一个版本表结构的升级,这也是传统方式下严谨的升级脚本应该满足的要求。

建表脚本示例:

BEGIN;

-- 新数据库建表
CREATE SCHEMA IF NOT EXISTS staff;

CREATE TABLE IF NOT EXISTS staff.staffs
(
    id             BIGINT AUTO_INCREMENT NOT NULL,
    staffId        VARCHAR(10)           NOT NULL,
    createTime     TIMESTAMP             NOT NULL,
    lastUpdateTime TIMESTAMP             NOT NULL,
    name           VARCHAR(50)           NOT NULL,
    PRIMARY KEY (id)
);

CREATE SCHEMA IF NOT EXISTS duty;

CREATE TABLE IF NOT EXISTS duty.onDutyDef
(
    id        BIGINT AUTO_INCREMENT NOT NULL,
    name      VARCHAR(50)           NOT NULL,
    startTime TIME                  NOT NULL,
    endTime   TIME                  NOT NULL,
    PRIMARY KEY (id)
);

CREATE TABLE IF NOT EXISTS duty.breakDef
(
    dutyId    BIGINT      NOT NULL,
    name      VARCHAR(10) NOT NULL,
    startTime TIME        NOT NULL,
    endTime   TIME        NOT NULL,
    PRIMARY KEY (dutyId, name)
);

-- 插入默认配置数据
INSERT INTO duty.onDutyDef
    (name, startTime, endTime)
VALUES ('普通班', '09:00:00', '18:30:00');

INSERT INTO duty.breakDef
    (dutyId, name, startTime, endTime)
VALUES (1, '午餐', '12:30:00', '14:00:00'),
       (1, '晚餐', '18:30:00', '19:30:00');

COMMIT;

升级脚本示例

BEGIN;
-- 已有表字段变更
ALTER TABLE duty.signrecords ADD COLUMN clientId VARCHAR(40);
ALTER TABLE staff.staffs ADD COLUMN supervisor VARCHAR(100);
ALTER TABLE staff.staffs ADD COLUMN password VARCHAR(64);

-- 数据升级
UPDATE staff.staffs SET supervisor='00001,00002';
UPDATE staff.staffs SET password='8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92';


-- 新增表
CREATE SCHEMA IF NOT EXISTS users;

CREATE TABLE IF NOT EXISTS users.userroles (
  staffId          VARCHAR(15) NOT NULL,
  rolename         VARCHAR(15) NOT NULL,
  PRIMARY KEY (staffId, rolename)
);

-- 新增表补充默认数据
INSERT INTO users.userroles(staffId,rolename)
VALUES
('00001','hr'),
('00001','supervisor'),
('00002','hr'),
('00002','supervisor'),
('00003','supervisor');

COMMIT;

推荐的搭档

根据这么多年我的使用经验,有下面这些用法,可以最大地发挥Flyway的作用

1. H2+Flyway

H2是一个纯Java实现的类似Derby的数据库,其最大的特点有三个:

  • 纯Java实现,只需在代码中引入一个jar文件,你就有了一个数据库;
  • 支持内存、文件、C/S三种模式;
  • 对SQL标准的兼容,以及对其它数据库的兼容模式。

以上三个特点,决定了它特别适合做持续集成,或者一键部署的项目:

  • 开发环境用内存模式,配合Flyway自动建表,与TDD一起带来极速的开发体验;
  • 测试环境用内存模式或文件模式,自动化测试一天重新运行个一百遍不是梦;
  • 生成环境用文件模式或C/S模式,(Web)服务器动态扩容也不是问题。

如果配合Spring-Boot的Profile机制,一套代码在开发环境、测试环境、生产环境完美无暇地切换:

#application.properties:
#主配置文件中配置好所有默认参数
spring.profiles.active=dev
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.initialize=false
flyway.baseline-version=0.0.0
flyway.baseline-on-migrate=true
flyway.validate-on-migrate=false

#application-dev.properties:
#开发环境使用内存模式,支持一键运行
spring.datasource.url=jdbc:h2:mem:kq

#application-test.properties:
#测试环境使用内存模式或文件模式,支持反复运行或数据持久化
spring.datasource.url=jdbc:h2:~/data/h2/kq
#保密要求高时,请使用JAVA虚拟机参数配置账号密码,如: -Dspring.datasource.username=test
spring.datasource.username=test
spring.datasource.password=123456

#application-prod.properties:
#生产环境建议所有数据库参数都使用JAVA虚拟机参数配置
spring.datasource.url=jdbc:h2:/var/lib/h2/kq
#尤其是账号密码,一定不要写死在配置文件中
#使用JAVA虚拟机参数配置账号密码,如: -Dspring.datasource.username=test

#开发环境运行项目:
mvn spring-boot:run

#测试环境运行项目:
mvn spring-boot:run -Dspring.profiles.active=test

#生产环境通过jar包运行项目:
java -jar kq.jar -Dspring.profiles.active=prod -Dspring.datasource.username=secret ...

#生产环境部署在tomcat下,在setenv.sh中配置参数:
#tomcat/bin/setenv.sh:
JAVA_OPTS="${JAVA_OPTS} -Dspring.profiles.active=prod"
JAVA_OPTS="${JAVA_OPTS} -Dspring.datasource.username=secret"
JAVA_OPTS="${JAVA_OPTS} -Dspring.datasource.password=secret"
...

 

2. H2+Postgresql+Flyway

    H2在开发环境和一些小项目中,是一个非常好的选择,尤其是其内存或文件模式由于没有网络开销,启动、运行快得一塌糊涂。但是当数据量达到百万级时,其性能就明显不如RDBMS头部的几大C/S数据库了。

    由于H2和Postgresql对SQL标准的良好兼容性,从H2切换到Postgresql并不是难事,这使我们在同一个项目中享受H2+Flyway带来的极速开发模式,和Postgresql的稳定和大数据量的支持并不冲突,同样配合Spring-Boot我们可以这样配置:

#application.properties:
#主配置文件中配置好所有默认参数
spring.profiles.active=dev
spring.datasource.initialize=false
flyway.baseline-version=0.0.0
flyway.baseline-on-migrate=true
flyway.validate-on-migrate=false

#application-dev.properties:
#开发环境使用内存模式,支持一键运行
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:mydb;MODEL=;MODE=PostgreSQL

#application-sit.properties:
#自动化测试环境使用内存模式,支持一键运行
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:mydb;MODEL=;MODE=PostgreSQL

#application-uat.properties:
#用户模拟测试使用postgresql数据库,保证代码与postgresql的兼容性
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localsrv:5432/mydb
#保密要求高时,请使用JAVA虚拟机参数配置账号密码,如: -Dspring.datasource.username=test
spring.datasource.username=test
spring.datasource.password=123456

#application-prod.properties:
#生产环境建议所有数据库参数都使用JAVA虚拟机参数配置
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.url=jdbc:postgresql://localsrv:5432/mydb
#尤其是账号密码,一定不要写死在配置文件中

#开发环境运行项目:
mvn spring-boot:run

#自动化测试环境运行项目:
mvn spring-boot:run -Dspring.profiles.active=sit

#用户模拟测试环境运行项目:
mvn spring-boot:run -Dspring.profiles.active=uat

#生产环境通过jar包运行项目:
java -jar kq.jar -Dspring.profiles.active=prod -Dspring.datasource.username=secret ...

#生产环境部署在tomcat下,在setenv.sh中配置参数:
#tomcat/bin/setenv.sh:
JAVA_OPTS="${JAVA_OPTS} -Dspring.profiles.active=prod"
JAVA_OPTS="${JAVA_OPTS} -Dspring.datasource.username=secret"
JAVA_OPTS="${JAVA_OPTS} -Dspring.datasource.password=secret"
...

需要注意的是,存储过程、非SQL标准的类型、函数二者还是有差异。如果没用到这些,可以不改一行代码在H2和Postgresql之间平滑切换;如果用到了特殊的函数,可以通过Java来扩展H2函数来保证SQL与Postgresql一致;存储过程和特殊类型,就要大家自己去研究了。

3. Docker+Mysql/Postgresql+Flyway

    自从六年前用上Docker,我就喜欢上它一句命令就运行/停止/重置/"卸载"一套开源软件(如数据库、Web服务)的能力。通过Docker可以不留痕迹地尝试或使用绝大部分开源的新东西而不需要安装/卸载它们,加上现在Docker对Windows、Mac、Linux的全面支持,现在要用什么软件我首先会去找有没有现成的Docker镜像。

    使用Docker+Flyway来做持续集成(数据库的反复重建),也是一个非常好的选择,Docker配置一两句脚本,就可以达到安装或重置数据库的效果:

#删除已经存在的数据库容器,即使不存在也没什么影响
docker rm mydb
#启动数据库容器
docker run --name=mydb \
  -p 5432:5432 \
  -d --restart=unless-stopped \
  -e POSTGRES_USER=${dbuser} \
  -e POSTGRES_PASSWORD=${dbpwd} \
  -e POSTGRES_DB=${database} \
  postgres:alpine

使用Docker还带来另一个好处——开发、测试、生产环境使用相同的镜像,则可以保证三者环境的一致,不太会遇到环境相关的问题。

 

使用Flyway需要考虑的问题

当然,想要用好Flyway,有些问题也需要提前考虑:

  • 如何在现有项目上使用Flyway

    我也是从项目开始一段时间之后,才开始使用Flyway的,上线Flyway的时候并没有遇到比手工执行脚本更困难的事情。现有项目想要使用Flyway,可以遵循以下三步:

  1. 导出现有数据库的建表脚本,和新项目必须的基础数据,放入Flyway升级脚本中,如:src/main/resources/db/migration/V0.0.0__init.sql
  2. 根据项目实际情况,按前述方法引入Flyway;
  3. 在Flyway配置中,在调用migrate之前,设置baselineVersion为一个大于步骤1中版本的值,如:V0.0.1。

baselineVerion参数设置方法,同样分Java代码、Spring Xml配置、Spring-Boot配置文件三种,看到这里你一定有能力自己去设置,我就不在复述。

  • 升级脚本越来越多怎么办

当经历几十个版本之后,"src/main/resources/db/migration"下面脚本越来越多,带来两个问题:

  1. 脚本太多,难以维护;
  2. 通过SQL脚本无法直观地看出最新的表结构。

我的经验和建议是:

  1. 关闭Flyway的脚本文件校验和检查,即设置validateOnMigrate=false;
  2. 定期将合并历史脚本,比如将V1.0.1,V1.0.2,V1.0.3的脚本都合并到V1.0中,并删除前三个文件。

脚本合并非Flyway才需要的技能,我有一些技巧让SQL更简洁:

  1. 将ALTER语句合并到CREATE语句中;
  2. DROP与CTEATE+ALTER抵消;
  3. UPDATE视情况合并到INSERT语句中(基础数据升级),或者直接去掉(业务数据升级)。

结语

    如果你在用关系数据库,赶紧把Flyway用起来,开始你的极速编码体验吧!

    如果你因为RDBMS数据升级问题而切换到NoSQL,那赶紧换机会切回RDBMS,用Flyway来管理升级吧!要不然更多线上Bug正等着你!

参考资料

 

© 著作权归作者所有

chentao106

chentao106

粉丝 14
博文 5
码字总数 15028
作品 0
深圳
私信 提问
加载中

评论(21)

pooo
pooo
用过liquibase,感觉也挺好用的
mlovewt
mlovewt
我们在使用中遇到一个问题,就是执行flyway的机器不同,生成的hash也不一样,比如同样的脚本,a启动执行完脚本后,b就直接没法启动了,校验脚本和库里的记录表不一致,后来就直接去掉flyway了
chentao106
chentao106 博主
请检查下,是否打包时编码不同、或操作系统换行符不一致造成的,校验和算法不可能是机器相关的,更可能是文件内容无意中发生了变化。而且即使问题解决不了,还有一个下策,就是关闭validateOnMigrate开关,多研究&看文档。
idoz
idoz
将数据库的用户密码写在启动脚本(写.env同样)或者是yml等配置文件是不安全的。尽管您不是使用root启动应用,有人登录您的应用服务器,受害的是您的数据库服务器;一般应用都需要update权限滴 ...
chentao106
chentao106 博主
你建议密码写在哪里呢?
idoz
idoz
带有https的configure server 中
chentao106
chentao106 博主
configure server并没有增加安全性啊,只是集中管理配置而已(大规模服务的不得已选择),某种程度上可以说增加了配置管理的复杂性、降低了安全性(不同微服务共享一套配置服务的情况下)。 仅从配置管理的安全性来说,敏感配置不能写到yml和properties是必须的;而不管环境变量、配置服务器,都应该用管理手段,控制人员对特定(测试、生产)服务器的访问。
idoz
idoz
增加了配置管理的复杂性、降低了安全性, 您不觉得有哪个地方不对劲么?
idoz
idoz
写在环境变量中,一个top命令就可以看到喽。我想说的是:当你的应用处于某种风险中时(风险可能来自于内部、操作系统或应用本身的漏洞),请不要将这个风险传播给你的数据服务。一般来说,提高安全性就会增加复杂性,如果反了,那就要从自身找问题喽。
chentao106
chentao106 博主
嗯,这点确实有道理,并且是我没想到的。 光top看不到,ps可以:ps -ef | grep java 有问题解决问题,还是两点:1. 管理上无论如何要隔离;2. 宏服务可以利用Spring指定配置文件的机制,微服务除了集中化配置服务,也没什么更高效的方案。
Maxwell1987
Maxwell1987
都能 top 了,curl 一下也不是什么难事了。密码集中存储是增加了一定的安全性,也没安全到这个程度了。
idoz
idoz
回复 @chentao106 : 按下C试试
idoz
idoz
回复 @Maxwell1987 : 1. curl看到的是加密后的数据 2. curl需要证书,否则是不能连接到配置服务器的。
Maxwell1987
Maxwell1987
回复 @idoz : 都能 top 了,就能找到程序,你是想静态找还是动态找是个难事吗?
idoz
idoz
回复 @Maxwell1987 : 是难事。
idoz
idoz
回复 @Maxwell1987 : 找到程序只能瞪瞪眼而已
Maxwell1987
Maxwell1987
回复 @idoz : 太赞了,在您的世界里按您的规矩来。
idoz
idoz
回复 @Maxwell1987 : 给我一个环境,Debian、OpenBSD、FreeBSD , CentOS这四个哪一个都行, 我启动程序运行,如果你能动我程序,(如停止、重启、杀死进程)算我输。 注:(拔掉电源这种方式算你输)
Maxwell1987
Maxwell1987
回复 @idoz : 这个环境还得先入侵进去但是只能 top?
idoz
idoz
回复 @Maxwell1987 : 不用,除了root权限外,你什么都能做。
OSC_EmQwRw
OSC_EmQwRw
这种只适合非生产环境,生产环境还是单独搞sql脚本上线,数据量大了比如加索引、更改字段等等一些操作会锁表,影响业务
数据库版本管理工具之flyway

1.引言 随着项目不断的增大,尤其是一个在不断开发完善的项目,随着需求变化,数据库的schema也会跟着变化,数据库也需要不断的扩充,加表加字段,(每一次的增加称作一次DB的迁移migration...

双月通天
2016/04/05
1.1K
0
数据库版本管理工具Flyway——基础篇

api:http://flywaydb.org/getstarted/firststeps/api.html 1. 引言 想到要管理数据库的版本,是在实际产品中遇到问题后想到的一种解决方案,当时各个环境的数据库乱作一团,没有任何一个人(...

引鸩怼孑
2015/09/09
366
0
数据库版本管理工具Flyway应用

Flyway介绍 Flyway是一款开源的数据库版本管理工具,它更倾向于规约优于配置的方式。Flyway可以独立于应用实现管理并跟踪数据库变更,支持数据库版本自动升级,并且有一套默认的规约,不需要...

吴伟祥
2019/03/26
92
0
简化 Spring Boot 项目部署,Flyway 搞起来

虽然我之前录了一个微人事(https://github.com/lenve/vhr)部署视频(新版微人事部署教程来啦),但是由于这次升级涉及到了 Redis 和 RabbitMQ,所以在本地跑微人事还是一件比较麻烦的事情,有...

江南一点雨
02/20
0
0
Flyway:数据库版本迁移工具的介绍

Flyway介绍 Flyway的定位:数据库的版本控制。 用一种简单、干净的方案,帮助用户完成数据库迁移的工作。使用Flyway,用户可以从任意一个数据库版本迁移到最新版本,简单而且有效。 支持多个...

heyikan
2019/06/01
0
0

没有更多内容

加载失败,请刷新页面

加载更多

结束数据库会话期间(踢出用户)

--可以使用这堆,查询出正在使用的会话期间,然后结束某个会话idUSE mastergoSELECT * from sysprocesses where dbid in (select dbid from sysdatabases where name='hbposv10_branch......

ethanleellj
26分钟前
30
0
TMS320C28x系列TMS320F2837x开发板的蜂鸣器、直流电机和步进电机接口

处理器 TI TMS320F2837x单/双核具有200MHz的高速处理能力,双核拥有多达12路的PWM输出。以下分别是TMS320F2837x单/双核CPU资源框图: 蜂鸣器 本开发板搭载有无源蜂鸣器,可以发出不同频率的声...

Tronlong创龙
27分钟前
29
0
Python3 撸代码窍门,怎样用 Map, Filter, Reduce 代替 For 循环.

感谢作者分享-http://bjbsair.com/2020-04-07/tech-info/30736.html 你是否有过这样的经历,你查看自己写的代码并看到满眼的 for 循环?你发现你必须斜着你的眼睛,并将脑袋前倾到你的显示器...

曹长卿
28分钟前
31
0
vscode插件

1、vetur 强大的vue开发插件 等待更新!!!!

米依若兮
28分钟前
25
0
服务器批量管理软件排名 批量管理vps

远程桌面是微软公司为了便于网络管理员管理维护服务器推出的一项服务。从windows 2000 server版本开始引入,网络管理员时候远程桌面连接器连接到网络任意一台开启了远程桌面控制功能的计算机...

09网络2
30分钟前
31
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部