开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

使用了这个数据库神器,让我工作效率提升了数倍

发表于 2021-07-13

前言

对于开发、测试、DBA、运维来说,数据库再熟悉不过了。

很多时候,我们需要在本地电脑上,直接连接开发或测试环境的数据库,方便对数据进行增删改查。当然很多数据库都提供了自带的客户端,比如mysql的客户端是这样的:

图片

用过的人基本都会吐槽,太low了,极其不好用。

  1. 写sql语句不方便,没有提示功能,特别是如果sql语句比较复杂,不能格式化,会看得人头皮发麻。
  2. 看数据不方便,如果数据比较多,整屏的数据显得有点凌乱。如果字段中包含json格式的数据,基本看不了。
  3. 对表的dml和ddl操作,都必须通过命令,低效率,而且容易出错。
  4. 很难看出表和表之间是怎样关联的。
  5. 导入导出数据就更不方便了。

有没有一款好用的工具,让我们能更高效的操作数据呢?

这就是今天我要推荐给大家的数据库神器:Navicat Premium。

下面我们一起看看Navicat Premium有哪些牛逼的功能。

  1. 支持多种数据库

在连接数据库之前,我们需要知道Navicat Premium支持哪些数据库。

点击工具栏中Connection上的加号:图片)就会弹出如下窗口:图片从图中它支持7种常规数据库,包括我们经常使用的mysql、sql server、oracle,还有mongodb等。此外,还支持8种数据库云。

  1. 轻松管理连接

2.1 连接数据库

之前我们通过mysql客户端连接mysql服务端时,需要通过命令行,比如:

1
ini复制代码mysql -h127.0.0.1 -uroot -p123456;

每次都需要输入连接地址、用户名、密码,不觉得烦吗?

恭喜你,Navicat Premium自带保存密码的功能:图片只需第一次连接数据库时配置:Connection Name、Host、Port、User Name,然后勾选 Save password即可。

以后就能通过界面双击Connection Name,直接访问数据库了:图片nice。

2.2 显示指定实例

有时候,同一个数据库连接下的实例很多,但只有少部分是我们真正需要关心的。我们想要过滤出这部分我们关心的实例,该怎么做呢?如果每次都从上百个实例中找一个,会不会有点太浪费时间了?

这时可以在databases tab页下勾选 Use custom database list:图片勾选我们想看到的数据库实例。

这样重新访问数据库连接的时候,会发现左边实例列表中只展示了,我们选择的数据库实例。图片

  1. 方便查询表信息

以前我们查看某个数据库实例拥有哪些表,一般通过如下命令:

1
ini复制代码show tables;

图片

不好意思,那已经是过去式了。

现在只需双击Tables节点,展开即可。除此之外,还能非常直观的看到试图、函数、事件、备份等功能。

如果想看数据,选中双击某张表就可以了:图片

如果想看表信息,可以通过右边的窗口:图片里面能清楚的看到表中的这些信息:记录行数、数据占用磁盘空间的大小、存储引擎、创建日期、修改日期、字符集等。

如果想看表结构,只需切换成ddl选项:图片这样就能看到该表完整的ddl语句。

比如:之前已经在dev环境创建了表,通过该功能快速获取ddl语句,然后复制到st环境执行,简直太棒了。

当然我们通过如下命令:

1
sql复制代码show create table jump_log;

也可以看到同样的效果,但是在Navicat Premium中只需动动鼠标就能搞定,确实更方便。

  1. 数据库实例的ddl操作

通常情况下,我们对数据库的ddl操作,其实不多。创建了数据库,很少去修改和删除。这些操作虽说有点食之无味,但确实弃之可惜。

右键单击某个数据库实例,就会弹出如下窗口:图片

里面包含了数据库实例的:创建、修改和删除操作,同时还能打开、关闭该实例。

4.1 数据库的创建和修改

创建和修改实例时,只需要输入以下三个字段:图片

4.2 mysql客户端

在右键单击某个数据库实例,出现的弹窗中如果选择Console,能调出mysql客户端:图片

4.3 执行外部脚本

如果选择Execute SQL File,能执行外部的sql脚本:图片

4.4 表导出

如果选择Dump SQL File,能导出所有表的数据:图片目前支持两种:

  1. 导出表结构 和 数据
  2. 只导出表结构

最终会导出到xxx.sql文件中:图片从图中看出,它最终生成的是能直接执行的sql脚本,如果选择的是Structure + Data,则它里面既包含表的创建语句,又包含数据初始化语句。而如果选择的是Structure Only,则sql脚本中只会包含含表的创建语句。

4.5 打印数据库

如果选择print database,能生成pdf文件:图片里面包含了表的信息,方面打印出来。

4.6 转换model

如果选择Reverse Database To Model,能弹窗表的model窗口:图片表的名称、表的字段,已经表之间的关联关系,通过这个model窗口能够非常直观的看明白。这个功能有点强喔。

4.7 找到数据

如果选择Find in database,能从整个数据库实例的所有表中,查找某个关键的数据:图片比如由此有个需求,要将数据库中所有的域名A,替换成域名B。以后我们需要查所有表的所有字段,是否包含域名A,然后才能找出有哪些表需要修改数据,相当麻烦。但如果有了Navicat Premium的这个查找功能,能够迅速减少我们的工作量。哇塞,太厉害了吧?

  1. 表的ddl操作

相对于数据库实例的ddl操作,表的ddl操作使用频率会更高一些。选中某张表右键点击一下,就会弹窗如下窗口:图片

如果选择Open Table,会弹出查看数据的窗口:图片这个功能后面会详细说。

5.1 表的设计窗口

如果选择Desgin Table,会弹窗表设计窗口,默认的tab是Field:图片在这个窗口中我们能定义表的字段,包含:字段名称、字段类型、字段长度、字段精度、是否允许为空、是否主键、字段描述、设置默认值等等。

它支持的字段类型有:图片实在太多了,列举不完。

如果在该窗口中选择Index tab页,则会弹出索引设计窗口:图片在这个窗口中我们能定义:索引名称、所属字段、索引类型、索引方法和索引描述等。此外,还可以建立:全文索引、普通索引、唯一索引等多种索引。

如果选择Options tab页,则会弹窗表信息的窗口:图片可以设置存储引擎、字段增长基础值、字符集等属性。

Comment选项主要是为了描述一下该表的功能,由于过于简单,此处省略截图了。

如果选择SQL Preview,也能看到该表完整的ddl语句:

图片

5.2 表的删除

上面重点介绍的是表的设计窗口,通过它我们能创建和修改表,那么表的删除呢?

主要是通过如下三个功能实现的:图片

  1. Delete Table:物理删除表
  2. Empty Table:清空表中的数据,但表任然保留
  3. Truncate Table:逻辑删除表

5.3 表的复制

有时候,我需要复制某张表,做备份。或者为了节省时间,基于某张已有的表创建新表。如果选择Duplicate Table,会出现如下选项:图片

如果接下来选择:Structure + Data,则表示复制表结构和数据,数据库会自动创建一张新表:

图片

里面包含的数据跟原表一样:图片

如果选择:Structure,则只复制表结构,数据库也会自动创建一张新表,但数据是空的:图片

5.4 数据导入

在实际工作当中,我们经常需要导入数据。

比如有一批新的用户数据需要导入数据库,这时可以选择Import Wizard,会弹如下窗口:图片目前Navicat Premium允许导入文件格式包括:dbf、db、txt、cvs、xls、xlsx、xml、json等。

我们以excel文件为例,假设现在接到需求,有这样的数据需要导入数据库:图片

可以新加一张表字段跟其对应:图片)然后选择刚刚的excel文件上传:图片)当然还需要选择该excel文件中的sheet,对应具体上传到哪张表:图片)接下来,还需选择excel文件的字段相对应的表中的字段:图片)然后发现已经导入成功:图片)查一下表,已经有数据了:图片

5.5 数据导出

在实际工作当中,数据导出的需求偶尔也会遇到。

这时可以选择Export Wizard,会弹如下窗口:图片我们选择导出到SQL script file中,然后会弹窗如下窗口:

图片可以选择要导出的表和对应的sql文件名称。

接下来,选择要导出的字段,允许只导出某些指定字段:图片)然后发现已经导出成功:图片)在这个路径下多了一个category.sql文件:图片其内容是很多insert语句,这不是我们一直想要的初始化脚本吗?

  1. 表的查询操作

查询操作可能是数据库中用得最多的操作,因为绝大多数功能,都是读多写少。

选中某张表,点击工具栏中的New Quer按钮,就会出现右边查询窗口:图片

6.1 执行sql语句:

在窗口中可以选择一个数据库连接 和 数据库实例:图片)在中间的空白区域,我们能编写sql语句:图片点击图中的类似箭头的按钮,就能执行该sql,执行结果会在下方的Result中显示出来。

虽说该窗口的名称是查询窗口,但并非仅仅支持查询操作,还可以执行其他操作,比如:update、delete、alter、drop等等。图片

6.2 保存sql语句:

有时候我们新建了一个查询窗口,里面写了很多sql语句,但由于急着关电脑,又想保存这些sql,避免丢失重写的情况发生。

这时我们如何保存该sql语句呢?图片只需点击上图中的保存按钮。

这样以后就能在Queries中非常方便得找到该sql了:图片

6.3 快速关联表

我们写查询sql如果涉及到多张表的时候,通常需要通过join或者子查询做关联。

图片这种方式不是说不行,但我想说的是,如果需要关联的表太多,这样sql效率比较低。

难道还有更高效的方式?

点击下图中的按钮:图片

会弹窗如下窗口:图片在该窗口的左边选择需要做关联的表,选择完之后会表会自动出现在右边窗口中。

然后在右边窗口中选择做的关联:图片点击ok之后,会自动生成sql语句:

图片太牛逼了。

6.4 格式化sql语句

有时候,我们写的sql很长,而且比较复杂,一眼根本没办法看出层次结构。虽说该sql能够正常运行,但每次看到它的时候,心头可能会一紧,比如:图片

看起来非常不直观,这时可以点击下图的按钮优化该sql:图片)优化后的效果如图:图片感觉瞬间优雅多了。

6.5 执行计划

有时候,我们想查看某条sql的执行计划,通常情况下需要在该sql语句前,加explain关键字,比如:

图片)这样执行后,就能看到执行计划了:图片

但还有更简单的方法,就是在执行sql之前,点击下图中的按钮:图片我们看到没有加explain关键字,但同样出现了执行计划。真的帅呆了。

6.6 切换展示方式

我们查询出来的数据,默认是用表格的方式展示的。如果字段中的内容非常少,这种方式还是比较好的。但如果某个字段的内容很多,比如extra字段是json类型的,里面层次结构很复杂:图片该字段展示的数据显示不太完整。

这时可以切换到form view模式:图片

然后在弹窗的窗口中点击下图中按钮:图片就会在下方出现完整的数据了。

6.7 新增和删除数据

我们在新增数据时,只需点击下图中的按钮:图片就会在一行空数据,然后在里面填值即可。

同样,我们在删除数据时,只需选中要删除的数据,然后点击下图中的按钮,就能轻松搞定:图片当然为了安全考虑,会先弹出一个确认窗口。

其实Navicat Premium的功能还有很多很多,我不可能一一列举完,在这里只是抛砖引玉,如果朋友们想了解更多功能可以找我私聊。

最后说一句(求关注,别白嫖我)

如果这篇文章对您有所帮助,或者有所启发的话,帮忙扫描下发二维码关注一下,您的支持是我坚持写作最大的动力。

求一键三连:点赞、转发、在看。

关注公众号:【苏三说技术】,在公众号中回复:面试、代码神器、开发手册、时间管理有超赞的粉丝福利,另外回复:加群,可以跟很多BAT大厂的前辈交流和学习。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

怎样防止同事用 QQ 邮箱提交公司代码?

发表于 2021-07-13

事情是这样的,最近组里新建了一个代码仓库来开发一个新的产品,再加上今天北京下大雨很多同事选择在家工作(包括我也是),于是我就选择用自己的个人电脑来工作。

但我的个人电脑里面的 Git 信息是用的我自己的个人邮箱:

1
2
Bash复制代码git config --global user.name "Germey"
git config --global user.email "cqc@cuiqingcai.com"

这两行命令大家用过 Git 的肯定都敲过对吧?

这个配置是全局生效的,所以如果我用 Git 的 commit 命令来提交代码的话,那么 commit 的名字和邮箱就会变成刚才我配置的个人信息。

然后如果把代码推送到公司的代码仓库里面,里面就会出现一个奇奇怪怪的用户名和头像,就像这样子:

image.png

图中上面两次 commit 就是我用个人电脑提交的,最后的那次 commit 是我上周在公司用公司电脑提交的。

这是不是很奇怪?

如果其他人也用的个人邮箱提交,那公司代码库里面就会出现各种怪怪的提交人的记录,无从知晓。

这肯定不能忍啊,以后要是有谁写了奇怪的代码都不好查是谁写的。

于是乎,我灵机一动,想:为何不在提交代码的时候做一个限制呢?

能做到吗?当然可以!

Git Hook

这里就介绍一个知识点 - Git Hook,它的意思就是在 Git 各种事件执行前和执行后执行一些自定义的逻辑,比如说,我们定义一个 pre-commit 的 Git Hook,那就能在 commit 之前执行一些操作,我们定义一个 post-push 的 Git Hook,那就能在 push 操作之后执行一些操作。

有关具体的内容可以参考官方文档:git-scm.com/book/en/v2/…

好,那这里我其实就是需要在 commit 之前做一下 Git 信息检查就好了,比如检查配置的邮箱不是工作邮箱,那就不允许执行 commit,所以就不会出现奇奇怪怪的 commit 记录了。

实操

说干就干。

配置 Git Hook 的工具有很多,Git 有原生支持,当然我们也可以用第三方库来做。

目前我们的代码仓库是基于 Node.js 开发的,所以 Node.js 的项目配置 Git Hook 比较流行的解决方案就是 husky,所以这里我也用 husky 来做了。

首先安装下 husky:

1
Bash复制代码yarn add husky

然后配置一个 Node.js 的 prepare 命令,这个命令可以在装完包 Node.js 包之后自动执行,所以 prepare 命令就配置成 husky 初始化的脚本,package.json 里面增加如下配置:

1
2
3
4
5
6
Bash复制代码{
"scripts": {
...
"prepare": "npx husky install"
},
}

OK,这样的话,其他人如果 clone 了这个仓库,装完所有 Node.js 包之后就会自动初始化 husky 的配置,然后在项目本地生成一个 .husky 的初始化目录,这样 Git Hook 就生效了。

Git Hook 生效之后,所有定义在 .husky 目录下的 Hook 脚本都会被应用,比如如果在 .husky 目录下添加一个 pre-commit 的脚本,那执行 commit 的之前,该脚本就会被预先执行做一些检查工作。

所以 .husky 目录下我就创建了一个 pre-commit 的脚本,写入了如下内容:

1
2
3
4
5
6
7
8
9
Bash复制代码EMAIL=$(git config user.email)
if [[ ! $EMAIL =~ ^[.[:alnum:]]+@microsoft\.com$ ]];
then
echo "Your git information is not valid";
echo "Please run:"
echo ' git config --local user.name "<Your name in Microsoft>"'
echo ' git config --local user.email "<Your alias>@microsoft.com"'
exit 1;
fi;

这是一个 Linux Shell 脚本,完全遵循 Shell 语法。

这里其实就是获取了 git config user.email 的返回结果,然后用正则表达式匹配是否符合公司邮箱格式,比如我们公司邮箱后缀当然是 microsoft.com 后缀,所以这里就用了 ^[.[:alnum:]]+@microsoft\.com$ 来进行匹配了。这里值得注意的是,为什么这里没有用 \S 来代表非空白字符,而是用了一个 [:alnum] 呢?这是因为 Bash Shell 本身不支持 \S 这种匹配,所以这里得换成 [:alnum]。

然后如果不匹配怎么办呢?

那就输出一些错误提示就好了,比如这里就提示请使用 git config —-local 命令来配置用户名和邮箱,之所以用-—local 是因为不想该配置影响全局的 Git 配置,所以这个配置只针对该仓库生效,然后 exit 1 就触发异常退出,程序运行终止,从而也不会触发 commit 命令了。

有了这个配置,我们来尝试下效果。

这会我没有做任何修改,Git 还是原来的配置,即我的全局个人邮箱配置。

这时候我执行下 commit 命令,就出现错误提示了:

1
2
3
4
Bash复制代码Your git information is not valid
Please run:
git config --local user.name "<Your name in Microsoft>"
git config --local user.email "<alias>@microsoft.com"

很棒!检测出来了。

按照这个提示说的,然后我运行下配置命令:

1
2
Bash复制代码git config --global user.name "Qingcai Cui"
git config --global user.email "xxxx@microsoft.com"

这里呢,我就配置了我的公司个人信息和公司邮箱。

然后重新再执行 commit 命令,就不会再出现如上的错误提示了!commit 成功!

大功告成!!!

有了它,我们就可以成功阻止一些奇奇怪怪的 commit 乱入公司的代码仓库了!

然后我把这个 PR 发出去了,有同事似乎也是深有感触,说道:

image_1.png

哈哈哈,有了这个,以后我们应该再也不会看到我们的代码仓库里面有 QQ 邮箱啦!

希望对大家有帮助~

更多精彩内容,请关注我的公众号「进击的 Coder」和「崔庆才丨静觅」。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

2021年四大流行Android手机自动化测试工具,全在这里

发表于 2021-07-13

Android 自动化测试的工具非常多,但是目前主流使用的就那几个,我会详细说明他们各自的情况,你可以根据自己的需要决定使用哪款工具。

测试资料包

Appium

Appium 是目前最主流的移动测试自动化框架,不仅支持 Android 应用,而且适用于 iOS、混合和 Web 应用程序。

它底层完全使用了 Selenium 和 WebDriver 的 API,所以如果你之前有用过 selenium, 几乎不需要额外的学习成本就可以使用 appium。

appium 通过 uiautomator(API 级别 16 或更高)和 Seledroid(API 级别低于 16)支持 Android,但是你不需要具体懂这两个框架的具体用法,appium 都已经帮你封装成了统一的使用规则。

Appium 的最大优势之一是几乎可以使用任何编程语言(例如 Java、Objective-C、JavaScript、PHP、Ruby、Python 或 C# 等)编写 Appium 脚本。

这意味这选择权在你,你可以使用自己最擅长的编程语言。如果你还熟悉 Selenium,那么不出意外,你可以在一天之内上手使用。

另外 webdriver 是一个统一的标准,已经提交给了 w3c 组织,你可以基于它的协议做进一步的扩展。看看简单的代码:

1
2
ini复制代码textFields = driver.find_elements_by_tag_name('textField')
assertEqual(textFields[0].get_attribute("value"), "Hello")

Airtest

第二个我要推荐国产之光 Airtest。Airtest 项目是由网易游戏推出的一款自动化测试框架,项目有几大组件:

  • Airtest:是一个跨平台的、基于图像识别的 UI 自动化测试框架,适用于游戏和 App,支持平台有 Windows、Android 和 iOS
  • Poco:是一款基于 UI 控件识别的自动化测试框架,目前支持 Android 原生 app/iOS 原生 app/微信小程序,还有主流的游戏引擎,也可以在其他引擎中自行接入 poco-sdk 来使用
  • AirtestIDE:跨平台的 UI 自动化测试编辑器,内置了 Airtest 和 Poco 的相关插件功能,能够使用它快速简单地编写 Airtest 和 Poco 代码,甚至都可以不写代码。
  • AirLab:真机自动化云测试平台,提供了非常多的手机型号和兼容性测试、海外云真机兼容性测试等服务
  • 私有化手机集群技术方案:从硬件到软件,提供企业内部私有化手机集群的解决方案

使用 Airtest 的好处是中文文档健全,对英文不好的同学支持优化。官网也贴心的准备了视频教学和演示,帮助你快速上手。

Detox

这个框架非常低调,但是你用过一定会爱上它。

Detox 一个非常强的 JavaScript 移动测试框架,它的测试执行非常快速和健壮,因为在测试执行期间不需要外部附加工具来编排和同步。

如果你优先考虑以下因素,你可以直接选用 detox:

  • 使用 JavaScript 编程语言
  • 比其他测试自动化框架更易于调试
  • app 是 React Native 开发的,可迅速和 detox 集成
  • 在测试执行方面比 Appium 快
  • 文档比较容易掌握

下面是具体的代码,比较典型的代码编写方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
scss复制代码describe('HelloDetoxTest', () => { 
beforeEach(async () => {
await device.reloadReactNative();
});
it('应该有欢迎屏幕', async () => {
await expect(element(by .id('welcome'))).toBeVisible();
});
it('should show hello Rect after tap', async () => {
await element(by.id('hello_react')).tap() ;
await expect(element(by.text('React!!!'))).toBeVisible();
});
it('点击后应该显示屏幕', async () => {
await element(by.id ('detox_button')).tap();
await expect(element(by.text('Detox!!!'))).toBeVisible();
});
});

Espresso

Espresso 是新型的的 Android 测试自动化框架,由 Google 开源,Espresso 的 API 体积小、可预测、易于学习。

你可以使用它快速编写简洁可靠的 Android UI 测试。

它非常可靠,与 UI 线程同步并且速度很快,因为不需要任何睡眠(当应用程序空闲时,测试在同一毫秒内运行)。测试资料包

而且通过简单的配置,可以直接集成在 appium 中。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

根据使用者反馈,对开源项目 go-gin-api 新增两个功

发表于 2021-07-13

前言

根据使用者的反馈,对开源项目:go-gin-api 新增两个小功能。

两个小功能都与语言包有关:

  • 功能1:接口返回的错误信息支持中英文;
  • 功能2:参数验证的错误信息支持中英文;

我的实现方式是在项目安装的时候,选择 语言,然后项目中根据选择的语言进行输出对应的中英文,如下图。

lang.png

接口返回的错误信息支持中英文

代码位置

项目代码位置:internal/pkg/code

使用方式

1
2
css复制代码// 与原来使用方式一致
code.Text(code.ServerError)

错误信息自定义

  • 中文:internal/pkg/code/zh-cn.go
  • 英文:internal/pkg/code/en-us.go

如果你想新增/编辑错误信息,在对应文件操作即可。

参数验证的错误信息支持中英文

代码位置

项目代码位置:internal/pkg/validation

使用方式

1
2
3
4
go复制代码req := new(createRequest)
if err := ctx.ShouldBindForm(req); err != nil {
fmt.Println(validation.Error(err))
}

错误信息语言包

错误信息语言包使用的是 go-playground/validator

  • 中文:v10/translations/zh
  • 英文:v10/translations/en

示例

1
2
c复制代码Username string `form:"username" binding:"required"` // 用户名
Nickname string `form:"nickname" binding:"required"` // 昵称

未使用语言包

1
2
dart复制代码// 错误信息
createRequest.Username' Error:Field validation for 'Username' failed on the 'required' tag\nKey: 'createRequest.Nickname' Error:Field validation for 'Nickname' failed on the 'required' tag"

使用中文语言包

1
2
arduino复制代码// 错误信息
Username为必填字段;Nickname为必填字段;

使用英文语言包

1
2
swift复制代码// 错误信息
Username is a required field;Nickname is a required field;

小结

以上,希望能对你有所帮助,代码已提交到 GitHub,下载最新代码用起来吧。

推荐阅读

  • 关于处理电商系统订单状态的流转,分享下我的技术方案(附带源码)
  • 我是怎么写 Git Commit message 的?

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

强烈推荐十几款IDEA开发必备的插件,实测非常好用

发表于 2021-07-13

IDEA有很多优秀的插件,使用它们不仅大大增加了开发效率,也能给大家带来更好的coding体验。“工欲善其事必先利其器”,以下插件基本都可以通过 IDEA 自带的插件管理中心安装。

1、CodeGlance

   拖动浏览代码更加方便,还有放大镜功能。

在这里插入图片描述

2、Restfultoolkit

   一套 RESTful 服务开发辅助工具集,完美代替postman。(该插件在IDEA 2020版本未适配,可用Restfultool代替)

  • 根据 URL 直接跳转到对应的方法定义 ( or Ctrl Alt N );
  • 提供了一个 Services tree 的显示窗口;
  • 一个简单的 http 请求工具;
  • 在请求方法上添加了有用功能::复制生成 URL,复制方法参数……
  • 其他功能::java 类上添加 Convert to JSON 功能,格式化 json 数据 ;

在这里插入图片描述

3、Easy Code

   它可以直接对数据的表生成entity、controller、service、dao、mapper,无需任何编码,简单而强大。

在这里插入图片描述

4、GsonFormat

   Json转Java类,该插件可以加快开发进度,使用非常方便,效率高。

在这里插入图片描述

5、Statistic

   统计插件,查看你的代码数据。比如整个项目的代码总行数,分别统计各个类型文件的总行数。

在这里插入图片描述

6、Rainbow Brackets

   它可以实现配对括号相同颜色,并且实现选中区域代码高亮的功能。对增强写代码的有趣性和排错等都有一些帮助。

在这里插入图片描述

7、Translation

   很不错的翻译插件,支持谷歌、有道、百度三种翻译。还有一个很强大的功能,程序员估计都对变量起名而头疼,有了它就再也不会了!(操作方式:选中并点击右键,会出现“Translate and Replace”,快去试试吧!)

在这里插入图片描述

8、Lombok

   它主要用来简化,减少代码的编写。使代码看起来更清晰,简洁。只需要加注解,不用再写get、set、toString、equals和hashCode方法了。

在这里插入图片描述

9、Maven Helper

   用它查找maven依赖是相当方便的,可显示依赖结构,可以查找,处理依赖冲突很方便。

在这里插入图片描述

10、Alibaba Java Code Guidelines

   阿里巴巴出品的代码规范插件,用于帮助程序员规范自己的代码,检测出潜在的问题,改善代码质量。

在这里插入图片描述

11、GenerateAllSetter

  该插件作用是可以快速针对已有的model实体对象的属性生产set代码,免去开发者在开发过程中set属性值时还需要去实体对象中翻查的时间,生成的同时会附带类型默认值。

在这里插入图片描述

12、MybatisX

  MybatisX 是一款基于 IDEA 的快速开发插件,为效率而生。最强拍档:搭配Mybatis-Plus开发。

在这里插入图片描述

13、JRebel and XRebel

  JRebel是一款JVM插件,它使得Java代码修改后不用重启系统,立即生效。Jrebel 可快速实现热部署,节省了大量重启时间,提高了个人开发效率。目前对于idea热部署最好的解决方案就是安装JRebel插件。

在这里插入图片描述

14、Codota

  Codota,程序员的编程助手,可以用于代码的智能补全功能,它基于百万级github仓库java程序,能根据程序上下文及依赖给出最适合的代码提示及自动补全推荐,给代码开发带来了极大的便利,绝对是idea 插件中的必备利器。

在这里插入图片描述

15、Chinese Language Pack EAP

  官方出品的IDEA汉化包,2020版本之后的IDEA都可以用。

好用的插件,持续更新中。记得收藏!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

leetcode每日一题系列-天际线问题 leetcode-

发表于 2021-07-13

leetcode-218-天际线问题

[博客链接]

菜🐔的学习之路

掘金首页

[题目描述]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
java复制代码城市的天际线是从远处观看该城市中所有建筑物形成的轮廓的外部轮廓。给你所有建筑物的位置和高度,请返回由这些建筑物形成的 天际线 。 

每个建筑物的几何信息由数组 buildings 表示,其中三元组 buildings[i] = [lefti, righti, heighti] 表示:



lefti 是第 i 座建筑物左边缘的 x 坐标。
righti 是第 i 座建筑物右边缘的 x 坐标。
heighti 是第 i 座建筑物的高度。


天际线 应该表示为由 “关键点” 组成的列表,格式 [[x1,y1],[x2,y2],...] ,并按 x 坐标 进行 排序 。关键点是水平线段的左端点。
列表中最后一个点是最右侧建筑物的终点,y 坐标始终为 0 ,仅用于标记天际线的终点。此外,任何两个相邻建筑物之间的地面都应被视为天际线轮廓的一部分。

注意:输出天际线中不得有连续的相同高度的水平线。例如 [...[2 3], [4 5], [7 5], [11 5], [12 7]...] 是不正确的答
案;三条高度为 5 的线应该在最终输出中合并为一个:[...[2 3], [4 5], [12 7], ...]



示例 1:


输入:buildings = [[2,9,10],[3,7,15],[5,12,12],[15,20,10],[19,24,8]]
输出:[[2,10],[3,15],[7,12],[12,0],[15,10],[20,8],[24,0]]
解释:
图 A 显示输入的所有建筑物的位置和高度,
图 B 显示由这些建筑物形成的天际线。图 B 中的红点表示输出列表中的关键点。

示例 2:


输入:buildings = [[0,2,3],[2,5,3]]
输出:[[0,3],[5,0]]




提示:


1 <= buildings.length <= 104
0 <= lefti < righti <= 231 - 1
1 <= heighti <= 231 - 1
buildings 按 lefti 非递减排序

Related Topics 树状数组 线段树 数组 分治 有序集合 扫描线 堆(优先队列)
👍 430 👎 0

[题目链接]

leetcode题目链接

[github地址]

代码链接

[思路介绍]

思路一:暴力扫描

  • 输出点坐标要求
  • 下一个点与当前节点高度不同,能通过两个节点确定一条单拐点直线
  • 边界情况:第一个节点为第一个点的横坐标以及其高度,最后一点的为最后一个节点的横坐标和0
  • 其实分析一下发现,我们要找的其实是每个区间的最高高度
  • 然后通过这个最高高度的变化进行列表输出
  • 通过Map进行存储每个节点的最高高度吧
  • map好像没啥大用不如list一个最大整数段
  • 直接内存溢出了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
java复制代码public List<List<Integer>> getSkyline(int[][] buildings) {
List<List<Integer>> res = new ArrayList<>();
//初始节点
List<Integer> xh = new ArrayList<>();
for (int i = 0; i < buildings[0][0]; i++) {
xh.add(0);
}

for (int[] nums : buildings
) {
int l = nums[0], r = nums[1], h = nums[2];
while (l < r) {
//TODO 这一部分可以优化
while (xh.size() < l) {
xh.add(0);
}
if (xh.size() == l) {
xh.add(h);
}
xh.set(l, Math.max(xh.get(l), h));
l++;
}
}
int index = 0;
//确立初始节点
for (; index < xh.size(); index++) {
if (xh.get(index) != 0) {
List<Integer> start = new ArrayList<>();
start.add(index);
start.add(xh.get(index));
res.add(start);
break;
}
}
int temp = xh.get(index);
while (index < xh.size() - 1) {
if (xh.get(index) != temp) {
List<Integer> node = new ArrayList<>();
node.add(index);
node.add(xh.get(index));
res.add(node);
temp = xh.get(index);
}
index++;
}
List<Integer> end = new ArrayList<>();
end.add(xh.size());
end.add(0);
res.add(end);
return res;
}

时间复杂度O(n2)时间复杂度O(n^{2})时间复杂度O(n2)


思路二:扫描线

  • 尝试用map记录区间
  • 失败了 因为更新区间的时候会使map变化
  • 看了三叶大佬的题解还是没太看懂预处理的思路由来
  • 不过大概思路理解了
  • 我换一种按我理解里更通俗的方式解释一下吧
    • 首先要明白题意打印的点究竟是什么:按照扫描线观测可以发现,打印的点是每两条满足条件的扫描线形成的矩形的左端点
    • 其次我们要明白有一种特殊情况,也就是说前一个矩形的右延线可能和当前矩形延长线是一条线,也就是说当前矩形的左端点应该前移到之前的左端点
    • 记录每个矩形的左右端点,为了方便分辨左右端点
    • 我们可以通过对于height的正负值进行标注
    • 也就是说当height为负代表左端点,height为正代表右端点
    • 通过大根堆来保证每个最高的左端点依次输出
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
java复制代码        public List<List<Integer>> getSkyline(int[][] buildings) {
List<List<Integer>> res = new ArrayList<>();
//记录每个矩形的左右端点,为了方便分辨左右端点,我们可以通过对于height的正负值进行标注
//也就是说当height为负代表左端点,height为正代表右端点
List<int[]> ps = new ArrayList<>();
for (int[] b : buildings) {
int l = b[0], r = b[1], h = b[2];
ps.add(new int[]{l, -h});
ps.add(new int[]{r, h});
}
//排序的标准是为了按照横坐标进行排序,确定每个横坐标的点,然后按照
Collections.sort(ps, new Comparator<int[]>() {
@Override
public int compare(int[] a, int[] b) {
//横坐标排序
if (a[0] != b[0]) {
return a[0] - b[0];
}
//高度排序
return a[1] - b[1];
}
});
// 大根堆(保证)
PriorityQueue<Integer> q = new PriorityQueue<>((a,b)->b-a);
int prev = 0;
q.add(prev);
for (int[] p : ps) {
int point = p[0], height = p[1];
if (height < 0) {
// 如果是左端点,说明存在一条往右延伸的可记录的边,将高度存入优先队列
q.add(-height);
} else {
// 如果是右端点,说明这条边结束了,将当前高度从队列中移除
q.remove(height);
}

// 取出最高高度,如果当前不与前一矩形“上边”延展而来的那些边重合,则可以被记录
int cur = q.peek();
if (cur != prev) {
List<Integer> list = new ArrayList<>();
list.add(point);
list.add(cur);
res.add(list);
prev = cur;
}
}
return res;

}

时间复杂度O(n∗lgn)时间复杂度O(n*lg_{n}) 时间复杂度O(n∗lgn​)

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

当 Lombok 遇见了 MapStruct の「坑」

发表于 2021-07-13

前言

2021 年了,相信搞 Java 的小伙伴们不会还没有人没用过 Lombok 吧?

Lombok 是一款通过「注解」的形式简化并消除冗余代码的 Java 插件,利用「Annotation Processor」原理,在编译时生成一些「重复」代码。另外需要注意的是,在 IDEA 环境下,需要额外安装一个 Lombok 插件。(本文不会专门介绍 Lombok 的使用方法,想要深入学习的小伙伴可以去 官方文档 学习 Lombok 提供的所有注解的使用方法。)

可能一些朋友对 MapStruct 就有点陌生了,但是我敢肯定的是,你们一定用过和他功能类似的工具。比如 Apache Commons BeanUtils、Spring BeanUtils、BeanCopier、Dozer 等等。没错,MapStruct 也是为了解决对象属性拷贝这一个通用需求的。传统使用「反射」进行属性拷贝的方式,在大数据量的场景下,性能低下,效率堪忧。MapStruct 底层则是通过 getter/setter 的方式提升属性拷贝的性能的,跟 Lombok 一样利用「Annotation Processor」的原理,在编译时生成代码。

踩坑

首先我们按照 MapStruct 官方文档介绍,搭一个简单的栗子🌰~

引入依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
xml复制代码<properties>
<mapstruct.version>1.4.2.Final</mapstruct.version>
</properties>

<dependencies>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${mapstruct.version}</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<annotationProcessorPaths>
<!-- MapStruct 在编译时会通过这个插件生成代码 -->
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>

创建两个类,用于属性拷贝。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
java复制代码// 源对象
public class CarDO {
private String make;
private int numberOfSeats;
private CarType type;

public CarDO() {
}

public CarDO(String make, int numberOfSeats, CarType type) {
this.make = make;
this.numberOfSeats = numberOfSeats;
this.type = type;
}

public String getMake() {
return make;
}

public void setMake(String make) {
this.make = make;
}

public int getNumberOfSeats() {
return numberOfSeats;
}

public void setNumberOfSeats(int numberOfSeats) {
this.numberOfSeats = numberOfSeats;
}

public CarType getType() {
return type;
}

public void setType(CarType type) {
this.type = type;
}
}

// 测试枚举
public enum CarType {
/**
* 普通
*/
COMMON,
/**
* 老爷车
*/
OLD,
/**
* 跑车
*/
SPORTS;
}

// 目标对象
public class CarDTO {
private String make;
private int seatCount;
private String type;

public CarDTO() {
}

public CarDTO(String make, int seatCount, String type) {
this.make = make;
this.seatCount = seatCount;
this.type = type;
}

public String getMake() {
return make;
}

public void setMake(String make) {
this.make = make;
}

public int getSeatCount() {
return seatCount;
}

public void setSeatCount(int seatCount) {
this.seatCount = seatCount;
}

public String getType() {
return type;
}

public void setType(String type) {
this.type = type;
}
}

创建一个对象拷贝接口,用于告诉 MapStruct 需要生成哪个对象的属性拷贝。

注意:这里 @Mapper / Mappers / @Mapping 都是 org.mapstruct 包下的,当和 Mybatis 一起使用时,不要引用错了~

1
2
3
4
5
6
7
java复制代码@Mapper
public interface CarMapper {
CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);

@Mapping(source = "numberOfSeats", target = "seatCount")
CarDTO carToCarDto(CarDO car);
}

最后创建测试类,测试一把!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
java复制代码public class TestMapStruct {
@Test
public void shouldMapCarToDto() {
//given
CarDO car = new CarDO("Morris", 5, CarType.SPORTS);

//when
CarDTO carDto = CarMapper.INSTANCE.carToCarDto(car);

//then
Assert.assertNotNull(carDto);
Assert.assertEquals("Morris", carDto.getMake());
Assert.assertEquals(5, carDto.getSeatCount());
Assert.assertEquals("SPORTS", carDto.getType());
}
}

按照官方文档的做法,十分顺利,测试通过!

如果我们把 2 个实体类里的 getter/setter 替换成 Lombok,会发生什么事情呢?

我们先改造 pom.xml 文件,增加 Lombok 相关依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
diff复制代码<properties>
+ <lombok.version>1.18.12</lombok.version>
<mapstruct.version>1.4.2.Final</mapstruct.version>
</properties>

<dependencies>
+ <dependency>
+ <groupId>org.projectlombok</groupId>
+ <artifactId>lombok</artifactId>
+ <version>${lombok.version}</version>
+ </dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${mapstruct.version}</version>
</dependency>
</dependencies>

此时,我们的实体类代码应该是这样子的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
java复制代码// 源对象
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CarDO {
private String make;
private int numberOfSeats;
private CarType type;
}

// 目标对象
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CarDTO {
private String make;
private int seatCount;
private String type;
}

再来执行一遍测试类,编译时就会出现类似这种「java: 找不到符号 符号: 方法 getXXX()」错误提示,具体如下图:

异常

当出现这种错误,分明就是 Lombok 没有生效嘛,那么该怎么解决这个问题呢?

解坑

还记得文章开头介绍的,Lombok 和 MapStruct 都是利用「Annotation Processor」在程序编译时生成代码的吗?

了解原理,问题就容易解决了。

前文我们在测试 MapStruct 的时候,在 pom.xml 文件中添加了一个一个插件,用于告诉 Maven 编译时,需要额外执行 MapStruct 的代码生成逻辑,但是我们没有告诉 Maven 在编译时,Lombok 也需要生成代码。

我们在 pom.xml 文件的 build 节点中加上这么一段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
diff复制代码<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<annotationProcessorPaths>
<!-- Lombok 在编译时会通过这个插件生成代码 -->
+ <path>
+ <groupId>org.projectlombok</groupId>
+ <artifactId>lombok</artifactId>
+ <version>${lombok.version}</version>
+ </path>
<!-- MapStruct 在编译时会通过这个插件生成代码 -->
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${mapstruct.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>

这个时候,再去运行一下通过测试类,就不会出现上述找不到 get 方法的错误了~


创建时间:2021-04-22 14:01:21

原文链接:xkcoding.com/2021/04/22/…

配套代码:github.com/xkcoding/pr…

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

为什么 HTTP/3 基于UDP,可靠么?

发表于 2021-07-13

⚠️本文为掘金社区首发签约文章,未获授权禁止转载

对网络技术有一定研究的同学一定知道,HTTP/3竟然是基于UDP的。在很多同学的印象里,UDP属于一种非常低级的协议:传输不可靠,没有拥塞机制,把它用在准确可靠的万维网传输上,是一件不可想象的事情。

然而,真的是这样吗?我们先把吃惊的嘴合上,一起来看一下,为什么HTTP/3可以基于UDP,并且这还是一种非常聪明的选择。

要明白这个选择,我们首先就得消除一下对于UDP的误解。

1. UDP是最纯洁的传输层协议

实际上,UDP并不是像大家想象中的那样不可信,它只是因为简单,才让你有这样的认知。从另一个角度来说,它**其实是最纯洁的传输层**协议。

那么UDP在网络传输中,到底处于一个什么位置呢?我们需要简单看一下典型的网络分层。

img

物理层和连接层

  • 物理层处于网络的最下层,它处理的消息,全部是1和0,这是硬件层面的东西,保证了我们的信息能够正常交流
  • 连接层对这些1和0进行了初步的整合,组成了固定长度的信息帧(frame),每个信息帧里面,都包含SRC和DST(这里是mac地址),得以让我们的两台机器进行点对点通信

这时候网络数据包还不能逃离局域网,想要更大规模的传输,就需要网络层的帮助。

网络层

为了让这些地址有意义, 网络层加入了IP协议,通过IP地址进行目标机器的定位,最后经过路由器的转换,由连接层负责具体信息的传输。比如我在下面的这个wireshark的抓包。

img

  • EthernetII就是我们所说的连接层以太网,传输的信息是Frame
  • 下面的是IPv4协议,表明了是一个IP包。

如果你去看一下IP的报文,会发现它的格式是非常怪异的,中间节点在路由的时候,需要走先解包然后再封包的过程。这是由于IP协议是网络层协议,它的头信息,比如IP地址,其实是连接层协议的报文体(playload)。

到了IP协议,我们已经离UDP很近了。

传输层

IP协议只是解决了计算机到计算机之间的通信问题,但我们每台机器上还会有不同的进程,如何区分它们呢?这就是传输层协议要干的事。

UDP和TCP协议,通过加入了端口号,来识别某一个进程。IP地址加上端口号,就是我们写代码需要面对的socket。现在的大多数网络通信,包括HTTP/1,HTTP/2等,都是基于TCP。

从协议层面来看,UDP其实是最干净的协议,它完完全全的暴露了IP协议的所有内容。相比较IP协议,它仅仅多了一个端口号,所以UDP协议拥有IP协议的所有特点。比如无序性,不保证可靠性(Best Effort)。至于拥塞机制这种更高级的流量协商控制,UDP根本就不管这些。

UDP这种没有特性的特性,使得应用面比TCP窄的多。我们通常把**TCP/IP**协议作为一个整体去介绍,以至于忽略了最原始最纯洁的UDP协议。

img

我们来看一下一个典型的UDP协议。如上图,相对于IP协议,UDP协议仅仅多了两个端口,一个长度,一个checksum,确实是无与伦比的纯洁。

2. TCP作为基础协议太复杂

你可能会问,TCP和UDP都是传输层协议,那为什么HTTP/3不是基于TCP呢?那是因为TCP本身就已经非常复杂了,有太多历史遗留的包袱。

为了保证信息的可靠传输,顺序传输,同时兼顾吞吐量,TCP做了大量工作。相比较UDP,我们可以看一下TCP的协议都多了哪些内容。如图,是一个wireshark抓取的,典型的TCP协议包。

img

我们常说的三次握手(四次合并成三次),四次挥手,就是Flags和序列号逻辑组合的结合体。如果加入了TLS安全协议,这个握手的过程会更长。

连接建立后,由于采用了一问一答的ACK确认模式,TCP的效率其实是不怎么高的。它要传输很多无用信息,还得等待。

为了提高网络传输效率,TCP使用滑动窗口来解决批量发送,同时解决顺序性问题。为了解决网络拥塞,TCP使用慢启动、拥塞避免、快速重传、快速恢复等机制,使得网络吞吐量保持在一定的水平。

我们可以看一下wikipedia上TCP协议的一张细节图,在《TCP/IP详解 卷一:协议》中,对此进行了非常详细的介绍。可以看到细节问题还是非常多的。

img

那什么叫做协议?协议,就是规定了大家都遵守的标准,所有协议都是利益共同体共同商定的结果。比如我的分布式数据库使用了MySQL的接入层协议,那么就要遵循MySQL协议所制定的一系列标准,否则就跑不起来。

**协议越底层,对稳定性要求就越高。**TCP协议,目前已经被编码到了操作系统,不论是协议升级,还是BUG修复,都是伤筋动骨的。

3. 为什么UDP可行?

为了抛开历史的包袱,HTTP/3选择了UDP,主要是为了解决对头阻塞问题。它的底层协议,就是大名鼎鼎的QUIC,一个运行在传输层(也可以说是应用层)的协议。与TCP不同的是,QUIC代码并没有硬编码在操作系统内核中,而是完全运行在用户空间的,默认集成了TLS。

img

如上图所示,HTTP/3基于QUIC,而QUIC是完全基于UDP的。

但UDP不是号称无连接的么?它怎么去实现可靠性等一些额外的功能呢?

其实,连接这个词,是一个虚拟的概念,在网络中根本就没有连接这么一条线,连接只是为了方便你逻辑上的理解。考虑到你现在的客户端服务器是client,服务端是server。那么client和server的数据包交互,就可能会有下面这种行走路径。

img

在没有数据交互的时候,server和client就是单纯的两个点。即使你拔了网线重新插上(期间无交互),它们依然可以继续相互间发送数据。

UDP号称的无连接,其实和TCP的连接没什么两样。唯一的区别是,UDP把数据包发送之后,就什么也不管了,这些信息对端可能收到了,也可能没收到。而当每次都对发送的数据进行ACK确认,它就变成了TCP。

至于这部分确认代码,是放在传输层,还是放在应用层,这都关系不大;但代码是放在操作系统,还是可以独立升级的包中,那关系可就大了。

QUIC是可以独立于操作系统发行的,避免了操作系统缓慢的更新换代问题。QUIC的实现,依然要面对消息的可靠性、滑动窗口、拥塞控制等场景,你可以认为它就是一个TCP,但它与TCP有本质的区别。

这些区别,我们对比一下HTTP的各个版本,在数据传输方面的表现就知道了了。

  1. 在比较早的HTTP1.0实现中,如果需要从服务端获取大量资源,会开启N条TCP短链接,并行的获取信息。但由于TCP的三次握手和四次挥手机制,在连接数量增加的时候,整体的代价就变得比较大
  2. 在HTTP/1.1中,通过复用长连接,来改善这个情况,但问题是,由于TCP的消息确认机制和顺序机制以及流量控制策略的原因,资源获取必须要排队使用。一个请求,需要等待另外一个请求传输完毕,才能开始
  3. HTTP/2采用多路复用,多个资源可以共用一个连接。 但它解决的只是应用层的复用,在TCP的传输上依然是阻塞的,后面的资源需要等待前面的传输完毕才能继续。这就是队头阻塞现象(Head-of-line blocking)
  4. QUIC抽象出了一个stream(流)的概念,多个流,可以复用一条连接,那么滑动窗口这些概念就不用作用在连接上了,而是作用在stream上。由于UDP只管发送不管成功与否的特性,这些数据包的传输就能够并发执行。协议的server端,会解析并缓存这些数据包,进行组装和整理等。由于抽象出了stream的概念,就使得某个数据包传输失败,只会影响个stream的准确性,而不是整个连接的准确性

End

了解了以上内容,相信你一定能得出结论:HTTP/3基于UDP,是非常靠谱的。它不仅实现了可靠性传输,而且能够获得较大的性能提升。我们来总结一下QUIC的这些改进:

  1. QUIC能够实现TCP协议的所有功能性需求,并集成了TLS,功能上赶超了TCP
  2. 一条连接,多个stream并发传输,真正的多路复用,有效解决队头阻塞现象,性能上超越了TCP
  3. 减少握手次数,尤其是带TSL传输的握手次数,有更低的握手延迟
  4. 由于易于升级,为协议的更新和发展,提供了巨大的想象空间

QUIC产生的原因,主要是由于TCP的限制所引起的。连接是一种非常宝贵的资源,创建、销毁,以及其上的传输,都是非常耗时的。TCP的可靠性机制,在计算机网络发展的早期,确实是非常有效的,但随着硬件的升级,它的ACK传输模式,在效率上制约了更高性能的发展。由于TCP标准的概念深入人心,它的代码甚至直接存在于内核上,使得协议升级困难。

随着网络基础设施的提升,TCP的这种可靠传输模式,反而成了制约。如果我们的信息处理,能够全部在一条连接上完成,那就太好了。这样,在一些密集的资源传输时,比如批量小图片、视频点播、弱网传输时,就不会受到RTT的影响。另外我的数据缓存和拥塞控制等,也会更加灵活。举个极端的例子,我的内存足够大能把stream缓存起来,我甚至能够忍受某个1MB大小的文件,10秒钟后文件的第一个字节到达,而不是像TCP一样一直重传重传(因为它受限于TCP窗口)。

为什么能够这么做呢?还是得益于UDP纯洁的属性,它只是IP协议的一个编程接口,它真的是一张白纸,什么都没有。如果你愿意,你甚至可以在UDP的基础上,完全复刻TCP的所有功能,只要你能把server端和client端对应起来。这就是QUIC所做的。

⚠️本文为掘金社区首发签约文章,未获授权禁止转载

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

本地安装K8S方式 方式一 minikube 方式二 kin

发表于 2021-07-13

注意 安装后占用本机资源较少,适合学习测试使用,不建议用于生产

前提条件

  • 安装 docker
  • 安装 kubectl 安装指南

方式一 minikube

minikube 安装指南
启动

1
bash复制代码minikube start

集群中安装 ingress:

1
bash复制代码minikube addons enable ingress

方式二 kind (Kubernets in docker)

kind 安装指南
Then spins up a kind cluster:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
bash复制代码cat <<EOF | kind create cluster --image=kindest/node:v1.18.15 --config=-
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
kubeadmConfigPatches:
- |
kind: InitConfiguration
nodeRegistration:
kubeletExtraArgs:
node-labels: "ingress-ready=true"
extraPortMappings:
- containerPort: 80
hostPort: 80
protocol: TCP
- containerPort: 443
hostPort: 443
protocol: TCP
EOF

安装 ingress:

1
bash复制代码kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/provider/kind/deploy.yaml

安装结果

我使用mac系统,使用方式二 通过kind安装

检查安装k8s健康状态

1
bash复制代码kubectl get cs

查看结果

1
2
3
4
bash复制代码NAME                 STATUS      MESSAGE                                                                                     ERROR
scheduler Unhealthy Get http://127.0.0.1:10251/healthz: dial tcp 127.0.0.1:10251: connect: connection refused
controller-manager Unhealthy Get http://127.0.0.1:10252/healthz: dial tcp 127.0.0.1:10252: connect: connection refused
etcd-0 Healthy {"health":"true"}

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Golang连接MySQL数据库之CRUD 搭建环境 项目结

发表于 2021-07-13

我们这次来学习使用Golang来连接MySQL数据库,并使用Golang实现数据库的CRUD操作。

搭建环境

首先我们创建一个Golang的项目,并配置项目GOPATH,这一步可以参考我的博客Golang环境安装&IDEA开发Golang。

因为我们使用的是MySQL数据库,所以我们需要获取Golang的MySQL数据库驱动。
我们在项目GOPATH的目录下,执行go get命令来获取MySQL的驱动,命令执行成功之后,会直接从网上下载MySQL驱动的包到你的GOPATH目录下的src目录下。

1
go复制代码go get -u github.com/go-sql-driver/mysql

Golang的项目环境搭建完成之后,我们还需要创建一张数据库表。

1
2
3
4
5
6
7
sql复制代码CREATE TABLE `tb_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(11) DEFAULT NULL,
`password` varchar(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `nameindex` (`name`(10))
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

项目结构

在这里插入图片描述

编写结构体

为了能够便于封装从数据库中获取的数据,我们创建一个Golang结构,来封装我们的数据。
我们创建一个bean包,然后在该包下创建一个user.go文件。

user.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
go复制代码package bean

type User struct {
id int
name string
password string
}

func (user *User) GetId() int {
return user.id
}

func (user *User) SetId(id int) {
user.id = id
}

func (user *User) GetName() string {
return user.name
}

func (user *User) SetName(name string) {
user.name = name
}

func (user *User) GetPassword() string {
return user.password
}

func (user *User) SetPassword(password string) {
user.password = password
}

编写数据库连接工具

由于每次连接数据库都需要获取数据库连接,所以我们直接将数据库的连接操作封装为一个方法,这样就可以不用每次都执行获取连接的步骤了。
我们创建一个util包,在包下创建一个initdb.go文件。

initdb.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
go复制代码package util

import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
"strings"
)

//我们先将数据库配置信息定义成为常量
const (
userName = "root"
password = "admin"
ip = "127.0.0.1"
port = "3306"
dbName = "db_database08"
)

//初始化数据库连接,返回数据库连接的指针引用
func InitDB() *sql.DB {
//Golang数据连接:"用户名:密码@tcp(IP:端口号)/数据库名?charset=utf8"
path := strings.Join([]string{userName, ":", password, "@tcp(", ip, ":", port, ")/", dbName, "?charset=utf8"}, "")
//打开数据库,前者是驱动名,所以要导入: _ "github.com/go-sql-driver/mysql"
db, err := sql.Open("mysql", path)
if err != nil {
//如果打开数据库错误,直接panic
panic(err)
}
//设置数据库最大连接数
db.SetConnMaxLifetime(10)
//设置上数据库最大闲置连接数
db.SetMaxIdleConns(5)
//验证连接
if err := db.Ping(); err != nil {
panic(err)
}
//将数据库连接的指针引用返回
return db
}

CRUD操作

Insert操作

我们创建一个insert包,在该包下创建一个insert.go文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
go复制代码package main

import (
"fmt"
"mysql/util"
)

func main() {
//使用工具获取数据库连接
db := util.InitDB()
//开启事务
tx, err := db.Begin()
if err != nil {
//事务开启失败,直接panic
panic(err)
}
//准备SQL语句
sql := "insert into tb_user (`name`, `password`) values (?, ?)"
//对SQL语句进行预处理
stmt, err := db.Prepare(sql)
if err != nil {
panic(err)
}
result, err := stmt.Exec("阿部多瑞","123")
if err != nil {
//SQL执行失败,直接panic
panic(err)
}
//提交事务
tx.Commit()
//返回插入记录的id
fmt.Println(result.LastInsertId())
}

Select操作

我们创建一个select包,在该包下创建一个select.go文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
go复制代码package main

import (
"fmt"
"mysql/bean"
"mysql/util"
)

func main() {
//使用工具获取数据库连接
db := util.InitDB()
//准备SQL语句
sql := "select * from tb_user"
//对SQL语句进行预处理
stmt, err := db.Prepare(sql)
if err != nil {
panic(err)
}
rows, err := stmt.Query()
if err != nil {
//SQL执行失败,直接panic
panic(err)
}
var users []bean.User
for rows.Next() {
var id int
var name, password string
err := rows.Scan(&id, &name, &password)
if err != nil {
//读取结果集失败
panic(err)
}
var user bean.User
user.SetId(id)
user.SetName(name)
user.SetPassword(password)
users = append(users, user)
}
fmt.Println(users)
}

Update操作

我们创建一个update包,在该包下创建一个update.go文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
go复制代码package main

import (
"mysql/util"
)

func main() {
//使用工具获取数据库连接
db := util.InitDB()
//开启事务
tx, err := db.Begin()
if err != nil {
//事务开启失败,直接panic
panic(err)
}
//准备SQL语句
sql := "update tb_user set `password` = ? where `id` = ?"
//对SQL语句进行预处理
stmt, err := db.Prepare(sql)
if err != nil {
panic(err)
}
_, err = stmt.Exec("789", 1)
if err != nil {
//SQL执行失败,直接panic
panic(err)
}
//提交事务
tx.Commit()
}

Delete操作

我们创建一个delete包,在该包下创建一个delete.go文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
go复制代码package main

import (
"mysql/util"
)

func main() {
//使用工具获取数据库连接
db := util.InitDB()
//开启事务
tx, err := db.Begin()
if err != nil {
//事务开启失败,直接panic
panic(err)
}
//准备SQL语句
sql := "delete from tb_user where `id` = ?"
//对SQL语句进行预处理
stmt, err := db.Prepare(sql)
if err != nil {
panic(err)
}
_, err = stmt.Exec(1)
if err != nil {
//SQL执行失败,直接panic
panic(err)
}
//提交事务
tx.Commit()
}

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…609610611…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%