Merge remote-tracking branch 'LCTT/master'

This commit is contained in:
Xingyu Wang 2020-10-04 09:53:10 +08:00
commit d458281f9c
5 changed files with 99 additions and 87 deletions

View File

@ -1,8 +1,8 @@
[#]: collector: (lujun9972) [#]: collector: (lujun9972)
[#]: translator: (geekpi) [#]: translator: (geekpi)
[#]: reviewer: ( ) [#]: reviewer: (wxy)
[#]: publisher: ( ) [#]: publisher: (wxy)
[#]: url: ( ) [#]: url: (https://linux.cn/article-12676-1.html)
[#]: subject: (Installing and running Vagrant using qemu-kvm) [#]: subject: (Installing and running Vagrant using qemu-kvm)
[#]: via: (https://fedoramagazine.org/vagrant-qemukvm-fedora-devops-sysadmin/) [#]: via: (https://fedoramagazine.org/vagrant-qemukvm-fedora-devops-sysadmin/)
[#]: author: (Andy Mott https://fedoramagazine.org/author/amott/) [#]: author: (Andy Mott https://fedoramagazine.org/author/amott/)
@ -12,47 +12,50 @@
![][1] ![][1]
Vagrant 是一个出色的工具,DevOps 专业人员、程序员、系统管理员和普通极客来使用它来建立可复制的基础架构来进行开发和测试。来自它们的网站: Vagrant 是一个出色的工具DevOps 专业人员、程序员、系统管理员和普通极客来使用它来建立可重复的基础架构来进行开发和测试。引用自它的网站:
> Vagrant 是用于在单工作流程中构建和管理虚拟机环境的工具。凭借简单易用的工作流程和对自动化的关注Vagrant 降低了开发环境的设置时间,提高了生产效率,并使”在我的机器上工作“的借口成为过去。 > Vagrant 是用于在单工作流程中构建和管理虚拟机环境的工具。凭借简单易用的工作流程并专注于自动化Vagrant 降低了开发环境的设置时间,提高了生产效率,并使“在我的机器上可以工作”的借口成为过去。
> >
> 如果你已经熟悉 Vagrant 的基础知识,那么文档为所有的功能和内部结构提供了更好的参考。 > 如果你已经熟悉 Vagrant 的基础知识,那么文档为所有的功能和内部结构提供了更好的参考。
> >
> Vagrant 提供了易于配置、可复制、可移植的工作环境,它建立在行业标准技术之上,并由一个一的工作流程控制,帮助你和你的团队最大限度地提高生产力和灵活性。 > Vagrant 提供了基于行业标准技术构建的、易于配置、可复制、可移植的工作环境,并由一个一的工作流程控制,帮助你和你的团队最大限度地提高生产力和灵活性。
> >
> <https://www.vagrantup.com/intro> > <https://www.vagrantup.com/intro>
本指南将通过必要的步骤,让 Vagrant 在基于 Fedora 的机器上工作 本指南将逐步介绍使 Vagrant 在基于 Fedora 的计算机上工作所需的步骤
我从最小化安装 Fedora Server 开始,因为这样可以减少主机操作系统的内存占用,但如果你已经有一台可以使用的 Fedora 机器,无论是服务器还是工作站,那么也没问题。 我从最小化安装 Fedora 服务器开始,因为这样可以减少宿主机操作系统的内存占用,但如果你已经有一台可以使用的 Fedora 机器,无论是服务器还是工作站版本,那么也没问题。
### 检查机器是否支持虚拟化 ### 检查机器是否支持虚拟化
``` ```
$ sudo lscpu | grep Virtualization $ sudo lscpu | grep Virtualization
```
```
Virtualization: VT-x Virtualization: VT-x
Virtualization type: full Virtualization type: full
``` ```
### 安装 qemu-kvm ### 安装 qemu-kvm
``` ```
sudo dnf install qemu-kvm libvirt libguestfs-tools virt-install rsync sudo dnf install qemu-kvm libvirt libguestfs-tools virt-install rsync
``` ```
### 启用并启动 libvirt 守护进程 ### 启用并启动 libvirt 守护进程
``` ```
sudo systemctl enable --now libvirtd sudo systemctl enable --now libvirtd
``` ```
### 安装 Vagrant ### 安装 Vagrant
``` ```
sudo dnf install vagrant sudo dnf install vagrant
``` ```
### 安装 Vagrant libvirtd 插件 ### 安装 Vagrant libvirtd 插件
``` ```
sudo vagrant plugin install vagrant-libvirt sudo vagrant plugin install vagrant-libvirt
@ -64,48 +67,58 @@ sudo vagrant plugin install vagrant-libvirt
vagrant box add fedora/32-cloud-base --provider=libvirt vagrant box add fedora/32-cloud-base --provider=libvirt
``` ```
### 创建一个最小的 Vagrantfile 来测试: LCTT 译注以防你不知道box 是 Vagrant 中的一种包格式Vagrant 支持的任何平台上的任何人都可以使用盒子来建立相同的工作环境。)
### 创建一个最小化的 Vagrantfile 来测试
``` ```
$ mkdir vagrant-test $ mkdir vagrant-test
$ cd vagrant-test $ cd vagrant-test
$ vi VagrantfileVagrant.configure("2") do |config| $ vi Vagrantfile
```
```
Vagrant.configure("2") do |config|
config.vm.box = "fedora/32-cloud-base" config.vm.box = "fedora/32-cloud-base"
end end
``` ```
**注意文件名和文件内容的大写。** **注意文件名和文件内容的大写。**
### 检查文件 ### 检查文件
``` ```
vagrant statusCurrent machine states: vagrant status
```
```
Current machine states:
default not created (libvirt) default not created (libvirt)
The Libvirt domain is not created. Run 'vagrant up' to create it. The Libvirt domain is not created. Run 'vagrant up' to create it.
``` ```
### 启动 box ### 启动 box
``` ```
vagrant up vagrant up
``` ```
### 连接到你的新机器 ### 连接到你的新机器
``` ```
vagrant ssh vagrant ssh
``` ```
完成了。现在你的 Fedora 机器上有 Vagrant 在工作 完成了。现在你的 Fedora 机器上 Vagrant 可以工作了
要停止机器,请使用 _vagrant halt_。这只是简单地停止机器,但保留虚拟机和磁盘。 要停止该机器,请使用 `vagrant halt`。这只是简单地停止机器,但保留虚拟机和磁盘。
要关闭并删除它,请使用 _vagrant destroy_。这将删除整个机器和你在其中所做的任何更改。 要关闭并删除它,请使用 `vagrant destroy`。这将删除整个机器和你在其中所做的任何更改。
### 接下来的步骤 ### 接下来的步骤
在运行 _vagrant up_ 命令之前,你不需要下载 box。你可以直接在 Vagrantfile 中指定 box 和提供者如果还没有的话Vagrant 会下载它。下面是一个例子,它还设置了内存量和 CPU 数量: 在运行 `vagrant up` 命令之前,你不需要下载 box。你可以直接在 Vagrantfile 中指定 box 和提供者如果还没有的话Vagrant 会下载它。下面是一个例子,它还设置了内存量和 CPU 数量:
``` ```
# -*- mode: ruby -*- # -*- mode: ruby -*-
@ -131,7 +144,7 @@ via: https://fedoramagazine.org/vagrant-qemukvm-fedora-devops-sysadmin/
作者:[Andy Mott][a] 作者:[Andy Mott][a]
选题:[lujun9972][b] 选题:[lujun9972][b]
译者:[geekpi](https://github.com/geekpi) 译者:[geekpi](https://github.com/geekpi)
校对:[校对者ID](https://github.com/校对者ID) 校对:[wxy](https://github.com/wxy)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出

View File

@ -1,5 +1,5 @@
[#]: collector: (lujun9972) [#]: collector: (lujun9972)
[#]: translator: ( ) [#]: translator: (gxlct008)
[#]: reviewer: ( ) [#]: reviewer: ( )
[#]: publisher: ( ) [#]: publisher: ( )
[#]: url: ( ) [#]: url: ( )

View File

@ -1,5 +1,5 @@
[#]: collector: (lujun9972) [#]: collector: (lujun9972)
[#]: translator: ( ) [#]: translator: (lxbwolf)
[#]: reviewer: ( ) [#]: reviewer: ( )
[#]: publisher: ( ) [#]: publisher: ( )
[#]: url: ( ) [#]: url: ( )

View File

@ -7,18 +7,17 @@
[#]: via: (https://nicolasparada.netlify.com/posts/go-messenger-messages/) [#]: via: (https://nicolasparada.netlify.com/posts/go-messenger-messages/)
[#]: author: (Nicolás Parada https://nicolasparada.netlify.com/) [#]: author: (Nicolás Parada https://nicolasparada.netlify.com/)
Building a Messenger App: Messages 构建一个即时消息应用(四):消息
====== ======
This post is the 4th on a series: 本文是该系列的第四篇。
* [Part 1: Schema][1] * [第一篇: 模式][1]
* [Part 2: OAuth][2] * [第二篇: OAuth][2]
* [Part 3: Conversations][3] * [第三篇: 对话Conversations][3]
在这篇文章中,我们将对端点进行编码以创建一条消息并列出它们,同时还将编写一个端点以更新参与者上次阅读消息的时间。 首先在 `main()` 函数中添加这些路由。
In this post well code the endpoints to create a message and list them, also an endpoint to update the last time the participant read messages. Start by adding these routes in the `main()` function.
``` ```
router.HandleFunc("POST", "/api/conversations/:conversationID/messages", requireJSON(guard(createMessage))) router.HandleFunc("POST", "/api/conversations/:conversationID/messages", requireJSON(guard(createMessage)))
@ -26,11 +25,11 @@ router.HandleFunc("GET", "/api/conversations/:conversationID/messages", guard(ge
router.HandleFunc("POST", "/api/conversations/:conversationID/read_messages", guard(readMessages)) router.HandleFunc("POST", "/api/conversations/:conversationID/read_messages", guard(readMessages))
``` ```
Messages goes into conversations so the endpoint includes the conversation ID. 消息进入对话,因此端点包含对话 ID。
### Create Message ### 创建消息
This endpoint handles POST requests to `/api/conversations/{conversationID}/messages` with a JSON body with just the message content and return the newly created message. It has two side affects: it updates the conversation `last_message_id` and updates the participant `messages_read_at`. 该端点使用仅包含消息内容的 JSON 主体处理对 `/api/conversations/{conversationID}/messages` 的 POST 请求,并返回新创建的消息。 它有两个副作用:更新对话 `last_message_id` 以及更新参与者 `messages_read_at`
``` ```
func createMessage(w http.ResponseWriter, r *http.Request) { func createMessage(w http.ResponseWriter, r *http.Request) {
@ -119,7 +118,7 @@ func createMessage(w http.ResponseWriter, r *http.Request) {
} }
``` ```
First, it decodes the request body into an struct with the message content. Then, it validates the content is not empty and has less than 480 characters. 首先,它将请求正文解码为具有消息内容的结构。然后,它验证内容不为空并且少于 480 个字符。
``` ```
var rxSpaces = regexp.MustCompile("\\s+") var rxSpaces = regexp.MustCompile("\\s+")
@ -141,9 +140,9 @@ func removeSpaces(s string) string {
} }
``` ```
This is the function to remove spaces. It iterates over each line, remove more than two consecutives spaces and returns with the non empty lines. 这是删除空格的函数。它迭代每一行,删除两个以上的连续空格,然后回非空行。
After the validation, it starts an SQL transaction. First, it queries for the participant existance in the conversation. 验证之后,它将启动一个 SQL 事务。首先,它查询对话中的参与者是否存在。
``` ```
func queryParticipantExistance(ctx context.Context, tx *sql.Tx, userID, conversationID string) (bool, error) { func queryParticipantExistance(ctx context.Context, tx *sql.Tx, userID, conversationID string) (bool, error) {
@ -161,13 +160,13 @@ func queryParticipantExistance(ctx context.Context, tx *sql.Tx, userID, conversa
} }
``` ```
I extracted it into a function because its reused later. 我将其提取到一个函数中,因为稍后可以重用。
If the user isnt participant of the conversation, we return with a `404 Not Found` error. 如果用户不是对话参与者,我们将返回一个 `404 NOT Found` 错误。
Then, it inserts the message and updates the conversation `last_message_id`. Since this point, `last_message_id` cannot by `NULL` because we dont allow removing messages. 然后,它插入消息并更新对话 `last_message_id`。从这时起,由于我们不允许删除消息,因此 `last_message_id` 不能为 `NULL`
Then it commits the transaction and we update the participant `messages_read_at` in a goroutine. 接下来提交事务,并在 goroutine 中更新参与者 `messages_read_at`
``` ```
func updateMessagesReadAt(ctx context.Context, userID, conversationID string) error { func updateMessagesReadAt(ctx context.Context, userID, conversationID string) error {
@ -185,11 +184,11 @@ func updateMessagesReadAt(ctx context.Context, userID, conversationID string) er
} }
``` ```
Before responding with the new message, we must notify about it. This is for the realtime part well code in the next post so I left a comment there. 在回复这条新消息之前,我们必须通知它。 这是我们将要在下一篇文章中编写的实时部分,因此我在那里留一个注释。
### Get Messages ### 获取消息
This endpoint handles GET requests to `/api/conversations/{conversationID}/messages`. It responds with a JSON array with all the messages in the conversation. It also has the same side affect of updating the participant `messages_read_at`. 这个端点处理对 `/api/conversations/{conversationID}/messages` 的 GET 请求。 它用一个包含会话中所有消息的 JSON 数组进行响应。 它还具有更新参与者 `messages_read_at` 的副作用。
``` ```
func getMessages(w http.ResponseWriter, r *http.Request) { func getMessages(w http.ResponseWriter, r *http.Request) {
@ -267,11 +266,11 @@ func getMessages(w http.ResponseWriter, r *http.Request) {
} }
``` ```
First, it begins an SQL transaction in readonly mode. Checks for the participant existance and queries all the messages. In each message, we use the current authenticated user ID to know whether the user owns the message (`mine`). Then it commits the transaction, updates the participant `messages_read_at` in a goroutine and respond with the messages. 首先,它以只读模式开始一个 SQL 事务。 检查参与者是否存在并查询所有消息。 在每条消息中,我们使用当前经过身份验证的用户 ID 来了解用户是否拥有该消息(`我的`)。 然后,它提交事务,在 goroutine 中更新参与者 `messages_read_at` 并以消息响应。
### Read Messages ### 阅读消息
This endpoint handles POST requests to `/api/conversations/{conversationID}/read_messages`. Without any request or response body. In the frontend well make this request each time a new message arrive in the realtime stream. 该端点处理对 `/api/conversations/{conversationID}/read_messages` 的 POST 请求。 没有任何请求或响应主体。 在前端,每次有新消息到达实时流时,我们都会发出此请求。
``` ```
func readMessages(w http.ResponseWriter, r *http.Request) { func readMessages(w http.ResponseWriter, r *http.Request) {
@ -288,11 +287,11 @@ func readMessages(w http.ResponseWriter, r *http.Request) {
} }
``` ```
It uses the same function weve been using to update the participant `messages_read_at`. 它使用了与更新参与者 `messages_read_at` 相同的函数。
* * * * * *
That concludes it. Realtime messages is the only part left in the backend. Wait for it in the next post. 到此为止。实时消息是后台仅剩的部分了。请等待下一篇文章。
[Souce Code][4] [Souce Code][4]
@ -302,7 +301,7 @@ via: https://nicolasparada.netlify.com/posts/go-messenger-messages/
作者:[Nicolás Parada][a] 作者:[Nicolás Parada][a]
选题:[lujun9972][b] 选题:[lujun9972][b]
译者:[译者ID](https://github.com/译者ID) 译者:[译者ID](https://github.com/gxlct008)
校对:[校对者ID](https://github.com/校对者ID) 校对:[校对者ID](https://github.com/校对者ID)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出

View File

@ -1,5 +1,5 @@
[#]: collector: (lujun9972) [#]: collector: (lujun9972)
[#]: translator: ( ) [#]: translator: (gxlct008)
[#]: reviewer: ( ) [#]: reviewer: ( )
[#]: publisher: ( ) [#]: publisher: ( )
[#]: url: ( ) [#]: url: ( )
@ -7,25 +7,24 @@
[#]: via: (https://nicolasparada.netlify.com/posts/go-messenger-realtime-messages/) [#]: via: (https://nicolasparada.netlify.com/posts/go-messenger-realtime-messages/)
[#]: author: (Nicolás Parada https://nicolasparada.netlify.com/) [#]: author: (Nicolás Parada https://nicolasparada.netlify.com/)
Building a Messenger App: Realtime Messages 构建一个即时消息应用(五):实时消息
====== ======
This post is the 5th on a series: 本文是该系列的第五篇。
* [Part 1: Schema][1] * [第一篇: 模式][1]
* [Part 2: OAuth][2] * [第二篇: OAuth][2]
* [Part 3: Conversations][3] * [第三篇: 对话][3]
* [Part 4: Messages][4] * [第四篇: 消息][4]
对于实时消息,我们将使用 [服务器发送事件 (Server-Sent Events)][5]。这是一个开放的连接,我们可以在其中传输数据。我们将拥有用户订阅发送给他的所有消息的和端点。
For realtime messages well use [Server-Sent Events][5]. This is an open connection in which we can stream data. Well have and endpoint in which the user subscribes to all the messages sended to him. ### 消息户端
### Message Clients 在 HTTP 部分之前,让我们先编写一个<ruby>映射<rt>map</rt></ruby> ,让所有客户端都监听消息。 像这样全局初始化:
Before the HTTP part, lets code a map to have all the clients listening for messages. Initialize this globally like so: ```go
```
type MessageClient struct { type MessageClient struct {
Messages chan Message Messages chan Message
UserID string UserID string
@ -34,17 +33,17 @@ type MessageClient struct {
var messageClients sync.Map var messageClients sync.Map
``` ```
### New Message Created ### 已创建的新消息
Remember in the [last post][4] when we created the message, we left a “TODO” comment. There well dispatch a goroutine with this function. 还记得在 [上一篇文章][4] 中,当我们创建这条消息时,我们留下了一个 “TODO” 注释。在那里,我们将使用这个函数来调度一个 goroutine。
``` ```go
go messageCreated(message) go messageCreated(message)
``` ```
Insert that line just where we left the comment. 把这行代码插入到我们留注释的位置。
``` ```go
func messageCreated(message Message) error { func messageCreated(message Message) error {
if err := db.QueryRow(` if err := db.QueryRow(`
SELECT user_id FROM participants SELECT user_id FROM participants
@ -70,19 +69,19 @@ func broadcastMessage(message Message) {
} }
``` ```
The function queries for the recipient ID (the other participant ID) and sends the message to all the clients. 该函数查询收件人 ID (其他参与者 ID),并将消息发送给所有客户端。
### Subscribe to Messages ### 订阅消息
Lets go to the `main()` function and add this route: 让我们转到 `main()` 函数并添加以下路由:
``` ```go
router.HandleFunc("GET", "/api/messages", guard(subscribeToMessages)) router.HandleFunc("GET", "/api/messages", guard(subscribeToMessages))
``` ```
This endpoint handles GET requests on `/api/messages`. The request should be an [EventSource][6] connection. It responds with an event stream in which the data is JSON formatted. 此端点处理 `/api/messages` 上的 GET 请求。请求应该是一个 [EventSource][6] 连接。它用一个事件流响应,其中的数据是 JSON 格式的。
``` ```go
func subscribeToMessages(w http.ResponseWriter, r *http.Request) { func subscribeToMessages(w http.ResponseWriter, r *http.Request) {
if a := r.Header.Get("Accept"); !strings.Contains(a, "text/event-stream") { if a := r.Header.Get("Accept"); !strings.Contains(a, "text/event-stream") {
http.Error(w, "This endpoint requires an EventSource connection", http.StatusNotAcceptable) http.Error(w, "This endpoint requires an EventSource connection", http.StatusNotAcceptable)
@ -127,29 +126,30 @@ func subscribeToMessages(w http.ResponseWriter, r *http.Request) {
} }
``` ```
First it checks for the correct request headers and checks the server supports streaming. We create a channel of messages to make a client and store it in the clients map. Each time a new message is created, it will go in this channel, so we can read from it with a `for-select` loop. 首先,它检查请求头是否正确,并检查服务器是否支持流式传输。我们创建一个消息通道,用它来构建一个客户端,并将其存储在客户端 map 中。每当创建新消息时,它都会进入这个通道,因此我们可以通过 `for-select` 循环从中读取。
Server-Sent Events uses this format to send data:
``` <ruby>服务器发送事件<rt>Server-Sent Events</rt></ruby>使用以下格式发送数据:
```go
data: some data here\n\n data: some data here\n\n
``` ```
We are sending it in JSON format: 我们以 JSON 格式发送:
``` ```json
data: {"foo":"bar"}\n\n data: {"foo":"bar"}\n\n
``` ```
We are using `fmt.Fprintf()` to write to the response writter in this format and flushing the data in each iteration of the loop. 我们使用 `fmt.Fprintf()` 以这种格式写入响应写入器,并在循环的每次迭代中刷新数据。
This will loop until the connection is closed using the request context. We defered the close of the channel and the delete of the client, so when the loop ends, the channel will be closed and the client wont receive more messages. 这个循环会一直运行,直到使用请求上下文关闭连接为止。我们延迟了通道的关闭和客户端的删除,因此,当循环结束时,频道将被关闭,客户端将不会收到更多的消息。
Note aside, the JavaScript API to work with Server-Sent Events (EventSource) doesnt support setting custom headers 😒 So we cannot set `Authorization: Bearer <token>`. And thats the reason why the `guard()` middleware reads the token from the URL query string also. 注意,<ruby>服务器发送事件<rt>Server-Sent Events</rt></ruby> (EventSource) 的 JavaScript API 不支持设置自定义头部😒,所以我们不能设置 `Authorization: Bearer <token>`。这就是为什么 `guard()` 中间件也会从 URL 查询字符串中读取令牌的原因。
* * * * * *
That concludes the realtime messages. Id like to say thats everything in the backend, but to code the frontend Ill add one more endpoint to login. A login that will be just for development. 实时消息部分到此结束。我想说的是,这就是后端的全部内容。但是为了编写前端代码,我将再增加一个登录端点。一个仅用于开发的登录。
[Souce Code][7] [Souce Code][7]
@ -159,7 +159,7 @@ via: https://nicolasparada.netlify.com/posts/go-messenger-realtime-messages/
作者:[Nicolás Parada][a] 作者:[Nicolás Parada][a]
选题:[lujun9972][b] 选题:[lujun9972][b]
译者:[译者ID](https://github.com/译者ID) 译者:[译者ID](https://github.com/gxlct008)
校对:[校对者ID](https://github.com/校对者ID) 校对:[校对者ID](https://github.com/校对者ID)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出