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

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


  • 首页

  • 归档

  • 搜索

程序员必备:Git入门,超详细

发表于 2021-10-21

本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。

对于一个刚入行的程序员或者是大学生,我建议大家还是好好的把git学学,因为在现在大部分企业代码的管理工具都是使用Git,其实Git很简单,无非就是一些命令,我们平常多去用它,就能熟能生巧。我觉得你们可以自己去创建Gitee和GitHub账号,把自己的代码托管上去,进行管理。下面就是关于Git的一些总结,觉得有用的建议收藏。

Git 是什么?

  • Git 是一个开源的分布式版本控制系统,用于敏捷高效地处理任何或小或大的项目。
  • Git 是 Linus Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。
  • Git 与常用的版本控制工具 CVS, Subversion 等不同,它采用了分布式版本库的方式,不必服务器端软件支持。

Git 与 SVN 区别

Git 不仅仅是个版本控制系统,它也是个内容管理系统(CMS),工作管理系统等。

如果你是一个具有使用 SVN 背景的人,你需要做一定的思想转换,来适应 Git 提供的一些概念和特征。
Git 与 SVN 区别点:

  1. Git 是分布式的,SVN不是:这是 Git 和其它非分布式的版本控制系统,例如SVN,CVS等,最核心的区别。
  2. Git 把内容按元数据方式存储,而 SVN 是按文件:所有的资源控制系统都是把文件的元信息隐藏在一个类似 .svn、.cvs 等的文件夹里。
  3. Git 分支和SVN的分支不同:分支在 SVN 中一点不特别,就是版本库中的另外的一个目录。
  4. Git 没有一个全局的版本号,而 SVN 有:目前为止这是跟 SVN 相比 Git 缺少的最大的一个特征。
  5. Git 的内容完整性要优于 SVN:Git 的内容存储使用的是 SHA-1 哈希算法。这能确保代码内容的完整性,确保在遇到磁盘故障和网络问题时降低对版本库的破坏。

Git 快速入门

Git 相关网站

  • Git 官网:git-scm.com
  • Git 完整命令手册:git-scm.com/docs
  • Git 命令手册(pdf版):github-git-cheat-sheet.pdf
  • Git 简明指南:git-guide

Git 工作流程

Git 一般工作流程如下:

  • 克隆 Git 资源作为工作目录。
  • 在克隆的资源上添加或修改文件。
  • 如果其他人修改了,可以更新资源。
  • 在提交前查看修改。
  • 提交修改。
  • 在修改完成后,如果发现错误,可以撤回提交并再次修改并提交。

下图展示了 Git 的工作流程:

Git 工作空间

Git 工作空间分为:

  • 工作区(Working Directory):在电脑里能看到的目录。
  • 版本库(Repository):工作区有一个隐藏目录 .git,这个目录不算工作区,而是 Git 的版本库。
  • 暂存区(Stage/Index):一般存放在 .git 目录下的 index 文件(.git/index)中,所以我们把暂存区有时也叫作索引(index)。

下面这个图展示了工作区、版本库中的暂存区和版本库之间的关系:


图中左侧为工作区,右侧为版本库。在版本库中标记为 index 的区域是暂存区(stage/index),标记为 master 的是 master 分支所代表的目录树。此时 HEAD 实际是指向 master 分支的一个“游标”,所以图示的命令中出现 HEAD 的地方可以用 master 来替换。图中的 objects 标识的区域为 git 的对象库,实际位于 .git/objects 目录下。

  • 当对工作区修改(或新增)的文件执行 git add 命令时,暂存区的目录树会被更新,同时工作区修改(或新增)的文件内容会被写入到对象库中的一个新的对象中,而该对象的id被记录在暂存区的文件索引中。
  • 当执行提交操作 git commit 时,暂存区的目录树会写到版本库(对象库)中,master 分支会做相应的更新,即 master 最新指向的目录树就是提交时原暂存区的目录树。
  • 当执行 git reset HEAD 命令时,暂存区的目录树会被重写,会被 master 分支指向的目录树所替换,但是工作区不受影响。
  • 当执行 git rm --cached 命令时,会直接从暂存区删除文件,工作区则不做出改变。
  • 当执行 git checkout . 或 git checkout -- 命令时,会用暂存区全部的文件或指定的文件替换工作区的文件。这个操作很危险,会清楚工作区中未添加到暂存区的改动。
  • 当执行 git checkout HEAD . 或 git checkout HEAD 命令时,会用 HEAD 指向的 master 分支中的全部或部分文件替换暂存区和工作区中的文件。这个命令也是极度危险的。因为不但会清楚工作区中未提交的改动,也会清楚暂存区中未提交的改动。

Git 文件状态

Git 有三种状态,你的文件可能处于其中之一:

  • 已提交(committed):表示数据已经安全的保存在本地数据库中。
  • 已修改(modified):表示修改了文件,但还没保存到数据库中。
  • 已暂存(staged):表示对一个已修改文件的当前版本做了标记,使之包含在下次提交的快照中。

Git 安装

在使用 Git 前我们需要先安装 Git。Git 目前支持 Linux/Unix、Solaris、Mac和 Windows 平台上运行。
Git 各平台安装包下载地址为:git-scm.com/downloads

在 Linux 上安装

如果要在 Linux 上用二进制安装程序来安装 Git,可以使用发行版包含的基础软件包管理工具来安装。如果以 Fedora 上为例,可以使用 yum:

1
shell复制代码$ sudo yum install git

如果在基于 Debian 的发行版上,请尝试用 apt-get:

1
shell复制代码$ sudo apt-get install git

Git 官方网站上有在各种 Unix 风格的系统上安装步骤,网址为 git-scm.com/download/li…。

在 Mac 上安装

在 Mac 上安装 Git 有多种方式。最简单的方法是安装 Xcode Command Line Tools。Mavericks 10.9 或更高版本的系统中,在 Terminal 里尝试首次运行 git 命令即可。如果没有安装过命令行开发者工具,将会提示你安装。

如果想安装更新的版本,可以使用二进制安装程序。官方维护的 OSX Git 安装程序可以在 Git 官方网站下载,网址为 git-scm.com/download/ma…。

也可以将 Git OS X 安装程序作为 GitHub for Mac 的一部分来安装。它们的图形化 Git 工具有一个安装命令行工具的选项。可以从 GitHub for Mac 网站下载该工具,网址为 mac.github.com。

在 Windows 上安装

在 Windows 上安装 Git 也有几种安装方法。官方版本可以在 Git 官方网站下载。打开 git-scm.com/download/wi…,下载会自动开始。要注意这是一个名为 Git for Windows 的项目(也叫做 msysGit),和 Git 是分别独立的项目;更多信息请访问 msysgit.github.io。

另一个简单的方法是安装 GitHub for Windows。该安装程序包含图形化和命令行版本的 Git。它也能支持 Powershell,提供了稳定的凭证缓存和健全的 CRLF 设置。可以在 GitHub for Windows 网站下载,网址为 windows.github.com。

从源代码安装

有人觉得从源码安装 Git 更实用,因为可以得到最新的版本。二进制安装程序倾向于有一些滞后,当然近几年 Git 已经成熟,这个差异不再显著。

如果想要从源码安装 Git,需要安装 Git 依赖的库:curl、zlib、openssl、expat,还有 libiconv。 如果你的系统上有 yum(如 Fedora)或者 apt-get(如基于 Debian 的系统),可以使用以下命令之一来安装最小化的依赖包来编译和安装 Git 的二进制版:

1
2
3
4
shell复制代码$ sudo yum install curl-devel expat-devel gettext-devel \
openssl-devel zlib-devel
$ sudo apt-get install libcurl4-gnutls-dev libexpat1-dev gettext \
libz-dev libssl-dev

为了能够添加更多格式的文档(如 doc, html, info),需要安装以下的依赖包:

1
2
shell复制代码$ sudo yum install asciidoc xmlto docbook2x
$ sudo apt-get install asciidoc xmlto docbook2x

当安装好所有的必要依赖,可以继续从几个地方来取得最新发布版本的 tar 包。可以从 kernel.org 网站获取,网址为 www.kernel.org/pub/softwar…,或从 GitHub 网站上的镜像来获得,网址为 github.com/git/git/rel…。通常在 GitHub 上的是最新版本,但 kernel.org 上包含有文件下载签名,如果想验证下载正确性的话会用到。

接着,编译并安装:

1
2
3
4
5
6
shell复制代码$ tar -zxf git-2.0.0.tar.gz
$ cd git-2.0.0
$ make configure
$ ./configure --prefix=/usr
$ make all doc info
$ sudo make install install-doc install-html install-info

完成后,可以使用 Git 来获取 Git 的升级:

1
shell复制代码$ git clone git://git.kernel.org/pub/scm/git/git.git

Git 配置

Git 提供了一个叫做 git config 的工具,专门用来配置或读取相应的工作环境变量。

配置文件的存储位置

这些环境变量,决定了 Git 在各个环节的具体工作方式和行为。这些变量可以存放在以下三个不同的地方:

  • /etc/gitconfig 文件:系统中对所有用户都普遍适用的配置。若使用 git config 时用 –system 选项,读写的就是这个文件。
  • ~/.gitconfig 文件:用户目录下的配置文件只适用于该用户。若使用 git config 时用 –global 选项,读写的就是这个文件。
  • 当前项目的 Git 目录中的配置文件(也就是工作目录中的 .git/config 文件):这里的配置仅仅针对当前项目有效。每一个级别的配置都会覆盖上层的相同配置,所以 .git/config 里的配置会覆盖 /etc/gitconfig 中的同名变量。

在 Windows 系统上,Git 会找寻用户主目录下的 .gitconfig 文件。主目录即 HOME 变量指定的目录,一般都是 C:\Users\USER。此外,Git 还会尝试找寻 /etc/gitconfig 文件,只不过看当初 Git 装在什么目录,就以此作为根目录来定位。

查看 git 的版本信息

查看git的版本信息:

1
shell复制代码git --version

配置用户信息

当Git安装完成后首先要做的事情是配置个人的用户名称和电子邮件地址。这是非常重要的,因为每次Git提交都会使用该信息。它被永远的嵌入到了你的提交中:

1
2
shell复制代码git config --global user.name 'user_name'
git config --global user.email 'user_email'

具体可参考 Git Configuration

配置文本编辑器

设置Git默认使用的文本编辑器, 一般可能会是 Vi 或者 Vim。如果你有其他偏好,比如 Emacs 的话,可以重新设置:

1
shell复制代码git config --global core.editor emacs

配置差异分析工具

还有一个比较常用的是,在解决合并冲突时使用哪种差异分析工具。比如要改用 vimdiff 的话:

1
shell复制代码git config --global merge.tool vimdiff

查看配置信息

要检查已有的配置信息,可以使用 git config –list 命令:

1
shell复制代码git config --list

有时候会看到重复的变量名,这是因为Git从不同的的配置文件中(例如:/etc/gitconfig以及~/.gitconfig)读取相同的变量名。在这种情况下,对每个唯一的变量名,Git使用最后的那个值。

也可以直接查阅某个环境变量的设定,使用如下命令 git config {key}:

1
shell复制代码git config user.name

也可以直接查看某个配置文件的配置信息:

1
2
3
4
5
shell复制代码git config --local  --list
or
git config --global --list
or
git config --system --list

上面的三个命令分别为:查看当前仓库配置信息、查看当前用户(global)配置信息、查看系统配置信息。

git 配置文件

  1. 系统级文件 $(prefix)/etc/gitconfig,本文即 /usr/etc/gitconfig 文件。
    git config –system 用来指定读写系统级文件。初始不存在,若不存在则无影响。
  2. 用户级文件 ~/.gitconfig
    git config –global 指定只操作用户级文件。初始不存在,若不存在则无影响。
  3. Repository 级文件 .git/config
    git config –local 对写操作,则只写入 Repository 级文件(默认行为);对读操作,则只从 Repository 级文件读。
  4. git config –file config-file 则指定 config-file。

清除认证信息

1
2
3
shell复制代码git config --global --unset credential.helper
or
git config --system --unset credential.helper

上面的命令可以解决 remote: HTTP Basic: Access denied 错误。

保存认证信息

1
2
3
shell复制代码git config --local credential.helper store
or
git config --system credential.helper store

上面的命令可以解决每次提交都要输入用户名和密码的问题。具体可参考 Git Credential Storage

Git 常用命令

创建代码仓库

git init

git init 的命令格式为:

1
2
3
shell复制代码git init [-q | --quiet] [--bare] [--template=]
[--separate-git-dir ]
[--shared[=]] [directory]

第一步:创建一个代码仓库非常简单,首先,选择一个合适的地方,创建一个空目录:

1
2
3
shell复制代码mkdir repository
cd repository
pwd

mkdir命令用于创建目录,cd命令用于进入目录,pwd命令用于显示当前目录。
第二步,通过git init命令把这个目录变成Git可以管理的仓库或者通过git init <directory>命令直接指定一个目录作为Gi仓库:

1
shell复制代码git init

Git瞬间就把仓库建好了,而且告诉你是一个空的仓库(empty Git repository),细心的读者可以发现当前目录下多了一个.git的目录,这个目录是Git来跟踪管理版本库的,没事千万不要手动修改这个目录里面的文件,不然改乱了,就把Git仓库给破坏了。如果你没有看到.git目录,那是因为这个目录默认是隐藏的,用ls -ah命令就可以看见。

git clone

git clone 的命令格式为:

1
2
3
4
5
6
7
shell复制代码git clone [--template=]
[-l] [-s] [--no-hardlinks] [-q] [-n] [--bare] [--mirror]
[-o ] [-b ] [-u ] [--reference ]
[--dissociate] [--separate-git-dir ]
[--depth ] [--[no-]single-branch]
[--recurse-submodules] [--[no-]shallow-submodules]
[--jobs ] [--] []

克隆到当前目录,可以使用以下命令格式:

1
shell复制代码git clone

参数说明:

  • repository:Git 仓库。
  • directory:本地目录。

添加暂存区

git add 的命令格式为:

1
2
3
4
shell复制代码git add [--verbose | -v] [--dry-run | -n] [--force | -f] [--interactive | -i] [--patch | -p]
[--edit | -e] [--[no-]all | --[no-]ignore-removal | [--update | -u]]
[--intent-to-add | -N] [--refresh] [--ignore-errors] [--ignore-missing]
[--chmod=(+|-)x] [--] […]

git add命令可以在提交之前多次执行。它只在运行git add命令时添加指定文件的内容; 如果希望随后的更改包含在下一个提交中,那么必须再次运行git add将新的内容添加到索引。默认情况下,git add命令不会添加忽略的文件。

基本用法

1
shell复制代码git add [path]

通常是通过git add [path]的形式把[path]添加到索引库中,[path]可以是文件也可以是目录。
git不仅能判断出[path]中,修改(不包括已删除)的文件,还能判断出新添的文件,并把它们的信息添加到索引库中。

常用命令

1
2
3
4
5
shell复制代码git add .               # 将所有修改添加到暂存区
git add *Presenter # 将以Presenter结尾的文件的所有修改添加到暂存区
git add Base* # 将所有以Base开头的文件的修改添加到暂存区(例如:BaseActivity.java,BaseFragment.java)
git add Model? # 将以Model开头且后面只有一位的文件的修改添加到暂存区(例如:Model1.java,ModelA.java)
git add model/*.java # 将model目录及其子目录下所有 *.java 文件的修改添加到暂存区

代码提交

git commit 的命令格式为:

1
2
3
4
5
6
shell复制代码git commit [-a | --interactive | --patch] [-s] [-v] [-u] [--amend]
[--dry-run] [(-c | -C | --fixup | --squash) ]
[-F | -m ] [--reset-author] [--allow-empty]
[--allow-empty-message] [--no-verify] [-e] [--author=]
[--date=] [--cleanup=] [--[no-]status]
[-i | -o] [-S[]] [--] […]

git commit命令将索引的当前内容与描述更改的用户和日志消息一起存储在新的提交中。

提交暂存区到本地仓库区

1
shell复制代码git commit -m [message]

将未添加到暂存区的文件,同时提交到本地仓库区

1
2
shell复制代码git commit –am 
git commit –a –m

提交暂存区的指定文件到仓库区

1
shell复制代码git commit   ... -m

提交时显示所有diff信息

1
shell复制代码git commit -v

修改最近一次提交

1
shell复制代码git commit --amend

修改最近一次提交,并改写上一次commit的提交信息

1
shell复制代码git commit --amend -m

分支管理

git branch 的命令格式为:

1
2
3
4
5
6
shell复制代码git branch [--color[=] | --no-color] [-r | -a]
[--list] [-v [--abbrev= | --no-abbrev]]
[--column[=] | --no-column] [--sort=]
[(--merged | --no-merged) []]
[--contains []]
[--points-at

git branch命令用于列出,创建或删除分支。

列出所有本地分支

1
shell复制代码git branch

列出所有远程分支

1
shell复制代码git branch -r

列出所有本地分支和远程分支

1
shell复制代码git branch -a

新建一个分支,但依然停留在当前分支

1
shell复制代码git branch

新建一个分支,并切换到该分支

1
shell复制代码git checkout -b

git checkout 命令加上 -b 参数表示创建并切换,相当于以下两条命令:

1
2
shell复制代码git branch 
git checkout

新建一个分支,指向指定 commit

1
shell复制代码git branch

新建一个分支,与指定的远程分支建立追踪关系

1
shell复制代码git branch --track

切换到指定分支,并更新工作区

1
shell复制代码git checkout

切换到上一个分支

1
shell复制代码git checkout -

建立追踪关系,在现有分支与指定的远程分支之间

1
shell复制代码git branch --set-upstream

合并指定分支到当前分支

1
shell复制代码git merge

选择一个 commit,合并进当前分支

1
shell复制代码git cherry-pick

删除本地分支

1
shell复制代码git branch -d

删除远程分支

1
2
3
shell复制代码git push origin :
或
git push origin --delete

标签管理

git tag 的命令格式为:

1
2
shell复制代码git tag [-a | -s | -u ] [-f] [-m  | -F ]
[ |

git tag命令用于创建,列出,删除或验证使用GPG签名的标签对象。

列出所有标签

1
shell复制代码git tag

新建一个标签在当前 commit

1
shell复制代码git tag

新建一个标签在指定 commit

1
shell复制代码git tag

新建一个带标签信息的标签在当前 commit

1
shell复制代码git tag -a  -m [message]

删除本地标签

1
shell复制代码git tag -d

删除远程标签

方法一:直接删除远程标签:

1
shell复制代码git push origin --delete tag

方法二:先删除本地标签,再删除远程标签:

1
2
shell复制代码git tag -d 
git push origin :refs/tags/

查看标签信息

1
shell复制代码git show

推送某个标签到远程

1
shell复制代码git push origin

一次性推送所有尚未推送到远程的本地标签

1
shell复制代码git push origin --tags

查看信息

查看Git版本号

1
shell复制代码git --version

显示有变更的文件

1
shell复制代码git status

显示当前分支的提交历史记录

下表介绍了一些 git log 命令支持的一些常用的选项及其释义:

选项 说明
-p 按补丁格式显示每个更新之间的差异。
–word-diff 按 word diff 格式显示差异。
–stat 显示每次更新的文件修改统计信息。
–shortstat 只显示 –stat 中最后的行数修改添加移除统计。
–name-only 仅在提交信息后显示已修改的文件清单。
–name-status 显示新增、修改、删除的文件清单。
–abbrev-commit 仅显示 SHA-1 的前几个字符,而非所有的 40 个字符。
–relative-date 使用较短的相对时间显示(比如,“2 weeks ago”)。
–graph 显示 ASCII 图形表示的分支合并历史。
–pretty 使用其他格式显示历史提交信息。可用的选项包括 oneline,short,full,fuller 和 format(后跟指定格式)。
–oneline –pretty=oneline –abbrev-commit 的简化用法。

默认不用任何参数的话,git log 会按提交时间列出所有的更新,最近的更新排在最上面。可以看到,每次更新都有一个 SHA-1 校验和、作者的名字和电子邮件地址、提交时间,最后缩进一个段落显示提交说明。

1
shell复制代码git log

git log 有许多选项可以帮助你搜寻感兴趣的提交,接下来我们介绍些最常用的。

我们常用 -p 选项展开显示每次提交的内容差异,用 -<n> 则仅显示最近的两次更新:

1
shell复制代码git log -p -2

该选项除了显示基本信息之外,还在附带了每次 commit 的变化。当进行代码审查,或者快速浏览某个搭档提交的 commit 的变化的时候,这个参数就非常有用了。

--stat,仅显示简要的增改行数统计。

1
shell复制代码git log --stat

--pretty 选项,可以指定使用完全不同于默认格式的方式展示提交历史。比如用 oneline 将每个提交放在一行显示,这在提交数很大时非常有用。另外还有 short,full 和 fuller 可以用,展示的信息或多或少有些不同,后面也可以指定提交历史的次数(比如:-<n> ),具体展示效果请自己动手实践一下。

1
shell复制代码git log --pretty=oneline

format选项,可以定制要显示的记录格式,这样的输出便于后期编程提取分析。

1
shell复制代码git log --pretty=format:"%h - %an, %ar : %s"

下表列出了常用的格式占位符写法及其代表的意义:

选项 说明
%H 提交对象(commit)的完整哈希字串
%h 提交对象的简短哈希字串
%T 树对象(tree)的完整哈希字串
%t 树对象的简短哈希字串
%P 父对象(parent)的完整哈希字串
%p 父对象的简短哈希字串
%an 作者(author)的名字
%ae 作者的电子邮件地址
%ad 作者修订日期(–date= 制定的格式)
%aD 作者修订日期(RFC2822格式)
%ar 作者修订日期(相对格式,如:1 day ago)
%at 作者修订日期(UNIX timestamp)
%ai 作者修订日期(ISO 8601 格式)
%cn 提交者(committer)的名字
%ce 提交者的电子邮件地址
%cd 提交日期 (–date= 制定的格式)
%cD 提交日期(RFC2822格式)
%cr 提交日期(相对格式,如:1 day ago)
%ct 提交日期(UNIX timestamp)
%ci 提交日期(ISO 8601 格式)
%s 提交说明

除了定制输出格式的选项之外,git log 还有许多非常实用的限制输出长度的选项,也就是只输出部分提交信息。用 –since 和 –until选项显示按照时间作限制的提交,比如说具体的某一天(“2008-01-15”),或者是多久以前(“2 years 1 day 3 minutes ago”)。用 –author 选项显示指定作者的提交,用 –grep 选项搜索提交说明中的关键字。
下表还列出了其他常用的类似选项:

选项 说明
-(n) 仅显示最近的 n 条提交
–since, –after 仅显示指定时间之后的提交
–until, –before 仅显示指定时间之前的提交
–author 仅显示指定作者相关的提交
–committer 仅显示指定提交者相关的提交

远程同步

显示所有远程仓库

1
shell复制代码git remote -v

获取某个远程主机的全部更新

1
shell复制代码git fetch

获取某个远程主机的某个分支的更新

1
shell复制代码git fetch

比如,取回origin主机的master分支的更新:

1
shell复制代码git fetch origin master

显示某个远程仓库的信息

1
shell复制代码git remote show

获取某个远程主机的某个分支的更新与当前分支合并

1
shell复制代码git pull

比如,要取回origin主机的dev分支,与当前分支合并:

1
shell复制代码git pull origin dev

上面命令表示,取回origin/dev分支,再与当前分支合并。实质上,这等同于先做git fetch,再执行git merge。

1
2
shell复制代码git fetch origin
git merge origin/dev

获取某个远程主机的某个分支的更新与本地的某个分支合并

1
shell复制代码git pull  :

比如,要取回origin主机的dev分支,与本地的master分支合并:

1
shell复制代码git pull origin dev:master

将本地的当前分支自动与对应的远程主机”追踪分支”进行合并

1
shell复制代码git pull

将当前分支推送到远程主机的对应分支

1
shell复制代码git push

将当前分支到远程主机的对应分支

1
shell复制代码git push

将本地指定分支到远程主机的对应分支

1
shell复制代码git push

强行推送当前分支到远程主机的对应分支(忽略冲突)

1
shell复制代码git push  --force

推送所有分支到远程仓库

1
shell复制代码git push  --all

代码回滚

恢复暂存区的指定文件到工作区

1
shell复制代码git checkout

恢复某个commit的指定文件到暂存区和工作区

1
shell复制代码git checkout

恢复暂存区的所有文件到工作区

1
shell复制代码git checkout .

回滚添加操作

1
shell复制代码git reset

回滚最近一次提交

1
shell复制代码git reset --soft HEAD^

永久删除最后几个提交

1
shell复制代码git reset --hard HEAD~3

本文转载自: 掘金

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

爬取中国大学排名并作可视化分析 导读 爬取中国大学排名 可视

发表于 2021-10-21

本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金

导读

肥友们,最近有位粉丝找到我直言出价让我代做作业,我一听当场就急眼了。我肥学是这样的人吗?


直接就问他给多少钱,嘻嘻嘻!!!当然了多少钱不也不会干的,既然是粉丝我肯定尽量帮啊,于是我就开始了今天的博客。

爬取中国大学排名

链接:中国大学排名
其实还是挺简单的,这位粉丝肯定没有好好看我以前的文章,这种爬取说过很多次了。所以我们直接整起来。


我们直接找到要获得的这些信息的id或者class

1
2
3
4
5
6
7
8
python复制代码res=requests.get(url=url,headers=header).content.decode('utf-8')
soup=BeautifulSoup(res,'lxml')
names=soup.findAll(name="a",attrs={"class":"name-cn"})
xinxi=soup.findAll("td")
with open("中国大学.csv", 'a', encoding="utf-8", newline="") as f:
for i in range(0,len(xinxi),6):
w=csv.writer(f)
w.writerow([xinxi[i].text.strip(),xinxi[i+1].text.strip(),xinxi[i+2].text.strip(),xinxi[i+3].text.strip(),xinxi[i+4].text.strip(),xinxi[i+5].text.strip()])

然后顺利拿到信息


可视化分析
=====

词云

上面我们以经拿到了信息,我们先对这些大学集中的省市和类别做一个词云分析


可以看出来综合和北京的居多

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
python复制代码import jieba
from wordcloud import WordCloud
from matplotlib import colors
import csv
import pandas as pd

info=pd.read_csv("中国大学.csv",usecols=[2,3])
text=info
cut_text = "".join(jieba.cut(str(text)))
color_list=['#FF0000','#9955FF','#66FFFF']#建立颜色数组
colormap=colors.ListedColormap(color_list)#调用
#color_mask = cv2.imread("11.jpg")
word_cloud=WordCloud(
font_path="msyh.ttc",
background_color='black',
mode="RGBA",
prefer_horizontal=1,
#mask=color_mask,
height=200,
width=200,
scale=1,
colormap=colormap,#设置颜色
margin=5
)

word_cloud1=word_cloud.generate(cut_text)
word_cloud1.to_file('2.png')

print("图片保存成功")

条形统计图


这里运用了Echarts做得有兴趣的大佬也可以用cufflinks做也可以达到同样的效果

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
javascript复制代码//这里只把js可变部分贴了出来
option = {
title: {
text: '中国大学数据'
},
tooltip: {
trigger: 'axis'
},
legend: {
data: ['总分', '办学层次']
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
toolbox: {
feature: {
saveAsImage: {}
}
},
xAxis: {
type: 'category',
boundaryGap: false,
data: [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]
},
yAxis: {
type: 'value'
},
series: [

{
name: '总分',
type: 'line',
stack: 'Total',
data: [969.2,855.3,768.7,723.4,654.8,649.7,577.0,574.3,567.9,537.9,522.6,519.3,518.3,516.6,513.8,508.3,488.1,487.8,474.0,465.3,447.0,444.3,442.2,435.7,430.5,427.8,419.8,418.2,401.8,400.4]
},
{
name: '办学层次',
type: 'line',
stack: 'Total',
data: [37.9,36.1,34.3,35.5,35.1,36.6,40.0,32.1,31.8,34.5,32.7,30.9,34.8,30.7,32.8,33.2,34.3,34.5,32.3,31.5,28.8,32.7,30.8,30.4,32.4,32.7,30.5,30.2,35.2,31.8]
}
]
};

获取全国211以上大学的动态地理坐标

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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
python复制代码plt.rcParams['font.family'] = 'sans-serif'
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
plt.rcParams['animation.writer'] = 'html'
plt.rcParams['animation.embed_limit'] = 100


def rgba_to_rgb(img_rgba):
img_rgb = Image.new("RGB", img_rgba.size, (255, 255, 255))
img_rgb.paste(img_rgba, mask=img_rgba.split()[3])
return img_rgb


def html_to_gif(html_file, gif_file, duration=0.5):
path = html_file.replace(".html", "_frames")
images = [os.path.join(path, x) for x in sorted(os.listdir(path))]
frames = [imageio.imread(x) for x in images]
if frames[0].shape[-1] == 4:
frames = [np.array(rgba_to_rgb(Image.fromarray(x))) for x in frames]
imageio.mimsave(gif_file, frames, 'gif', duration=duration)
return gif_file

cmap = ['#2E91E5','#1CA71C','#DA16FF','#B68100','#EB663B','#00A08B','#FC0080','#6C7C32','#862A16','#620042','#DA60CA','#0D2A63'] * 100

def getCoords(geom):
if isinstance(geom, geo.MultiPolygon):
return [np.array(g.exterior) for g in geom.geoms]
elif isinstance(geom, geo.Polygon):
return [np.array(geom.exterior)]
elif isinstance(geom, geo.LineString):
return [np.array(geom)]
elif isinstance(geom, geo.MultiLineString):
return [np.array(x) for x in list(geom.geoms)]
else:
raise Exception("geom must be one of [polygon,MultiPolygon,LineString,MultiLineString]!")


# 底图数据
dfprovince = gpd.read_file("dfprovince.geojson").set_crs("epsg:4326").to_crs("epsg:2343")
dfnanhai = gpd.read_file("dfnanhai.geojson").set_crs("epsg:4326").to_crs("epsg:2343")
dfline9 = dfnanhai[(dfnanhai["LENGTH"] > 1.0) & (dfnanhai["LENGTH"] < 2.0)]

# 散点数据
df985 = gpd.read_file("中国985大学.geojson").set_crs("epsg:4326").to_crs("epsg:2343")
df211 = gpd.read_file("中国211大学.geojson").set_crs("epsg:4326").to_crs("epsg:2343")
dfpoints = pd.concat([df985, df211], axis=0)
df = pd.DataFrame({"x": [pt.x for pt in dfpoints["geometry"]],
"y": [pt.y for pt in dfpoints["geometry"]]})
df["z"] = 1.0
df.index = dfpoints["name"].values


def bubble_map_dance(df, title="中国116所211高校位置分布",
filename=None,
figsize=(8, 6), dpi=144,
duration=0.5,
anotate_points=["北京邮电大学", "南昌大学", "华中农业大学", "东华大学", "云南大学",
"陕西师范大学", "内蒙古大学", "西藏大学", "新疆大学", "青海大学", "哈尔滨工程大学"]):
fig, ax_base = plt.subplots(figsize=figsize, dpi=dpi)
ax_child = fig.add_axes([0.800, 0.125, 0.10, 0.20])

def plot_frame(i):

ax_base.clear()
ax_child.clear()
# 绘制省边界
polygons = [getCoords(x) for x in dfprovince["geometry"]]
for j, coords in enumerate(polygons):
for x in coords:
poly = plt.Polygon(x, fill=True, ec="gray", fc="white", alpha=0.5, linewidth=.8)
poly_child = plt.Polygon(x, fill=True, ec="gray", fc="white", alpha=0.5, linewidth=.8)
ax_base.add_patch(poly)
ax_child.add_patch(poly_child)

# 绘制九段线
coords = [getCoords(x) for x in dfline9["geometry"]]
lines = [y for x in coords for y in x]
for ln in lines:
x, y = np.transpose(ln)
line = plt.Line2D(x, y, color="gray", linestyle="-.", linewidth=1.5)
line_child = plt.Line2D(x, y, color="gray", linestyle="-.", linewidth=1.5)
ax_base.add_artist(line)
ax_child.add_artist(line_child)

# 设置spine格式
for spine in ['top', 'left', "bottom", "right"]:
ax_base.spines[spine].set_color("none")
ax_child.spines[spine].set_alpha(0.5)
ax_base.axis("off")

# 设置绘图范围
bounds = dfprovince.total_bounds
ax_base.set_xlim(bounds[0] - (bounds[2] - bounds[0]) / 10, bounds[2] + (bounds[2] - bounds[0]) / 10)
ax_base.set_ylim(bounds[1] + (bounds[3] - bounds[1]) / 3.5, bounds[3] + (bounds[3] - bounds[1]) / 100)

ax_child.set_xlim(bounds[2] - (bounds[2] - bounds[0]) / 2.5, bounds[2] - (bounds[2] - bounds[0]) / 20)
ax_child.set_ylim(bounds[1] - (bounds[3] - bounds[1]) / 20, bounds[1] + (bounds[3] - bounds[1]) / 2)

# 移除坐标轴刻度
ax_child.set_xticks([]);
ax_child.set_yticks([]);
k = i // 3 + 1
m = i % 3
text = "NO." + str(k)

dfdata = df.iloc[:k, :].copy()
dftmp = df.iloc[:k - 1, :].copy()

# 绘制散点图像
if len(dftmp) > 0:
ax_base.scatter(dftmp["x"], dftmp["y"], s=100 * dftmp["z"] / df["z"].mean(),
c=(cmap * 100)[0:len(dftmp)], alpha=0.3, zorder=3)
ax_child.scatter(dftmp["x"], dftmp["y"], s=100 * dftmp["z"] / df["z"].mean(),
c=(cmap * 100)[0:len(dftmp)], alpha=0.3, zorder=3)

# 添加注释文字
for i, p in enumerate(dftmp.index):
px, py, pz = dftmp.loc[p, ["x", "y", "z"]].tolist()
if p in anotate_points:
ax_base.annotate(p, xy=(px, py), xycoords="data", xytext=(-15, 10),
fontsize=10, fontweight="bold", color=cmap[i], textcoords="offset points")

# 添加标题和排名序号
# ax_base.set_title(title,color = "black",fontsize = 12)
ax_base.text(0.5, 0.95, title, va="center", ha="center",
size=12, transform=ax_base.transAxes)
ax_base.text(0.5, 0.5, text, va="center", ha="center",
alpha=0.3, size=50, transform=ax_base.transAxes)

# 添加注意力动画
if m == 0:
px, py, pz = dfdata["x"][[-1]], dfdata["y"][[-1]], dfdata["z"][-1]
p = dfdata.index[-1]
ax_base.scatter(px, py, s=800 * pz / df["z"].mean(),
c=cmap[len(dfdata) - 1:len(dfdata)], alpha=0.5, zorder=4)
ax_base.annotate(p, xy=(px, py), xycoords="data",
xytext=(-15, 10), fontsize=20, fontweight="bold",
color=cmap[k - 1], textcoords="offset points", zorder=5)

if m == 1:
px, py, pz = dfdata["x"][[-1]], dfdata["y"][[-1]], dfdata["z"][-1]
p = dfdata.index[-1]
ax_base.scatter(px, py, s=400 * pz / df["z"].mean(),
c=cmap[len(dfdata) - 1:len(dfdata)], alpha=0.5, zorder=4)
ax_base.annotate(p, xy=(px, py), xycoords="data",
xytext=(-15, 10), fontsize=15, fontweight="bold",
color=cmap[k - 1], textcoords="offset points", zorder=5)

if m == 2:
px, py, pz = dfdata["x"][[-1]], dfdata["y"][[-1]], dfdata["z"][-1]
p = dfdata.index[-1]
ax_base.scatter(px, py, s=100 * pz / df["z"].mean(),
c=cmap[len(dfdata) - 1:len(dfdata)], alpha=0.5, zorder=4)
ax_base.annotate(p, xy=(px, py), xycoords="data",
xytext=(-15, 10), fontsize=10, fontweight="bold",
color=cmap[k - 1], textcoords="offset points", zorder=5)

my_animation = animation.FuncAnimation(fig, plot_frame, frames=range(0, 3 * len(df)), interval=int(duration * 1000))

if filename is None:
try:
from IPython.display import HTML
HTML(my_animation.to_jshtml())
return HTML(my_animation.to_jshtml())
except ImportError:
pass
else:
my_animation.save(filename)
return filename

最后对肥友说

为了粉丝这把拼了 我觉得真的收费的大概也就这样了吧,好几天没有写python了这把直接给我整爽了。持续关注我后面Java和python的web都给大家整一套。最后还是那句话一起肥学,一起加油

本文转载自: 掘金

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

尤雨溪几年前开发的“玩具 vite”,才100多行代码,却十

发表于 2021-10-21

本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。

  1. 前言

大家好,我是若川。欢迎关注我的公众号若川视野,最近组织了源码共读活动,感兴趣的可以加我微信 ruochuan12 参与,已进行两个多月,大家一起交流学习,共同进步。

想学源码,极力推荐之前我写的《学习源码整体架构系列》 包含jQuery、underscore、lodash、vuex、sentry、axios、redux、koa、vue-devtools、vuex4、koa-compose、vue-next-release、vue-this、create-vue等10余篇源码文章。

最近组织了源码共读活动,大家一起学习源码。于是各种搜寻值得我们学习,且代码行数不多的源码。

在 vuejs组织 下,找到了尤雨溪几年前写的“玩具 vite”
vue-dev-server,发现100来行代码,很值得学习。于是有了这篇文章。

阅读本文,你将学到:

1
2
3
4
5
6
sh复制代码1. 学会 vite 简单原理
2. 学会使用 VSCode 调试源码
3. 学会如何编译 Vue 单文件组件
4. 学会如何使用 recast 生成 ast 转换文件
5. 如何加载包文件
6. 等等
  1. vue-dev-server 它的原理是什么

vue-dev-server#how-it-works
README 文档上有四句英文介绍。

发现谷歌翻译的还比较准确,我就原封不动的搬运过来。

  • 浏览器请求导入作为原生 ES 模块导入 - 没有捆绑。
  • 服务器拦截对 *.vue 文件的请求,即时编译它们,然后将它们作为 JavaScript 发回。
  • 对于提供在浏览器中工作的 ES 模块构建的库,只需直接从 CDN 导入它们。
  • 导入到 .js 文件中的 npm 包(仅包名称)会即时重写以指向本地安装的文件。 目前,仅支持 vue 作为特例。 其他包可能需要进行转换才能作为本地浏览器目标 ES 模块公开。

也可以看看vitejs 文档,了解下原理,文档中图画得非常好。

Native ESM based dev serve

看完本文后,我相信你会有一个比较深刻的理解。

  1. 准备工作

3.1 克隆项目

本文仓库 vue-dev-server-analysis,求个star^_^

1
2
3
4
5
6
7
8
9
10
11
12
13
sh复制代码# 推荐克隆我的仓库
git clone https://github.com/lxchuan12/vue-dev-server-analysis.git
cd vue-dev-server-analysis/vue-dev-server
# npm i -g yarn
# 安装依赖
yarn

# 或者克隆官方仓库
git clone https://github.com/vuejs/vue-dev-server.git
cd vue-dev-server
# npm i -g yarn
# 安装依赖
yarn

一般来说,我们看源码先从package.json文件开始:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
json复制代码// vue-dev-server/package.json
{
"name": "@vue/dev-server",
"version": "0.1.1",
"description": "Instant dev server for Vue single file components",
"main": "middleware.js",
// 指定可执行的命令
"bin": {
"vue-dev-server": "./bin/vue-dev-server.js"
},
"scripts": {
// 先跳转到 test 文件夹,再用 Node 执行 vue-dev-server 文件
"test": "cd test && node ../bin/vue-dev-server.js"
}
}

根据 scripts test 命令。我们来看 test 文件夹。

3.2 test 文件夹

vue-dev-server/test 文件夹下有三个文件,代码不长。

  • index.html
  • main.js
  • text.vue

如图下图所示。

test文件夹三个文件

接着我们找到 vue-dev-server/bin/vue-dev-server.js 文件,代码也不长。

3.3 vue-dev-server.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
js复制代码// vue-dev-server/bin/vue-dev-server.js
#!/usr/bin/env node

const express = require('express')
const { vueMiddleware } = require('../middleware')

const app = express()
const root = process.cwd();

app.use(vueMiddleware())

app.use(express.static(root))

app.listen(3000, () => {
console.log('server running at http://localhost:3000')
})

原来就是express启动了端口3000的服务。重点在 vueMiddleware 中间件。接着我们来调试这个中间件。

鉴于估计很多小伙伴没有用过VSCode调试,这里详细叙述下如何调试源码。学会调试源码后,源码并没有想象中的那么难。

3.4 用 VSCode 调试项目

vue-dev-server/bin/vue-dev-server.js 文件中这行 app.use(vueMiddleware()) 打上断点。

找到 vue-dev-server/package.json 的 scripts,把鼠标移动到 test 命令上,会出现运行脚本和调试脚本命令。如下图所示,选择调试脚本。

调试

VSCode 调试 Node.js 说明

点击进入函数(F11)按钮可以进入 vueMiddleware 函数。如果发现断点走到不是本项目的文件中,不想看,看不懂的情况,可以退出或者重新来过。可以用浏览器无痕(隐私)模式(快捷键Ctrl + Shift + N,防止插件干扰)打开 http://localhost:3000,可以继续调试 vueMiddleware 函数返回的函数。

如果你的VSCode不是中文(不习惯英文),可以安装简体中文插件。

如果 VSCode 没有这个调试功能。建议更新到最新版的 VSCode(目前最新版本 v1.61.2)。

接着我们来跟着调试学习 vueMiddleware 源码。可以先看主线,在你觉得重要的地方继续断点调试。

  1. vueMiddleware 源码

4.1 有无 vueMiddleware 中间件对比

不在调试情况状态下,我们可以在 vue-dev-server/bin/vue-dev-server.js 文件中注释 app.use(vueMiddleware()),执行 npm run test 打开 http://localhost:3000。

没有执行 vueMiddleware 中间件的原始情况

再启用中间件后,如下图。

执行了 vueMiddleware 中间文件变化

看图我们大概知道了有哪些区别。

4.2 vueMiddleware 中间件概览

我们可以找到vue-dev-server/middleware.js,查看这个中间件函数的概览。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
js复制代码// vue-dev-server/middleware.js

const vueMiddleware = (options = defaultOptions) => {
// 省略
return async (req, res, next) => {
// 省略
// 对 .vue 结尾的文件进行处理
if (req.path.endsWith('.vue')) {
// 对 .js 结尾的文件进行处理
} else if (req.path.endsWith('.js')) {
// 对 /__modules/ 开头的文件进行处理
} else if (req.path.startsWith('/__modules/')) {
} else {
next()
}
}
}
exports.vueMiddleware = vueMiddleware

vueMiddleware 最终返回一个函数。这个函数里主要做了四件事:

  • 对 .vue 结尾的文件进行处理
  • 对 .js 结尾的文件进行处理
  • 对 /__modules/ 开头的文件进行处理
  • 如果不是以上三种情况,执行 next 方法,把控制权交给下一个中间件

接着我们来看下具体是怎么处理的。

我们也可以断点这些重要的地方来查看实现。比如:

重要断点

4.3 对 .vue 结尾的文件进行处理

1
2
3
4
5
6
7
8
9
10
11
12
13
js复制代码if (req.path.endsWith('.vue')) {
const key = parseUrl(req).pathname
let out = await tryCache(key)

if (!out) {
// Bundle Single-File Component
const result = await bundleSFC(req)
out = result
cacheData(key, out, result.updateTime)
}

send(res, out.code, 'application/javascript')
}

4.3.1 bundleSFC 编译单文件组件

这个函数,根据 @vue/component-compiler 转换单文件组件,最终返回浏览器能够识别的文件。

1
2
3
4
5
6
7
8
9
10
11
js复制代码const vueCompiler = require('@vue/component-compiler')
async function bundleSFC (req) {
const { filepath, source, updateTime } = await readSource(req)
const descriptorResult = compiler.compileToDescriptor(filepath, source)
const assembledResult = vueCompiler.assemble(compiler, filepath, {
...descriptorResult,
script: injectSourceMapToScript(descriptorResult.script),
styles: injectSourceMapsToStyles(descriptorResult.styles)
})
return { ...assembledResult, updateTime }
}

接着我们来看 readSource 函数实现。

4.3.2 readSource 读取文件资源

这个函数主要作用:根据请求获取文件资源。返回文件路径 filepath、资源 source、和更新时间 updateTime。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
js复制代码const path = require('path')
const fs = require('fs')
const readFile = require('util').promisify(fs.readFile)
const stat = require('util').promisify(fs.stat)
const parseUrl = require('parseurl')
const root = process.cwd()

async function readSource(req) {
const { pathname } = parseUrl(req)
const filepath = path.resolve(root, pathname.replace(/^\//, ''))
return {
filepath,
source: await readFile(filepath, 'utf-8'),
updateTime: (await stat(filepath)).mtime.getTime()
}
}

exports.readSource = readSource

接着我们来看对 .js 文件的处理

4.4 对 .js 结尾的文件进行处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
js复制代码if (req.path.endsWith('.js')) {
const key = parseUrl(req).pathname
let out = await tryCache(key)

if (!out) {
// transform import statements
// 转换 import 语句
// import Vue from 'vue'
// => import Vue from "/__modules/vue"
const result = await readSource(req)
out = transformModuleImports(result.source)
cacheData(key, out, result.updateTime)
}

send(res, out, 'application/javascript')
}

针对 vue-dev-server/test/main.js 转换

1
2
3
4
5
6
7
8
9
10
js复制代码import Vue from 'vue'
import App from './test.vue'

new Vue({
render: h => h(App)
}).$mount('#app')

// 公众号:若川视野
// 加微信 ruochuan12
// 参加源码共读,一起学习源码
1
2
3
4
5
6
7
8
9
10
js复制代码import Vue from "/__modules/vue"
import App from './test.vue'

new Vue({
render: h => h(App)
}).$mount('#app')

// 公众号:若川视野
// 加微信 ruochuan12
// 参加源码共读,一起学习源码

4.4.1 transformModuleImports 转换 import 引入

recast

validate-npm-package-name

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
js复制代码const recast = require('recast')
const isPkg = require('validate-npm-package-name')

function transformModuleImports(code) {
const ast = recast.parse(code)
recast.types.visit(ast, {
visitImportDeclaration(path) {
const source = path.node.source.value
if (!/^\.\/?/.test(source) && isPkg(source)) {
path.node.source = recast.types.builders.literal(`/__modules/${source}`)
}
this.traverse(path)
}
})
return recast.print(ast).code
}

exports.transformModuleImports = transformModuleImports

也就是针对 npm 包转换。 这里就是 "/__modules/vue"

1
js复制代码import Vue from 'vue' => import Vue from "/__modules/vue"

4.5 对 /__modules/ 开头的文件进行处理

1
js复制代码import Vue from "/__modules/vue"

这段代码最终返回的是读取路径 vue-dev-server/node_modules/vue/dist/vue.esm.browser.js 下的文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
js复制代码if (req.path.startsWith('/__modules/')) {
//
const key = parseUrl(req).pathname
const pkg = req.path.replace(/^\/__modules\//, '')

let out = await tryCache(key, false) // Do not outdate modules
if (!out) {
out = (await loadPkg(pkg)).toString()
cacheData(key, out, false) // Do not outdate modules
}

send(res, out, 'application/javascript')
}

4.5.1 loadPkg 加载包(这里只支持Vue文件)

目前只支持 Vue 文件,也就是读取路径 vue-dev-server/node_modules/vue/dist/vue.esm.browser.js 下的文件返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
js复制代码// vue-dev-server/loadPkg.js
const fs = require('fs')
const path = require('path')
const readFile = require('util').promisify(fs.readFile)

async function loadPkg(pkg) {
if (pkg === 'vue') {
// 路径
// vue-dev-server/node_modules/vue/dist
const dir = path.dirname(require.resolve('vue'))
const filepath = path.join(dir, 'vue.esm.browser.js')
return readFile(filepath)
}
else {
// TODO
// check if the package has a browser es module that can be used
// otherwise bundle it with rollup on the fly?
throw new Error('npm imports support are not ready yet.')
}
}

exports.loadPkg = loadPkg

至此,我们就基本分析完毕了主文件和一些引入的文件。对主流程有个了解。

  1. 总结

最后我们来看上文中有无 vueMiddleware 中间件的两张图总结一下:

没有执行 vueMiddleware 中间件的原始情况

启用中间件后,如下图。

执行了 vueMiddleware 中间文件变化

浏览器支持原生 type=module 模块请求加载。vue-dev-server 对其拦截处理,返回浏览器支持内容,因为无需打包构建,所以速度很快。

1
2
3
html复制代码<script type="module">
import './main.js'
</script>

5.1 import Vue from ‘vue’ 转换

1
2
3
4
5
6
7
js复制代码// vue-dev-server/test/main.js
import Vue from 'vue'
import App from './test.vue'

new Vue({
render: h => h(App)
}).$mount('#app')

main.js 中的 import 语句
import Vue from ‘vue’
通过 recast 生成 ast 转换成 import Vue from "/__modules/vue"
而最终返回给浏览器的是 vue-dev-server/node_modules/vue/dist/vue.esm.browser.js

5.2 import App from ‘./test.vue’ 转换

main.js 中的引入 .vue 的文件,import App from './test.vue'
则用 @vue/component-compiler 转换成浏览器支持的文件。

5.3 后续还能做什么?

鉴于文章篇幅有限,缓存 tryCache 部分目前没有分析。简单说就是使用了 node-lru-cache 最近最少使用 来做缓存的(这个算法常考)。后续应该会分析这个仓库的源码,欢迎持续关注我@若川。

非常建议读者朋友按照文中方法使用VSCode调试 vue-dev-server 源码。源码中还有很多细节文中由于篇幅有限,未全面展开讲述。

值得一提的是这个仓库的 master 分支,是尤雨溪两年前写的,相对本文会比较复杂,有余力的读者可以学习。

也可以直接去看 vite 源码。

看完本文,也许你就能发现其实前端能做的事情越来越多,不由感慨:前端水深不可测,唯有持续学习。

最后欢迎加我微信 ruochuan12 交流,参与 源码共读 活动,大家一起学习源码,共同进步。


关于 && 交流群

最近组织了源码共读活动,感兴趣的可以加我微信 ruochuan12 参与,长期交流学习。

作者:常以若川为名混迹于江湖。欢迎加我微信ruochuan12。前端路上 | 所知甚少,唯善学。

关注公众号若川视野,每周一起学源码,学会看源码,进阶高级前端。

若川的博客

segmentfault若川视野专栏,开通了若川视野专栏,欢迎关注~

掘金专栏,欢迎关注~

知乎若川视野专栏,开通了若川视野专栏,欢迎关注~

github blog,求个star^_^~

本文转载自: 掘金

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

IDEA这样配置,好用到爆炸!!! 1 idea 简介 2

发表于 2021-10-20
  1. idea 简介

IDEA 全称 IntelliJ IDEA ,是 JetBrains 公司 使用 java 编程语言开发的集成环境,
这家公司总部位于捷克共和国的首都布拉格。

公司旗下还有其它产品,例如 WebStorm、PyCharm、PhpStorm、DataGrip 等。

IntelliJ 在业界被公认为是地球上最好的 java 开发工具。尤其在代码自动提示、重构、版本工具、JUnit、代码分析等方面的功能可以说是超常的。

1.1 版本区别

  • Ultimate:旗舰版,收费(限30天免费试用),功能无限制。旗舰版本支持 java、HTML、CSS、PHP、Python 等开发语言
  • Community:社区版,免费,功能有限制。社区版只支持 Java、Kotlin 等少数语言。

虽然社区版免费,但是建议用旗舰版,毕竟功能齐全。

  1. 下载与安装

2.1 安装

官网地址:

1
css复制代码https://www.jetbrains.com/idea/download/#section=windows

这里我们选择下载旗舰版(收费,可以免费试用30天):

双击下载好的软件

2.2 激活

在盗版软件横行的时代,能够支持正版显得多么弥足珍贵。

有钱的还是建议购买正版,支持人家的劳动成果,实在没钱可以去下载社区版或者免费试用 30 天。

  1. 常用配置

3.1 设置主题

1
css复制代码File -> Settings -> Appearance & Behavior -> Appearance

官方默认主题是 Darcula

3.2 设置字体大小

1
css复制代码File -> Settings -> Editor -> Font

3.3 设置自动导包

1
css复制代码File -> Settings -> Editor -> General -> Auto Import

3.4 设置显示行号和方法间的分隔符

1
css复制代码File -> Settings -> Editor -> General -> Appearance

3.5 设置自定义注释

1
rust复制代码File -> Settings -> Editor -> File and Code Templates -> Includes -> File Header

1
2
3
4
5
perl复制代码/**
@author 公众号:eclipse编程
@description TODO
@date ${YEAR}-${MONTH}-${DAY} ${TIME}
*/

3.6 自定义代码模板

1
css复制代码File -> Settings -> Editor -> Live Templates

然后输入模板名称,按下 Tab 键

3.7 设置编码格式

1
css复制代码File -> Editor -> File Encodings

3.8 设置忽略隐藏文件和文件夹

有时候我们新建完项目之后会显示一堆无用的文件夹或者文件,这里可以设置忽略隐藏。

1
css复制代码File -> Editor -> File Types -> Ignored Files and Folders


设置完效果

3.9 配置 maven

1
rust复制代码File -> Settings -> Build, Execution, Deployment -> Maven

3.10 连接数据库

这里我们选择连接 Mysql 数据库

3.11 新建项目初始化配置

3.11.1 设置初始化项目的 jdk

3.11.2 设置初始化项目的 maven


3.12 设置自动编译

1
rust复制代码File -> Settings -> Build,Execution,Deployment -> compiler

  1. 创建项目

4.1 新建 java 项目

1
rust复制代码File -> New -> Project -> Java

4.2 新建 javaweb 项目

1
rust复制代码File -> New -> Project -> Java Enterprise

4.3 新建 Maven 项目

1
rust复制代码File -> New -> Project -> Maven

4.4 新建 springboot 项目

1
rust复制代码File -> New -> Project -> Spring Initializr

  1. 常用插件

插件有利于提升我们的工作效率。idea 安装插件的位置如下:

1
rust复制代码File -> Settings -> Plugins


这里给大家推荐几款 idea 常用的插件

5.1 Translation


这个插件可以帮助我们翻译变量名、枚举等。

直接选中你想要翻译的词,然后右键选择 Translation。

5.2 Maven Helper


这个插件可以帮助我们查找和排除冲突依赖项。

5.3 Lombok


Lombok 通过简单的注解帮我们自动生成Setter、Getter等方法

1
2
3
4
5
6
7
8
java复制代码@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class User {
private String name;
private Integer age;
}

5.4 MybatisX


我们可以通过点击 Mapper 中左侧红色的小鸟直接找到对应的 xml 文件或者方法。

5.5 GsonFormatPlus

GsonFormatPlus 可以帮助我们将 json 格式的数据转成指定的 Object。

选中一个实体类鼠标右键


5.6 RestfulTool

RestfulTool 是一套 Restful 服务开发辅助工具集,可以预览项目中所有的接口信息,还可以通过其自带的 HTTP 请求工具进行接口测试。

  1. 常用快捷键

6.1 当前文件中查找

1
r复制代码 Ctrl+F

6.2 当前文件中替换文本

1
复制代码Ctrl+R

6.3 快速搜索文件

1
复制代码Ctrl+N

6.4 快速生成 Getter、Setter、构造器等方法

1
sql复制代码Alt+Insert

6.5 重写方法

1
复制代码Ctrl+O

6.6 复制当前行

1
复制代码Ctrl+D

6.7 全局查找

1
r复制代码Ctrl+Shift+F

6.8 全局替换

1
复制代码Ctrl+Shift+R

6.9 快速定位到文件行首

1
arduino复制代码Ctrl + home

6.10 快速定位到文件行末

1
arduino复制代码Ctrl + end

6.11 显示当前文件的层次

1
复制代码Ctrl + H

6.12 提示方法的参数

1
css复制代码Ctrl + P

6.13 显示最近的文件浏览记录

1
复制代码Ctrl + E

6.14 跳转到指定行处

1
复制代码Ctrl + G

6.15 切换窗口

1
复制代码Ctrl + Tab

6.16 快速修复代码

1
复制代码Alt + Enter

6.17 格式化整个文件的代码

1
复制代码Ctrl + Alt + L

6.18 优化导入的类

1
复制代码Ctrl + Alt + O

6.19 跳转到方法的实现处

1
css复制代码Ctrl + Alt + B

6.20 快速返回引用变量

1
复制代码Ctrl + Alt + V

6.21 向下插入行

1
bash复制代码shift+enter

6.22 重构方法

1
复制代码Ctrl + Alt + M

注:要避免和其他软件快捷键冲突。

本文转载自: 掘金

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

更好的 java 重试框架 sisyphus 入门简介 Wh

发表于 2021-10-20

What is Sisyphus

sisyphus 综合了 spring-retry 和 gauva-retrying 的优势,使用起来也非常灵活。

为什么选择这个名字

我觉得重试做的事情和西西弗斯很相似。

一遍遍的重复,可能徒劳无功,但是乐此不疲。

人一定要想象西西弗斯的快乐。——加缪

其他原因

以前看了 java retry 的相关框架,
虽然觉得其中有很多不足之处。但是没有任何重复造轮子的冲动,觉得是徒劳无功的。

当然这段时间也看了 Netty 的接口设计,和 Hibernate-Validator 的接口设计,觉得非常的巧妙。

觉得把这些东西结合,可以写出一个还不错的框架,就写了起来。

至少,sisyphus 是快乐的。

关于版本

这次的框架版本采用了比较保守的方式,使用 0.0.X。

原因有两个:

(1)我认为前期出于实验阶段。代码并不成熟,自测也不充分。所以不适合用于生产。

(2)这样可以快速迭代,而不至于为了追求更好导致版本特性迟迟无法迭代。

版本特性

我用了 5 个版本,实现了主要的特性:

(1)基于 fluent 接口声明式调用

(2)基于 annotation 的代理实现

(3)spring 的整合实现

(4)自定义注解的实现

未完成的工作

  • 更方便的工具类。
  • 使用文档
  • 测试代码

感受

想法是很容易产生的,但是想把它变成一个稳定的框架需要很长的时间锤炼。

为什么选择 sisyphus

作为开发者,我们一般都会选择比较著名的框架。

比如 guava-retrying spring-retry。

或者干脆自己写一个。

为什么不是 guava-retrying/spring-retry

java retry 这篇文章中我列举了常见的实现方式
以及上述的两种框架,也讲述了其中的不足。

guava-retrying 优缺点

优点

  • 使用灵活
  • fluent 优雅写法
  • 提供足够多的实现

缺点

  • 没有默认基于注解的实现
  • 重试策略设计并不友好

spring-retry

优点

  • 使用简单

缺点

  • 重试条件单一
  • 重试等待策略单一
  • 无法自定义注解

为什么不自己写一个

个人感受

我作为一名开发,平时说实在的,看到重试。

我肯定会偷懒写一个 for 循环,重试几次就结束了。

因为时间不允许。

如果你更勤快一点,就可以选择 spring-retry/guava-retrying。如果你熟悉他们的优缺点的话。

如果你渴望创造

sisyphus 所有的实现都是基于接口的。

你完全可以实现自己的实现,所有的东西基本完全可以被替换。

当然一些常见的策略实现,项目的基本框架都有详尽的注释,当做参考也可以有一点帮助。

sisyphus 做的更多的事情

netty 的灵感

参考了 netty 的设计,保证接口实现的一致性。

而且 sisyphus 还做了更多,还保证了接口和注解之间的一致性。

使用引导类,保证使用时的便利性,后期拓展的灵活性。

hibernate-validator

hibernate-validator 的作者是我知道为数不多的对于 java 注解应用很棒的开发者。(虽然所知甚少)

自定义注解就是从这个框架中学来的。

与 spring 为伍

spring 基本与我们的代码形影不离,所以你可以很简单的结合 spring.

就像你使用 spring-retry 一样。

快速开始

需要

jdk1.7+

maven 3.x+

maven 引入

sisyphus 使用 maven 管理 jar,

1
2
3
4
5
xml复制代码<plugin>
<groupId>com.github.houbb</groupId>
<artifactId>sisyphus-core</artifactId>
<version>0.0.6</version>
</plugin>

编码

作为入门案例,我们首先介绍些简单灵活的声明式编程

1
2
3
4
5
6
7
8
9
10
java复制代码public void helloTest() {
Retryer.<String>newInstance()
.callable(new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println("called...");
throw new RuntimeException();
}
}).retryCall();
}

代码简介

Retryer.<String>newInstance() 创建引导类的实例,String 是 callable 也就是待重试方法的返回值类型。

callable() 指定待重试的方法实现。

retryCall() 触发重试调用。

日志信息

1
2
3
erlang复制代码called...
called...
called...

以及一些异常信息。

等价配置

上面的配置其实有很多默认值,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
java复制代码/**
* 默认配置测试
*/
@Test(expected = RuntimeException.class)
public void defaultConfigTest() {
Retryer.<String>newInstance()
.maxAttempt(3)
.listen(RetryListens.noListen())
.recover(Recovers.noRecover())
.condition(RetryConditions.hasExceptionCause())
.retryWaitContext(RetryWaiter.<String>retryWait(NoRetryWait.class).context())
.callable(new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println("called...");
throw new RuntimeException();
}
}).retryCall();
}

这些默认值都是可以配置的。

比如什么时候触发重试?重试几次?多久触发一次重试?这些都会在下面的章节进行详细讲解。

小结

本文简单介绍了重试框架的设计缘由,及其使用入门。

java 重试框架 sisyphus 开源地址

希望本文对你有所帮助,如果喜欢,欢迎点赞收藏转发一波。

我是老马,期待与你的下次重逢。

NO_SIGN.png

本文转载自: 掘金

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

我的新书《Python3网络爬虫开发实战(第二版)》获得 P

发表于 2021-10-20

别急,这书现在还没上市哈,但很快了。

最近朋友们一直在催:你的第二版爬虫书怎么还不出来啊,我都等了好几年了!你不是前几个月就完稿了吗?咋这么慢。

别急,这下是真的很快就要上市了。

为啥我的第二版书“难产”了呢?原因有好多:

  • 一个就是工作原因,之前第一版书是读研期间写的,工作之后发现书中的一些案例已经过期了,于是就决定写第二版。但工作毕竟是工作,工作的内容还是需要放在第一位的,所以第二版书的内容基本都是利用下班之后或者周末的时间写出来的,所以进度很慢。
  • 另外一个就是为了解决案例过期的问题,之前耗费了很多精力自己制作了爬虫案例平台,scrape.center/,里面做了几十个爬虫案例,书中对这些案例进行了配合讲解,这就解决了一些案例过期的问题,也能更好地帮助读者进行练习,前前后后这个案例平台做了也有小半年的时间。

等书籍完稿之后,其实还有不少的事情,比如审稿、修改、封面设计、推荐语等等,总之步骤是很繁琐的,改天再写个文章专门介绍下写书这件事。

单独说说推荐语。

推荐语就是印在书的背面的各位专家对本书的评价和推荐内容,一般就是几句话加上专家的 Title。就在推荐语这一块上,我还憋了个“大招”,那就是让 Python 之父 Guido van Rossum 给我写个推荐语。

Python 之父想必大家应该知道是谁吧?就是 Guido van Rossum,Python 就是他在 1989 年编写出来的。

有人说,你还真敢想,还想让 Python 之父给你这本“名不见经传”的书写推荐语?闹呢?人家怎么可能会理你?

其实想想确实不可思议的,但我还是想试试,如果真的能拿到 Python 之父的推荐,那简直是荣幸之至!

另外由于我在微软工作,Python 之父 Guido 2020 年也宣布加入微软。所以,我和他也多少是同一个公司的了(但显然职位差距过大)。

所以,没准还是有机会的呢?

好,那么问题来了,我这书都是中文写的,Python 之父是看不懂中文的,咋办呢?难道我要把全书都翻译一遍吗?

是的,还真得是这样,我不能给个中文内容或者啥也不给就干巴巴地要求他帮我写推荐语吧?这也太滑稽了。

在此之前,有好几个月时间整本书的内容还在审稿和修改,等稿子审完的时候差不多就九月份了,然后九月份左右,我就拿到了编辑那边给到的全书审核完毕的 Word 文档,但还没正式排版。看了看,这个 Word 文档一共有 1000 多页,好家伙,我真的不敢相信我写了这么多。

但也没办法,那也得翻译一遍。

如果我全部手工翻译也太麻烦了,于是我就从网上找了一些工具,比如 Word 全文翻译工具,其背后就是对接了 Google 翻译,但这些工具还有上传大小限制,于是我就又把各个章节进行了拆分,一部分一部分地翻译完了再合并起来。

当然大家知道,Google 翻译肯定质量不能保证的吧,比如一些说法和名词就翻译不太准确,我就得进行手工审查和修改。所以我又花了好多天时间对全文进行检查和修改,包括不准确的标题、表述、名词等等。然后我还对整个目录索引重新规整了一遍。

总之整个翻译准备过程差不多也花了半个月的时间,最后翻译完了英文版差不多 1600 页,如图所示,比如目录最后一节就是 1561 页了:

全书翻译

但是,这个其实还不够,如果把这个书丢过去,人家没时间看怎么办?我总得好好介绍下整本书的情况吧。

于是我又把书的介绍和前言又翻译了一遍,内容包括:写书的初衷、整本书的介绍、整体章节的规划,这样的话能帮助 Guido 更好地理解整本书的脉络和内容。

于是又有了如下的内容:

书的介绍

好像还是不够,万一 Guido 觉得写推荐语很麻烦怎么办?如果我能给他提供几个 Draft Candidate 候选是不是更有帮助一些?这样他可以找些灵感或者稍微修改下就好了,于是我又草拟了一些候选推荐语,整理了一个文档。

嗯,好像就准备差不多了,一共三个东西:

  • 全书的翻译内容
  • 书的内容介绍
  • 候选推荐语

接下来就是联系 Guido 了,心中一阵忐忑。

为了更正式一点,我发了一封邮件给 Guido,邮件整体的内容就是:先表示下对他的感谢和敬佩,然后介绍下自己的基本情况和书的基本情况,比如我是做什么的,第一版书在中国的销量等等,接着开门见山地说想要请他帮忙写一段推荐语。然后后面就附上我的书的一些详细信息,比如我整理的全文翻译书稿、内容介绍、候选推荐语等。

内容如下:

我怀着十分忐忑而又激动的心情,按下了发送键。

接下来就是漫长的等待。

我每天早上醒来都会看看邮件有没有收到 Guido 的回信。

没回。

还是没回。

还是还是没回。

还是还是还是没回。

好像要凉了。

好像凉了。

好像真凉了。

真的凉了。

可能真的是太忙或者没看到我的邮件吧。

后来,我就联系了我们部门的总 Director(这里就不透露具体信息了),看看他能不能帮我 connect 一下 Guido,他爽快地答应了。

不太清楚我的Director怎么联系的,他说单独找了下 Guido,可能是发信或者内部联系。

然后第二天,他就告诉我,得到 Guido 回复了!Guido 说确实近期比较忙,也很希望能有一个 Draft Candidate 推荐语提供给他,他可以结合着书的内容来改写一下会更好。

我的 Director 人也非常好,他也给我出了很多建议,还帮我修改了下之前我写的 Draft Candidate 推荐语给他,转发了我的邮件还帮我又介绍了下。

最后,Guido 给了最终的推荐语!!

Guido 的回信,遮盖了部分邮件内容

收到这封邮件的时候我真的开心地要跳起来了,太开心了!我的书得终于拿到了 Python 之父的推荐啦!

正文如下:

I am happy to see that Python is so widely used in the Chinese IT community. I hope this book will help more people understand Python and web crawling/scraping. *

–Guido van Rossum, creator of Python, Distinguished Engineer, Microsoft

大意就是,我非常高兴 Python 能够在中国社区得到这么广泛的应用,希望本书能够帮助更多的朋友学习 Python 和网络爬虫。

嗯,整个过程比较漫长,但真的非常开心最终有了一个好的结果,真的非常荣幸能获得 Python 之父的推荐,同时也非常感谢我的 Director 帮我联系和出建议。

得到 Guido 的推荐语之后,后面的流程就很快了,还有其他各位专家的推荐语和序我也都联系好了,真的非常感谢各位专家的推荐,书中都会一一表示感谢。

现在内容也审核完毕了,最后把推荐语添加到封面上就投入印刷了!

请大家再耐心等待一小段时间,很快《Python3网络爬虫开发实战(第二版)》就要跟大家见面啦~

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

本文转载自: 掘金

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

【微服务的绊脚石--分布式事务】 SEATA解决方案介绍

发表于 2021-10-20

微服务模式面临的挑战

在微服务大行其道的今天,很多企业都采用了这种架构模式来提供服务。采用微服务模式会带来很多好处,比如高度可扩展,更短的开发周期,更加易于部署,更加开放的技术栈等等。但同样也带来了很多问题,比如我们今天的主题:分布式事务处理(Distributed Transaction Processing, DTP)。

我司的系统采用了微服务架构,将一个庞大的单体系统拆分出了多个独立的微服务,由于存在一些需要跨服务的系统功能,服务间的调用必不可少。那么问题来了,在单体系统中,事务的管理相对简单,在SpringBoot的应用中一个@Transactional注解就可以轻松搞定。而在微服务中,下游的服务出现问题时,上游的服务可能已经提交了事务,如何管理分布式事务成了微服务不得不面对的绊脚石。

应对方法

业界有几种相对成熟的应对之法,下面简单介绍3种:

  • 2PC/XA模式: 二阶段提交(2 Phase Commit)以及基于2PC的XA规范
  • SAGA模式: 长篇小说模式
  • TCC模式: Try, Confirm / Cancel

2PC/XA

在分布式系统中,每个节点虽然可以知晓自己的操作时成功或者失败,却无法知道其他节点的操作的成功或失败。2PC认为,当一个事务跨越多个节点时,为了保持事务的ACID特性,需要引入一个作为协调者的组件来统一掌控所有节点(称作参与者)的操作结果并最终指示这些节点是否要把操作结果进行真正的提交(比如将更新后的数据写入磁盘等等)。因此,二阶段提交的思路可以概括为: 参与者将操作成败通知协调者,再由协调者根据所有参与者的反馈情报决定各参与者是否要提交操作还是中止操作。

XA规范是OpenGroup组织关于分布式事务处理(DTP)的规范。该规范引入了全局的事务管理器(TM) 和局部的资源管理器(RM),类似2PC中的协调者和参与者。XA规范主要定义了二者之间的接口,它采用二阶段提交来保证所有资源同时提交或回滚特定的事务。目前主流数据库都是支持XA协议的,比如Oracle, MySQL, DB2。在Java中使用MysqlXAConnection可以很容易地实现分布式事务处理。

SAGA

SAGA模式最早出现于1987年普林斯顿大学的一篇论文,目的是为了处理计算机系统中的长事务(Long Lived Transactions, LLTs)的问题。 那么究竟多长的事务才算LLT呢,按论文中的说法,长事务是按小时、天计算的事务。

Saga的命名源于单词本身的含义,即长篇小说。核心是将长事务分解为多个子事务的集合,失败时不做回滚,而是采用补偿动作。补偿动作从语义角度撤消了事务Ti的行为,但未必能将数据库返回到执行Ti时的状态。(例如,如果事务触发导弹发射, 则可能无法撤消此操作)

  • 每个Saga由一系列sub-transaction Ti 组成
  • 每个Ti 都有对应的补偿动作Ci,补偿动作用于撤销Ti造成的结果

Saga定义了两种恢复策略:

  • Backward recovery,向后恢复,补偿所有已完成的事务,如果任一子事务失败。执行顺序是T1, T2, ..., Tj, Cj,..., C2, C1,其中j是发生错误的sub-transaction,这种做法的效果是撤销掉之前所有成功的sub-transation,使得整个Saga的执行结果撤销。
  • Forward recovery,向前恢复,重试失败的事务,假设每个子事务最终都会成功。适用于必须要成功的场景,执行顺序是类似于这样的:T1, T2, ..., Tj(失败), Tj(重试),..., Tn,其中j是发生错误的sub-transaction。该情况下不需要Ci。

Saga有两种实现方式:

  • Orchestration-based saga中央协调型

image.png

使用中央协调型SAGA创建的EC订单包含以下步骤:

  1. Order Service 接收 POST /orders 请求并创建 Create Order saga Orchestrator
  2. saga 协调器(Orchestrator)创建一个处于 PENDING 状态的订单
  3. 然后它向客户服务发送一个 Reserve Credit 命令
  4. 客户服务部尝试保留Credit
  5. 然后它会发回一条指示结果的回复消息
  6. saga 协调器批准或拒绝订单
  • Choreography-based saga地方自治型

image.png

使用地方自治型SAGA创建的EC订单包含以下步骤:

  1. OrderService接收 POST /orders 请求并创建一个处于 PENDING 状态的订单
  2. 然后它发出一个 Order Created 事件
  3. 客户服务的事件处理程序尝试保留Credit
  4. 然后它发出一个指示结果的事件
  5. OrderService 的事件处理程序批准或拒绝订单

Saga不提供ACID保证,因为原子性和隔离性不能得到满足。原论文描述如下:

full atomicity is not provided. That is, sagas may view the partial results of other sagas

TCC

TCC是三个单词的首字母缩写:Try, Confirm / Cancel。TCC模式需要准备一个协调器(orchestrator)来控制一系列进程,实现对多个资源(服务)的调用控制。 要调用的服务必须实现 Try / Confirm / Cancel 三个 API。

需要注意 TCC 模式可能需要两倍的时间。 这是因为 TCC 模式要对每个服务进行两次通信,并且需要在收到所有服务的Try响应后才开始确认。

TCC 模式的特点是服务会经过一个临时(pending)状态,确认后才进入最终状态,并且取消过程很容易。 例如,电子邮件服务发送请求将电子邮件标记为准备发送,确认请求发送电子邮件。 相应的取消请求只会被标记。 而在 Saga 模式中如果发送一封电子邮件,相应的补偿动作会发送另一封解释取消的电子邮件。

和SAGA对比

Saga相比TCC的缺点是缺少预留动作,导致补偿动作的实现比较麻烦:Ti就是commit,比如一个业务是发送邮件,在TCC模式下,先保存草稿(Try)再发送(Confirm),撤销的话直接删除草稿(Cancel)就行了。而Saga则就直接发送邮件了(Ti),如果要撤销则得再发送一份邮件说明撤销(Ci),实现起来有一些麻烦。

如果把上面的发邮件的例子换成:A服务在完成Ti后立即发送Event到ESB(企业服务总线,可以认为是一个消息中间件),下游服务监听到这个Event做自己的一些工作然后再发送Event到ESB,如果A服务执行补偿动作Ci,那么整个补偿动作的层级就很深。

不过没有预留动作也可以认为是优点:

  • 有些业务很简单,套用TCC需要修改原来的业务逻辑,而Saga只需要添加一个补偿动作就行了。
  • TCC最少通信次数为2n,而Saga为n(n=sub-transaction的数量)。
  • 有些第三方服务没有Try接口,TCC模式实现起来就比较tricky了,而Saga则很简单。
  • 没有预留动作就意味着不必担心资源释放的问题,异常处理起来也更简单(请对比Saga的恢复策略和TCC的异常处理)。

SEATA方案

Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。在 Seata 开源之前,Seata 对应的内部版本在阿里经济体内部一直扮演着分布式一致性中间件的角色,帮助经济体平稳的度过历年的双11,对各BU业务进行了有力的支撑。经过多年沉淀与积累,商业化产品先后在阿里云、金融云进行售卖。2019.1 为了打造更加完善的技术生态和普惠技术成果,Seata 正式宣布对外开源,未来 Seata 将以社区共建的形式帮助其技术更加可靠与完备。

AT模式

基于SEATA的用户调查,我发现在各大企业中使用最多的还是AT模式。AT这两个字母的含义我猜可能是AlibabaTransaction的缩写吧。AT是基于2PC模式和JDBC实现的。详细的实现原理请参考官方文档。

使用前提

  • 基于支持本地 ACID 事务的关系型数据库。
  • Java 应用,通过 JDBC 访问数据库。

整体机制

两阶段提交协议的演变:

  • 一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
  • 二阶段:
+ 提交异步化,非常快速地完成。
+ 回滚通过一阶段的回滚日志进行反向补偿。

下面我们来看一个采用AT模式的实例。

服务调用

以经典的EC订单场景为例,Business服务接受用户请求后,调用库存服务扣减库存,然后调用订单服务创建订单,订单服务调用账户服务扣减账户余额。例子中的4个服务属于Seata Client端(其中Business服务是TM,其他服务属于RM)。另外还有一个Seata Server(TC),与TC的交互完全由Seata库负责,业务开发人员并无感知。

UserBusinessServiceStorageServiceOrderServiceAccountServiceGET /purchase/commit/deductsuccess/debit/?money=5successsuccesstrueUserBusinessServiceStorageServiceOrderServiceAccountService
如果在调用链的某一处发生异常,则需要回滚前面已经执行的步骤。

UserBusinessServiceStorageServiceOrderServiceAccountService❌ exceptionrollbackrollbackrollbackparGET /purchase/rollback/deductsuccess/debit/?money=5failurefailurefalseUserBusinessServiceStorageServiceOrderServiceAccountService

代码实现

首先我们来看BusinessController,它实现了两个API,一个用于正常提交,用户ID是1001,一个用于回滚全局事务,用户ID是1002。

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
java复制代码/**
* 购买下单,模拟全局事务提交
*
* @return
*/
@RequestMapping("/purchase/commit")
public Boolean purchaseCommit(HttpServletRequest request) {
businessService.purchase("1001", "2001", 1);
return true;
}

/**
* 购买下单,模拟全局事务回滚
*
* @return
*/
@RequestMapping("/purchase/rollback")
public Boolean purchaseRollback() {
try {
businessService.purchase("1002", "2001", 1);
} catch (Exception e) {
e.printStackTrace();
return false;
}

return true;
}

接下来是BusinessService的实现,是不是太简单了!只用了一个@GlobalTransactional就实现了分布式事务处理! 其中xxxClient中利用了RestTemplate来进行远程API访问。

1
2
3
4
5
6
java复制代码@GlobalTransactional
public void purchase(String userId, String commodityCode, int orderCount) {
LOGGER.info("purchase begin ... xid: " + RootContext.getXID());
storageClient.deduct(commodityCode, orderCount);
orderClient.create(userId, commodityCode, orderCount);
}

然后是StorageService的实现,这里只是简单地操作数据库,扣减库存。我们注意到Provider的实现中可以不使用@GlobalTransactional注解。

1
2
3
4
5
6
java复制代码public void deduct(String commodityCode, int count) {
//select + for update
Storage storage = storageMapper.findByCommodityCode(commodityCode);
storage.setCount(storage.getCount() - count);
storageMapper.updateById(storage);
}

接下来是OrderService的实现,将创建的订单保存到数据库之后,调用了账户服务。

1
2
3
4
5
6
7
8
9
10
11
12
13
java复制代码public void create(String userId, String commodityCode, Integer count) {
BigDecimal orderMoney = new BigDecimal(count).multiply(new BigDecimal(5));
Order order = new Order();
order.setUserId(userId);
order.setCommodityCode(commodityCode);
order.setCount(count);
order.setMoney(orderMoney);

orderMapper.insert(order);

accountClient.debit(userId, orderMoney);

}

最后是AccountService的实现,这里扣减了账户余额,另外判断用户ID,如果是ERROR_USER_ID(1002)则抛出异常,用于模拟回滚场景。

1
2
3
4
5
6
7
8
9
java复制代码public void debit(String userId, BigDecimal num) {
Account account = accountMapper.selectByUserId(userId);
account.setMoney(account.getMoney().subtract(num));
accountMapper.updateById(account);

if (ERROR_USER_ID.equals(userId)) {
throw new RuntimeException("account branch exception");
}
}

需要注意的是,在Common模块中,实现了一个RestTemplate拦截器和一个过滤器。拦截器用于在跨服务调用时,在请求Header中添加事务ID。而拦截器则是在收到请求后,获取Header中的事务ID并绑定到本地。

1
2
3
4
5
6
7
8
9
10
11
12
java复制代码public class SeataRestTemplateInterceptor implements ClientHttpRequestInterceptor {

public ClientHttpResponse intercept(HttpRequest httpRequest, byte[] bytes, ClientHttpRequestExecution clientHttpRequestExecution) throws IOException {
HttpRequestWrapper requestWrapper = new HttpRequestWrapper(httpRequest);
String xid = RootContext.getXID();
if (StringUtils.isNotEmpty(xid)) {
requestWrapper.getHeaders().add(RootContext.KEY_XID, xid);
}

return clientHttpRequestExecution.execute(requestWrapper, bytes);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
java复制代码@Component
public class SeataFilter implements Filter {

@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) servletRequest;
String xid = req.getHeader(RootContext.KEY_XID.toLowerCase());
boolean isBind = false;
if (StringUtils.isNotBlank(xid)) {
RootContext.bind(xid);
isBind = true;
}
try {
filterChain.doFilter(servletRequest, servletResponse);
} finally {
if (isBind) {
RootContext.unbind();
}
}
}

}

数据库的定义在local-seata-env/initdb.d/all_in_one.sql中,实例相关代码已经传到Github上,有兴趣的同学可以参考这里:
github.com/ningmengwan…

使用 AT 模式需要的注意事项有哪些 ?

  1. 必须使用代理数据源,有 3 种形式可以代理数据源:
  • 依赖 seata-spring-boot-starter 时,自动代理数据源,无需额外处理。
  • 依赖 seata-all 时,使用 @EnableAutoDataSourceProxy (since 1.1.0) 注解,注解参数可选择 jdk 代理或者 cglib 代理。
  • 依赖 seata-all 时,也可以手动使用 DatasourceProxy 来包装 DataSource。
  1. 配置 GlobalTransactionScanner,使用 seata-all 时需要手动配置,使用 seata-spring-boot-starter 时无需额外处理。
  2. 业务表中必须包含单列主键,若存在复合主键,暂时只支持mysql,其他类型数据库建议先建一列自增id主键,原复合主键改为唯一键来规避下。
  3. 每个业务库中必须包含 undo_log 表,若与分库分表组件联用,分库不分表。
  4. 跨微服务链路的事务需要对相应 RPC 框架支持,目前 seata-all 中已经支持:Apache Dubbo、Alibaba Dubbo、sofa-RPC、Motan、gRpc、httpClient,对于 Spring Cloud 的支持,请大家引用 spring-cloud-alibaba-seata。其他自研框架、异步模型、消息消费事务模型请结合 API 自行支持。
  5. 目前AT模式支持的数据库有:MySQL、Oracle、PostgreSQL和 TiDB。
  6. 使用注解开启分布式事务时,若默认服务 provider 端加入 consumer 端的事务,provider 可不标注注解。但是,provider 同样需要相应的依赖和配置,仅可省略注解。
  7. 使用注解开启分布式事务时,若要求事务回滚,必须将异常抛出到事务的发起方,被事务发起方的 @GlobalTransactional 注解感知到。provide 直接抛出异常 或 定义错误码由 consumer 判断再抛出异常。

AT 模式和 Spring @Transactional 注解连用时需要注意什么 ?

@Transactional 可与 DataSourceTransactionManager 和 JTATransactionManager 连用分别表示本地事务和XA分布式事务,大家常用的是与本地事务结合。当与本地事务结合时,@Transactional和@GlobalTransaction连用,@Transactional 只能位于标注在@GlobalTransaction的同一方法层次或者位于@GlobalTransaction 标注方法的内层。这里分布式事务的概念要大于本地事务,若将 @Transactional 标注在外层会导致分布式事务空提交,当@Transactional 对应的 connection 提交时会报全局事务正在提交或者全局事务的xid不存在。

其他解决方案

我司在采用SEATA之前,也调研过基于Uber Cadence和Kafka实现的SAGA解决方案。Cadence是一个工作流平台,号称可以让你专注于的业务逻辑,把分布式系统的复杂性交给Cadence来处理。Cadence很强大,但是学习成本和定制成本相对较高,不像SEATA这般开箱即用。有兴趣的同学可以了解一下。

另外SEATA的SAGA模式也是一个不错的选择,有兴趣的同学可以看看这篇文章,作者是屹远(陈龙),蚂蚁金服分布式事务核心研发,Seata Committer。

下一篇[微服务的绊脚石–分布式事务] Seata-AT模式深入分析中,我们会针对在使用Seata过程中遇到的各种问题,结合当前最新的版本Seata 1.4.2的代码实现,跟大家一起深入了解一下Seata。

本文转载自: 掘金

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

阿里云搭建halo博客 阿里云搭建halo博客

发表于 2021-10-20

阿里云搭建halo博客

基础环境

  • Ubuntu20.04
  • 宝塔面板7.7.0
  • Nginx1.17.0(由于没有域名,安装1.18报错,所有安装较低版本)
  • MySQL5.6.50
  • PHP7.4
  • Docker20.10.8
  • Docker Compose1.29.2

购买服务器

去阿里云官网按自己的需求买对应需要的服务器

购买后操作

1
2
3
sql复制代码 apt update
apt upgrade
apt update可以查看一下

Ubuntu开启BBR加速

1
2
3
4
5
6
7
8
9
10
11
12
bash复制代码 echo net.core.default_qdisc=fq >> /etc/sysctl.conf
echo net.ipv4.tcp_congestion_control=bbr >> /etc/sysctl.conf
保存生效:
sysctl -p
执行:
sysctl net.ipv4.tcp_available_congestion_control
查看一下
lsmod | grep bbr
tcp_bbr               20480 1
lsmod | grep fq
sch_fq                 20480 1
sch_fq_codel           20480 1

安装docker和halo

通过docker安装halo,参考连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
bash复制代码 创建工作目录
mkdir ~/.halo && cd ~/.halo
  下载示例配置文件到工作目录
  wget https://dl.halo.run/config/application-template.yaml -O ./application.yaml
  安装dockerio
  apt install docker.io
  拉取最新的 Halo 镜像
  docker pull halohub/halo:1.4.12
  创建容器
  docker run -it -d --name halo -p 8090:8090 -v ~/.halo:/root/.halo --restart=unless-stopped halohub/halo:1.4.12
  -it: 开启输入功能并连接伪终端
  -d: 后台运行容器
  --name: 为容器指定一个名称
  -p: 端口映射,格式为 主机(宿主)端口:容器端口 ,可在 application.yaml 配置。
  -v: 工作目录映射。形式为:-v 宿主机路径:/root/.halo,后者不能修改。
  --restart: 建议设置为 unless-stopped,在 Docker 启动的时候自动启动 Halo 容器。

安装宝塔

1
2
3
4
5
ruby复制代码  安装宝塔
root@iZwz95g7tyywrpl59pad1aZ:~# wget -O install.sh http://download.bt.cn/install/install-ubuntu_6.0.sh && sudo bash install.sh
去除宝塔登录框
root@iZwz95g7tyywrpl59pad1aZ:~# sudo nano /etc/sysctl.conf
root@iZwz95g7tyywrpl59pad1aZ:~# wget -O install.sh http://download.bt.cn/install/install-ubuntu_6.0.sh && sudo bash install.sh

安装软件

5B6IaD.png

注意:没有域名,所以安装Nginx的版本较老,安装1.17版本就行

5Bg8pj.png

5BgNn0.png

反向代理

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
ini复制代码在宝塔网站面板进入设置,打开配置文件
将这些文件注释
# location ~ .*.(gif|jpg|jpeg|png|bmp|swf)$
  # {
  #   expires     30d;
  #   error_log /dev/null;
  #     access_log /dev/null;
  #\}
   
  # location ~ .*.(js|css)?$
  # {
    #   expires     12h;
    #   error_log /dev/null;
    #   access_log /dev/null;
  # }
在后面添加这些东西
location / {
proxy_pass http://127.0.0.1:8090/;
rewrite ^/(.*)$ /$1 break;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade-Insecure-Requests 1;
proxy_set_header X-Forwarded-Proto https;
}
​

提交就可以访问了,注意开启80端口
完成以后刷新进入页面,按照提示完成就搭建好了

5BgwAU.png

\

本文转载自: 掘金

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

Spring Cloud OpenFeign基本使用 Spr

发表于 2021-10-20

Spring Cloud OpenFeign基本使用

该项目通过自动配置和绑定到 Spring Environment 和其他 Spring 编程模型,为 Spring Boot 应用程序提供 OpenFeign 集成。

1、声明式 REST 客户端:Feign

Feign 是一个声明式 Web 服务客户端。它使编写 Web 服务客户端变得更容易。要使用 Feign 创建一个接口并对其进行注释。它具有可插入的注释支持,包括 Feign 注释和 JAX-RS 注释。 Feign 还支持可插拔的编码器和解码器。 Spring Cloud 添加了对 Spring MVC 注释的支持,并支持使用 Spring Web 中默认使用的相同 HttpMessageConverters。 Spring Cloud 集成了 Eureka、Spring Cloud CircuitBreaker 和 Spring Cloud LoadBalancer,在使用 Feign 时提供负载均衡的 http 客户端。

1.1、如何集成 Feign

要将 Feign 包含在您的项目中,请使用带有group org.springframework.cloud 和artifact ID spring-cloud-starter-openfeign 的 starter。有关使用当前 Spring Cloud Release Train 设置构建系统的详细信息,请参阅 Spring Cloud 项目页面。

示例:

1
2
3
4
5
6
7
8
9
java复制代码@SpringBootApplication
@EnableFeignClients
public class Application {

public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}

}

StoreClient.java

1
2
3
4
5
6
7
8
9
10
11
java复制代码@FeignClient("stores")
public interface StoreClient {
@RequestMapping(method = RequestMethod.GET, value = "/stores")
List<Store> getStores();

@RequestMapping(method = RequestMethod.GET, value = "/stores")
Page<Store> getStores(Pageable pageable);

@RequestMapping(method = RequestMethod.POST, value = "/stores/{storeId}", consumes = "application/json")
Store update(@PathVariable("storeId") Long storeId, Store store);
}

在@FeignClient 注释中,String 值(上面的“stores”)是任意客户端名称,用于创建 Spring Cloud LoadBalancer 客户端。您还可以使用 url 属性(绝对值或仅主机名)指定 URL。应用程序上下文中 bean 的名称是接口的完全限定名称。要指定您自己的别名值,您可以使用 @FeignClient 注释的限定符值。

上面的负载平衡器客户端将想要发现“stores”服务的物理地址。如果您的应用程序是 Eureka 客户端,那么它将解析 Eureka 服务注册表中的服务。如果不想使用 Eureka,可以使用 SimpleDiscoveryClient 在外部配置中配置服务器列表。

要在 @Configuration-annotated-classes 上使用 @EnableFeignClients 注释,请确保指定客户端所在的位置,例如:@EnableFeignClients(basePackages = “com.example.clients”) 或明确列出它们:@EnableFeignClients(clients = InventoryServiceFeignClient .class)

1.2.覆盖 Feign 默认值

Spring Cloud 的 Feign 支持的一个核心概念是命名客户端。每个 feign 客户端都是一个组件集合的一部分,这些组件一起工作以根据需要联系远程服务器,并且集合有一个名称,您可以使用 @FeignClient 注释作为应用程序开发人员为其指定。 Spring Cloud 使用 FeignClientsConfiguration 为每个命名的客户端按需创建一个新的集成作为 ApplicationContext。这包含(除其他外)一个 feign.Decoder、一个 feign.Encoder 和一个 feign.Contract。可以使用 @FeignClient 注释的 contextId 属性来覆盖该集合的名称。

Spring Cloud 允许您通过使用 @FeignClient 声明额外的配置(在 FeignClientsConfiguration 之上)来完全控制 feign 客户端。例子:

1
2
3
4
java复制代码@FeignClient(name = "stores", configuration = FooConfiguration.class)
public interface StoreClient {
//..
}

在这种情况下,客户端由 FeignClientsConfiguration 中已有的组件和 FooConfiguration 中的任何组件组成(后者将覆盖前者)。

FooConfiguration 不需要用@Configuration 注释。但是,如果是,那么请注意将其从任何包含此配置的@ComponentScan 中排除,因为在指定时它将成为 feign.Decoder、feign.Encoder、feign.Contract 等的默认源。这可以通过将它放在与任何 @ComponentScan 或 @SpringBootApplication 分开的、不重叠的包中来避免,或者可以在 @ComponentScan 中明确排除它。

除了更改 ApplicationContext 集合的名称之外,使用 @FeignClient 注释的 contextId 属性,它将覆盖客户端名称的别名,并将用作为该客户端创建的配置 bean 名称的一部分。

name 和 url 属性支持占位符。

1
2
3
4
java复制代码@FeignClient(name = "${feign.name}", url = "${feign.url}")
public interface StoreClient {
//..
}

Spring Cloud OpenFeign 默认为 feign 提供了以下 bean(BeanType beanName: ClassName):

  • DecoderfeignDecoder: ResponseEntityDecoder( 包装了一个SpringDecoder)
  • Encoder feignEncoder: SpringEncoder
  • Logger feignLogger: Slf4jLogger
  • MicrometerCapabilitymicrometerCapability:如果feign-micrometer在类路径上并且MeterRegistry可用
  • Contract feignContract: SpringMvcContract
  • Feign.Builder feignBuilder: FeignCircuitBreaker.Builder
  • ClientfeignClient:如果 Spring Cloud LoadBalancer 在类路径上,FeignBlockingLoadBalancerClient则使用。如果它们都不在类路径上,则使用默认的 feign 客户端。

spring-cloud-starter-openfeign 支持 spring-cloud-starter-loadbalancer。但是,作为一个可选的依赖项,如果您想使用它,您需要确保将其添加到您的项目中。

OkHttpClient 和 ApacheHttpClient 以及 ApacheHC5 feign 客户端可以通过将 feign.okhttp.enabled 或 feign.httpclient.enabled 或 feign.httpclient.hc5.enabled 分别设置为 true 并将它们放在类路径上来使用。您可以通过在使用 Apache 时提供 org.apache.http.impl.client.CloseableHttpClient 或 okhttp3.OkHttpClient 在使用 OK HTTP 或 org.apache.hc.client5.http.impl.classic 时提供 bean 来自定义使用的 HTTP 客户端。使用 Apache HC5 时的 CloseableHttpClient。

Spring Cloud OpenFeign 默认没有为 feign 提供以下 bean,但仍然会从应用程序上下文中查找这些类型的 bean 来创建 feign 客户端:

  • Logger.Level
  • Retryer
  • ErrorDecoder
  • Request.Options
  • Collection<RequestInterceptor>
  • SetterFactory
  • QueryMapEncoder
  • Capability (MicrometerCapability is provided by default)

默认情况下会创建 Retryer.NEVER_RETRY 类型为 Retryer 的 bean,这将禁用重试。请注意,这种重试行为与 Feign 默认行为不同,它会自动重试 IOExceptions,将它们视为与网络相关的瞬态异常,以及从 ErrorDecoder 抛出的任何 RetryableException。

创建其中一种类型的 bean 并将其放置在 @FeignClient 配置中(例如上面的 FooConfiguration)允许您覆盖所描述的每个 bean。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
java复制代码@Configuration
public class FooConfiguration {
@Bean
public Contract feignContract() {
return new feign.Contract.Default();
}

@Bean
public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
return new BasicAuthRequestInterceptor("user", "password");
}
}

这将 SpringMvcContract 替换为 feign.Contract.Default 并将 RequestInterceptor 添加到 RequestInterceptor 的集合中。

@FeignClient 也可以使用配置属性进行配置。

application.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
yaml复制代码feign:
client:
config:
feignName:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: full
errorDecoder: com.example.SimpleErrorDecoder
retryer: com.example.SimpleRetryer
defaultQueryParameters:
query: queryValue
defaultRequestHeaders:
header: headerValue
requestInterceptors:
- com.example.FooRequestInterceptor
- com.example.BarRequestInterceptor
decode404: false
encoder: com.example.SimpleEncoder
decoder: com.example.SimpleDecoder
contract: com.example.SimpleContract
capabilities:
- com.example.FooCapability
- com.example.BarCapability
metrics.enabled: false

可以以与上述类似的方式在 @EnableFeignClients 属性 defaultConfiguration 中指定默认配置。不同之处在于此配置将适用于所有 feign 客户端。

如果您更喜欢使用配置属性来配置所有 @FeignClient,您可以使用默认的 feign 名称创建配置属性。

您可以使用 feign.client.config.feignName.defaultQueryParameters 和 feign.client.config.feignName.defaultRequestHeaders 来指定将与名为 feignName 的客户端的每个请求一起发送的查询参数和标头。

application.yml

1
2
3
4
5
6
7
yaml复制代码feign:
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: basic

如果我们同时创建@Configuration bean 和配置属性,配置属性将获胜。它将覆盖@Configuration 值。但是如果你想把优先级改成@Configuration,你可以把feign.client.default-to-properties改成false。

如果我们想创建多个具有相同名称或 url 的 feign 客户端,以便它们指向同一服务器但每个具有不同的自定义配置,那么我们必须使用 @FeignClient 的 contextId 属性以避免这些配置的名称冲突Bean。

1
2
3
4
java复制代码@FeignClient(contextId = "fooClient", name = "stores", configuration = FooConfiguration.class)
public interface FooClient {
//..
}
1
2
3
4
java复制代码@FeignClient(contextId = "barClient", name = "stores", configuration = BarConfiguration.class)
public interface BarClient {
//..
}

也可以将 FeignClient 配置为不从父上下文继承 bean。您可以通过覆盖 FeignClientConfigurer bean 中的 inheritParentConfiguration() 以返回 false 来实现此目的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码@Configuration
public class CustomConfiguration{

@Bean
public FeignClientConfigurer feignClientConfigurer() {
return new FeignClientConfigurer() {

@Override
public boolean inheritParentConfiguration() {
return false;
}
};

}
}

默认情况下,Feign 客户端不编码斜杠/字符。您可以通过将 feign.client.decodeSlash 的值设置为 false 来更改此行为。

1.2.1. SpringEncoder 配置

在我们提供的 SpringEncoder 中,我们为二进制内容类型设置空字符集,为所有其他类型设置 UTF-8。

您可以修改此行为以通过将 feign.encoder.charset-from-content-type 的值设置为 true 来从 Content-Type 标头字符集派生字符集。

1.3.超时处理

我们可以在默认客户端和命名客户端上配置超时。 OpenFeign 使用两个超时参数:

  • connectTimeout 防止由于服务器处理时间长而阻塞调用者。
  • readTimeout 从连接建立时开始应用,在返回响应时间过长时触发。

如果服务器未运行或不可用,则数据包会导致连接被拒绝。通信以错误消息或回退结束。如果它设置得非常低,这可能会在 connectTimeout 之前发生。执行查找和接收此类数据包所花费的时间会导致此延迟的很大一部分。它可能会根据涉及 DNS 查找的远程主机进行更改。

1.4.手动创建 Feign 客户端

在某些情况下,可能需要以使用上述方法无法实现的方式自定义您的 Feign Client。在这种情况下,您可以使用 Feign Builder API 创建客户端。下面是一个示例,它创建了两个具有相同接口的 Feign 客户端,但为每个客户端配置了一个单独的请求拦截器。

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
java复制代码@Import(FeignClientsConfiguration.class)
class FooController {

private FooClient fooClient;

private FooClient adminClient;

@Autowired
public FooController(Client client, Encoder encoder, Decoder decoder, Contract contract, MicrometerCapability micrometerCapability) {
this.fooClient = Feign.builder().client(client)
.encoder(encoder)
.decoder(decoder)
.contract(contract)
.addCapability(micrometerCapability)
.requestInterceptor(new BasicAuthRequestInterceptor("user", "user"))
.target(FooClient.class, "https://PROD-SVC");

this.adminClient = Feign.builder().client(client)
.encoder(encoder)
.decoder(decoder)
.contract(contract)
.addCapability(micrometerCapability)
.requestInterceptor(new BasicAuthRequestInterceptor("admin", "admin"))
.target(FooClient.class, "https://PROD-SVC");
}
}

上面例子中的 FeignClientsConfiguration.class 是 Spring Cloud OpenFeign 提供的默认配置。

PROD-SVC 是客户端将向其发出请求的服务的名称。

Feign Contract 对象定义了接口上哪些注解和值是有效的。自动装配的 Contract bean 提供对 SpringMVC 注释的支持,而不是默认的 Feign 原生注释。

您还可以使用 Builder将 FeignClient 配置为不从父上下文继承 bean。您可以通过在 Builder 上覆盖调用inheritParentContext(false) 来做到这一点。

1.5. Feign Spring Cloud 断路器支持

如果 Spring Cloud CircuitBreaker 在类路径上并且 feign.circuitbreaker.enabled=true,Feign 将使用断路器包装所有方法。 要在每个客户端的基础上禁用 Spring Cloud CircuitBreaker 支持,请创建一个具有“prototype”范围的 vanilla Feign.Builder,例如:

1
2
3
4
5
6
7
8
java复制代码@Configuration
public class FooConfiguration {
@Bean
@Scope("prototype")
public Feign.Builder feignBuilder() {
return Feign.builder();
}
}

断路器名称遵循此模式 #()。当调用带有 FooClient 接口的 @FeignClient 并且被调用的没有参数的接口方法是 bar 时,断路器名称将是 FooClient#bar()。

从 2020.0.2 开始,断路器名称模式已从 _ 更改。使用 2020.0.4 中引入的 CircuitBreakerNameResolver,断路器名称可以保留旧模式。

提供 CircuitBreakerNameResolver 的 bean,您可以更改断路器名称模式。

1
2
3
4
5
6
7
java复制代码@Configuration
public class FooConfiguration {
@Bean
public CircuitBreakerNameResolver circuitBreakerNameResolver() {
return (String feignClientName, Target<?> target, Method method) -> feignClientName + "_" + method.getName();
}
}

要启用 Spring Cloud CircuitBreaker 组,请将 feign.circuitbreaker.group.enabled 属性设置为 true(默认为 false)。

1.6. Feign Spring Cloud 断路器Fallbacks

Spring Cloud CircuitBreaker 支持fallback的概念:当电路打开或出现错误时执行的默认代码路径。要为给定的 @FeignClient 启用回退,请将回退属性设置为实现回退的类名。您还需要将您的实现声明为 Spring bean。

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
java复制代码@FeignClient(name = "test", url = "http://localhost:${server.port}/", fallback = Fallback.class)
protected interface TestClient {

@RequestMapping(method = RequestMethod.GET, value = "/hello")
Hello getHello();

@RequestMapping(method = RequestMethod.GET, value = "/hellonotfound")
String getException();

}

@Component
static class Fallback implements TestClient {

@Override
public Hello getHello() {
throw new NoFallbackAvailableException("Boom!", new RuntimeException());
}

@Override
public String getException() {
return "Fixed response";
}

}

If one needs access to the cause that made the fallback trigger, one can use the fallbackFactory attribute inside @FeignClient.

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
java复制代码@FeignClient(name = "testClientWithFactory", url = "http://localhost:${server.port}/",
fallbackFactory = TestFallbackFactory.class)
protected interface TestClientWithFactory {

@RequestMapping(method = RequestMethod.GET, value = "/hello")
Hello getHello();

@RequestMapping(method = RequestMethod.GET, value = "/hellonotfound")
String getException();

}

@Component
static class TestFallbackFactory implements FallbackFactory<FallbackWithFactory> {

@Override
public FallbackWithFactory create(Throwable cause) {
return new FallbackWithFactory();
}

}

static class FallbackWithFactory implements TestClientWithFactory {

@Override
public Hello getHello() {
throw new NoFallbackAvailableException("Boom!", new RuntimeException());
}

@Override
public String getException() {
return "Fixed response";
}

}

1.7. Feign 和@Primary

将 Feign 与 Spring Cloud CircuitBreaker fallback一起使用时,ApplicationContext 中有多个相同类型的 bean。这将导致 @Autowired 无法工作,因为没有一个 bean,或者一个标记为主要的 bean。为了解决这个问题,Spring Cloud OpenFeign 将所有 Feign 实例标记为 @Primary,因此 Spring Framework 将知道要注入哪个 bean。在某些情况下,这可能是不可取的。要关闭此行为,请将 @FeignClient 的主要属性设置为 false。

1
2
3
4
java复制代码@FeignClient(name = "hello", primary = false)
public interface HelloClient {
// methods here
}

1.8. Feign 继承支持

Feign 通过单继承接口支持样板 API。这允许将常见操作分组到方便的基本接口中。

UserService.java

1
2
3
4
5
java复制代码public interface UserService {

@RequestMapping(method = RequestMethod.GET, value ="/users/{id}")
User getUser(@PathVariable("id") long id);
}

UserResource.java

1
2
3
4
java复制代码@RestController
public class UserResource implements UserService {

}

UserClient.java

1
2
3
4
5
6
java复制代码package project.user;

@FeignClient("users")
public interface UserClient extends UserService {

}

通常不建议在服务器和客户端之间共享一个接口。它引入了紧耦合,也不是所有维护的 Spring MVC 版本都支持(某些版本没有继承方法参数映射)。

1.9. Feign 请求/响应压缩

您可以考虑为您的 Feign 请求启用请求或响应 GZIP 压缩。您可以通过启用以下属性之一来执行此操作:

1
2
java复制代码feign.compression.request.enabled=true
feign.compression.response.enabled=true

Feign 请求压缩为您提供类似于您为 Web 服务器设置的设置:

1
2
3
java复制代码feign.compression.request.enabled=true
feign.compression.request.mime-types=text/xml,application/xml,application/json
feign.compression.request.min-request-size=2048

这些属性允许您选择压缩媒体类型和最小请求阈值长度。

对于除了 OkHttpClient 之外的 http 客户端,可以启用默认的 gzip 解码器来解码 UTF-8 编码的 gzip 响应:

1
2
java复制代码feign.compression.response.enabled=true
feign.compression.response.useGzipDecoder=true

1.10.Feign logging

为每个创建的 Feign 客户端创建一个记录器。默认情况下,记录器的名称是用于创建 Feign 客户端的接口的完整类名。 Feign logging 只响应 DEBUG 级别。

application.yml

1
yaml复制代码logging.level.project.user.UserClient: DEBUG

您可以为每个客户端配置的 Logger.Level 对象告诉 Feign 要记录多少。选择是:

  • NONE, 无日志记录(默认)。
  • BASIC, 仅记录请求方法和 URL 以及响应状态代码和执行时间。
  • HEADERS, 记录基本信息以及请求和响应标头。
  • FULL, 记录请求和响应的标头、正文和元数据。

例如,以下内容会将 Logger.Level 设置为 FULL:

1
2
3
4
5
6
7
java复制代码@Configuration
public class FooConfiguration {
@Bean
Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}

1.11. Feign Capability支持

Feign 功能公开了核心 Feign 组件,以便可以修改这些组件。例如,功能可以获取客户端,对其进行装饰,并将装饰后的实例返回给 Feign。对指标库的支持是一个很好的现实例子。请参阅 Feign 指标。

创建一个或多个 Capability bean 并将它们放置在 @FeignClient 配置中,让您可以注册它们并修改相关客户端的行为。

1
2
3
4
5
6
7
java复制代码@Configuration
public class FooConfiguration {
@Bean
Capability customCapability() {
return new CustomCapability();
}
}

1.12. Feign 指标

如果以下所有条件都为真,则会创建并注册 MicrometerCapability bean,以便您的 Feign 客户端将指标发布到 Micrometer:

  • feign-micrometer 在classpath上
  • A MeterRegistry bean可用
  • feign 指标属性设置为 true(默认情况下)
    • feign.metrics.enabled=true (作用所有客户端)
    • feign.client.config.feignName.metrics.enabled=true (作用单个客户端)

如果您的应用程序已经使用 Micrometer,那么启用指标就像将 feign-micrometer 放到您的类路径中一样简单。

您还可以通过以下任一方式禁用该功能:

  • 从您的类路径中排除 feign-micrometer
  • 将 feign 指标属性之一设置为 false
    • feign.metrics.enabled=false
    • feign.client.config.feignName.metrics.enabled=false

feign.metrics.enabled=false 禁用对所有 Feign 客户端的度量支持,而不管客户端级别标志的值:feign.client.config.feignName.metrics.enabled。如果要为每个客户端启用或禁用 merics,请不要设置 feign.metrics.enabled 并使用 feign.client.config.feignName.metrics.enabled。

您还可以通过注册自己的 bean 来自定义 MicrometerCapability:

1
2
3
4
5
6
7
java复制代码@Configuration
public class FooConfiguration {
@Bean
public MicrometerCapability micrometerCapability(MeterRegistry meterRegistry) {
return new MicrometerCapability(meterRegistry);
}
}

1.13. Feign @QueryMap 支持

OpenFeign @QueryMap 注释支持将 POJO 用作 GET 参数映射。不幸的是,默认的 OpenFeign QueryMap 注释与 Spring 不兼容,因为它缺少 value 属性。

Spring Cloud OpenFeign 提供了一个等效的 @SpringQueryMap 注解,用于将 POJO 或 Map 参数注解为查询参数映射。

例如:

Params 类定义了参数 param1 和 param2:

1
2
3
4
5
6
7
java复制代码// Params.java
public class Params {
private String param1;
private String param2;

// [Getters and setters omitted for brevity]
}

以下 feign 客户端通过使用 @SpringQueryMap 注解来使用 Params 类:

1
2
3
4
5
6
java复制代码@FeignClient("demo")
public interface DemoTemplate {

@GetMapping(path = "/demo")
String demoEndpoint(@SpringQueryMap Params params);
}

如果您需要对生成的查询参数映射进行更多控制,则可以实现自定义 QueryMapEncoder bean。

1.14. HATEOAS 支持

Spring 提供了一些 API 来创建遵循 HATEOAS 原则、Spring Hateoas 和 Spring Data REST 的 REST 表示。

如果您的项目使用 org.springframework.boot:spring-boot-starter-hateoas starter 或 org.springframework.boot:spring-boot-starter-data-rest starter,则默认启用 Feign HATEOAS 支持。

启用 HATEOAS 支持后,允许 Feign 客户端序列化和反序列化 HATEOAS 表示模型:EntityModel、CollectionModel 和 PagedModel。

1
2
3
4
5
6
java复制代码@FeignClient("demo")
public interface DemoTemplate {

@GetMapping(path = "/stores")
CollectionModel<Store> getStores();
}

1.15. Spring @MatrixVariable 支持

Spring Cloud OpenFeign 提供对 Spring @MatrixVariable 注解的支持。

如果映射作为方法参数传递,@MatrixVariable 路径段是通过使用 = 连接映射中的键值对来创建的。

如果传递了不同的对象,则@MatrixVariable 批注(如果已定义)中提供的名称或带批注的变量名称使用 = 与提供的方法参数连接。

即使在服务器端,Spring 不要求用户将路径段占位符命名为与matrix variable名称相同的名称,因为它在客户端过于模糊,Spring Cloud OpenFeign 要求您添加一个路径段占位符与@MatrixVariable 注释(如果已定义)中提供的名称或带注释的变量名称匹配的名称。

例如:

1
2
java复制代码@GetMapping("/objects/links/{matrixVars}")
Map<String, List<String>> getObjects(@MatrixVariable Map<String, List<String>> matrixVars);

请注意,变量名称和路径段占位符都称为 matrixVars。

1
2
3
4
5
6
java复制代码@FeignClient("demo")
public interface DemoTemplate {

@GetMapping(path = "/stores")
CollectionModel<Store> getStores();
}

1.16. Feign CollectionFormat 支持

我们通过提供 @CollectionFormat 注释来支持 feign.CollectionFormat。您可以通过传递所需的 feign.CollectionFormat 作为注释值来注释 Feign 客户端方法。

在以下示例中,使用 CSV 格式而不是默认的 EXPLODED 来处理方法。

1
2
3
4
5
6
7
8
java复制代码@FeignClient(name = "demo")
protected interface PageableFeignClient {

@CollectionFormat(feign.CollectionFormat.CSV)
@GetMapping(path = "/page")
ResponseEntity performRequest(Pageable page);

}

在发送 Pageable 作为查询参数时设置 CSV 格式,以便正确编码。

1.17.Reactive支持

由于 OpenFeign 项目目前不支持响应式客户端,例如 Spring WebClient,Spring Cloud OpenFeign 也不支持。我们将在核心项目中尽快添加对它的支持。

在完成之前,我们建议使用 feign-reactive 来支持 Spring WebClient。

1.17.1.早期初始化错误

根据您使用 Feign 客户端的方式,您可能会在启动应用程序时看到初始化错误。要解决此问题,您可以在自动装配客户端时使用 ObjectProvider。

1
2
java复制代码@Autowired
ObjectProvider<TestFeginClient> testFeginClient;

1.18.Spring Data支持

您可以考虑启用 Jackson Modules 以支持 org.springframework.data.domain.Page 和 org.springframework.data.domain.Sort 解码。

1
java复制代码feign.autoconfiguration.jackson.enabled=true

1.19. Spring @RefreshScope 支持

如果启用了 Feign 客户端刷新,则使用 feign.Request.Options 作为刷新范围的 bean 创建每个 feign 客户端。这意味着可以通过 POST /actuator/refresh 针对任何 Feign 客户端实例刷新诸如 connectTimeout 和 readTimeout 之类的属性。

默认情况下,Feign 客户端中的刷新行为是禁用的。使用以下属性启用刷新行为:

1
java复制代码feign.client.refresh-enabled=true

不要用@RefreshScope 注释来注释@FeignClient 接口。

2、配置属性

可以在 application.properties 文件、application.yml 文件或命令行开关中指定各种属性。本附录提供了常见 Spring Cloud OpenFeign 属性的列表以及对使用它们的底层类的引用。

属性贡献可以来自类路径上的其他 jar 文件,因此您不应认为这是一个详尽的列表。此外,您可以定义自己的属性。

名称 默认值 描述
feign.autoconfiguration.jackson.enabled false 如果为 true,将为 Jackson 页面解码提供 PageJacksonModule 和 SortJacksonModule bean。
feign.circuitbreaker.enabled false 如果为 true,则 OpenFeign 客户端将使用 Spring Cloud CircuitBreaker 断路器包装。
feign.circuitbreaker.group.enabled false 如果为 true,则 OpenFeign 客户端将使用带有组的 Spring Cloud CircuitBreaker 断路器包装。
feign.client.config
feign.client.decode-slash true Feign 客户端默认不编码斜杠/字符。要更改此行为,请将 decodeSlash 设置为 false。
feign.client.default-config default
feign.client.default-to-properties true
feign.client.refresh-enabled false 为 Feign 启用选项值刷新功能。
feign.compression.request.enabled false 使 Feign 发送的请求能够被压缩。
feign.compression.request.mime-types [text/xml, application/xml, application/json] 支持的 MIME 类型列表。
feign.compression.request.min-request-size 2048 最小阈值内容大小。
feign.compression.response.enabled false 使来自 Feign 的响应能够被压缩。
feign.compression.response.useGzipDecoder false 启用要使用的默认 gzip 解码器。
feign.encoder.charset-from-content-type false 指示字符集是否应从 {@code Content-Type} 标头派生。
feign.httpclient.connection-timeout 2000
feign.httpclient.connection-timer-repeat 3000
feign.httpclient.disable-ssl-validation false
feign.httpclient.enabled true 允许 Feign 使用 Apache HTTP 客户端。
feign.httpclient.follow-redirects true
feign.httpclient.hc5.enabled false 允许 Feign 使用 Apache HTTP Client 5。
feign.httpclient.hc5.pool-concurrency-policy 池并发策略。
feign.httpclient.hc5.pool-reuse-policy 池连接重用策略。
feign.httpclient.hc5.socket-timeout 5 Socket超时的默认值。
feign.httpclient.hc5.socket-timeout-unit Socket超时单位的默认值。
feign.httpclient.max-connections 200
feign.httpclient.max-connections-per-route 50
feign.httpclient.time-to-live 900
feign.httpclient.time-to-live-unit
feign.metrics.enabled true 为 Feign 启用指标功能。
feign.okhttp.enabled false 允许 Feign 使用 OK HTTP Client。

image.png
两年前,机翻的,spring官网的文档,跟spring中文网有什么关系啊,同志,只许州官放火不许百姓点灯吗

本文转载自: 掘金

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

sqlite的安装和简单使用 SQLite 安装 SQLit

发表于 2021-10-20

SQLite 安装

SQLite 的一个重要的特性是零配置的,这意味着不需要复杂的安装或管理。本章将讲解 Windows、Linux 和 Mac OS X 上的安装设置。

在 Windows 上安装 SQLite

  • 请访问 SQLite 下载页面,从 Windows 区下载预编译的二进制文件。
  • 您需要下载 sqlite-tools-win32-*.zip 和 sqlite-dll-win32-*.zip 压缩文件。
  • 创建文件夹 C:\sqlite,并在此文件夹下解压上面两个压缩文件,将得到 sqlite3.def、sqlite3.dll 和 sqlite3.exe 文件。
  • 添加 C:\sqlite 到 PATH 环境变量,最后在命令提示符下,使用 sqlite3 命令,将显示如下结果。

image.png

image.png

1
2
3
4
5
sql复制代码C:>sqlite3
SQLite version 3.7.15.2 2013-01-09 11:53:05
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite>

SQLite 创建数据库

SQLite 的 sqlite3 命令被用来创建新的 SQLite 数据库。您不需要任何特殊的权限即可创建一个数据。

语法

sqlite3 命令的基本语法如下:

1
sql复制代码$ sqlite3 DatabaseName.db

通常情况下,数据库名称在 RDBMS 内应该是唯一的。

另外我们也可以使用 .open 来建立新的数据库文件:

这条是验证过的,我们可以进入到db的数据库内部,执行.open 创建数据库

1
sql复制代码sqlite>.open test.db

上面的命令创建了数据库文件 test.db,位于 sqlite3 命令同一目录下。

打开已存在数据库也是用 .open 命令,以上命令如果 test.db 存在则直接会打开,不存在就创建它。
创建完成后,在文件夹中会有一个.db的数据库文件

image.png

如果您想创建一个新的数据库 <testDB.db>,SQLITE3 语句如下所示:

1
2
3
4
5
sql复制代码$ sqlite3 testDB.db
SQLite version 3.7.15.2 2013-01-09 11:53:05
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite>

上面的命令将在当前目录下创建一个文件 testDB.db。该文件将被 SQLite 引擎用作数据库。如果您已经注意到 sqlite3 命令在成功创建数据库文件之后,将提供一个 sqlite> 提示符。

一旦数据库被创建,您就可以使用 SQLite 的 .databases 命令来检查它是否在数据库列表中,如下所示:

1
2
3
4
sql复制代码sqlite>.databases
seq name file
--- --------------- ----------------------
0 main /home/sqlite/testDB.db

您可以使用 SQLite .quit 命令退出 sqlite 提示符,如下所示:

1
2
sql复制代码sqlite>.quit
$

.dump 命令

您可以在命令提示符中使用 SQLite .dump 点命令来导出完整的数据库在一个文本文件中,如下所示:

1
sql复制代码$sqlite3 testDB.db .dump > testDB.sql

上面的命令将转换整个 testDB.db 数据库的内容到 SQLite 的语句中,并将其转储到 ASCII 文本文件 testDB.sql 中。您可以通过简单的方式从生成的 testDB.sql 恢复,如下所示:

1
sql复制代码$sqlite3 testDB.db < testDB.sql

此时的数据库是空的,一旦数据库中有表和数据,您可以尝试上述两个程序。现在,让我们继续学习下一章。

本文转载自: 掘金

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

1…480481482…956

开发者博客

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