木头的博客

我是木头 有些想法 有点精力

这一节, 我们引入 Swagger 来自动根据代码里的注脚来生成接口文档。

Nest 为我们提供了一个专用的模块来搭配 Swagger 来使用

1. 安装依赖

1
yarn add @nestjs/swagger swagger-ui-express

2. 初始化 Swagger 模块

在我们的应用入口文件 main.ts 中添加一个 createSwagger 方法, 并在 bootstrap 方法中初始化它

main.ts
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
import { INestApplication, ValidationPipe } from '@nestjs/common'
import { NestFactory } from '@nestjs/core'
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'
import { AppModule } from 'app.module'

function createSwagger(app: INestApplication) {
const version = require('../package.json').version || ''

const options = new DocumentBuilder()
.setTitle('Nestjs Realworld Example App')
.setVersion(version)
.addBearerAuth()
.build()

const document = SwaggerModule.createDocument(app, options)
SwaggerModule.setup('/docs', app, document)
}

async function bootstrap() {
const app = await NestFactory.create(AppModule)
app.useGlobalPipes(new ValidationPipe())

if (process.env.SWAGGER_ENABLE && process.env.SWAGGER_ENABLE === 'true') {
createSwagger(app)
}

await app.listen(3000)
}

bootstrap().catch((err) => console.error(err))

createSwagger 中, 我们首先读取了来自 package.json 中的版本号来作为接口的版本

然后我们设置了 Title 和 Bearer 鉴权认证入口, 我们还设置了 /docs 为我们文档的入口

最后,我们判断环境变量中的 SWAGGER_ENABLE 是否打开, 如果打开我们就初始化 Swagger 文档系统。

.env.env.template 中增加 SWAGGER_ENABLE=true, 然后启动服务器

访问 http://localhost:3000/docs 就能看见我们的接口文档创建好啦!

swagger preview

阅读全文 »

这里记录了一些个人学习 Flutter 时遇到的一些问题, 可以作为避免踩坑和速查手册. 如有疑问欢迎留言.

阅读全文 »

上一章中, 我们完成了鉴权功能, 也就是 Auth 模块, 顺便简单实现了下注册和登录功能. 这一章我们就来正式的将登录和实现功能做完, 含有完整的数据校验和转化.

在正式的开始完善功能之前, 我们现清理重构一下已有的代码.

1. 重构代码

1.1 整理路由

我们上一张的登录和注册是写在 app.controller.ts 中的, 如果所有的路由都写在这里的话就会比较杂乱, 所以我们将它移动到 Auth 模块下.

首先生成 AuthController

1
nest g controller auth

然后将 /auth/register/auth/login 移到 AuthController 下

auth.controller.ts
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
import { Body, Controller, Post, Request, UseGuards } from '@nestjs/common'
import { AuthGuard } from '@nestjs/passport'
import { UserService } from '../user/user.service'
import { AuthService } from './auth.service'

@Controller('auth')
export class AuthController {
constructor(
private readonly userService: UserService,
private readonly authService: AuthService,
) {}

@Post('/register')
async register(
@Body() requestBody: { email: string; username: string; password: string },
) {
const user = await this.userService.createUser(requestBody)
const token = this.authService.generateToken(user.id, user.username)
return {
user: { ...user, token },
}
}

@UseGuards(AuthGuard('local'))
@Post('/login')
async login(@Request() req) {
const { user } = req
const token = this.authService.generateToken(user.id, user.username)
return {
user: { ...user, token },
}
}
}

顺便也移动下测试代码, 这里就不再赘述了.

阅读全文 »

上一章我们创建了一个用户表, 但是还没有实现真正的注册和登录. 要实现注册登录以及后续的权限校验, 我们还有一些工作要做.

目前有比较多的思路来对用户进行鉴权, 我们选用 Conduit 示例中展示的也是现在比较广泛的做法 JWT 进行认证.

要实现 JWT 鉴权, NestJS 为我们做好了大部分工作.

1. 安装依赖

但是在这之前, 我们要先安装下面的依赖

1
2
yarn add @nestjs/passport passport passport-local
yarn add -D @types/passport-local

Passport 你可以把它看作是一个小型的框架, 因为你可以通过一些简单的回调函数来进行配置. Passport 会在适当的时候对其进行调用.

@nestjs/passport 则对 Passport 进行了很好的集成.

阅读全文 »

上一个文章我们介绍了如何搭建一个开发环境和 Pipeline, 这篇文章开始我们将正式的用 TDD 的模式实现一个后端项目.

1. 安装依赖

我们选用了 Postgres 作为我们的数据库, 操作数据库的 ORM 我们选用 TypeORM, 这是一个 TypeScript 友好的 ORM, 并且 nest 也提供了非常便利的集成方法.

1
yarn add @nestjs/typeorm typeorm pg

2. 接入 TypeORM

编辑 app.module.ts

app.module.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import { Module } from '@nestjs/common'
import { AppController } from './app.controller'
import { AppService } from './app.service'
import { TypeOrmModule } from '@nestjs/typeorm'

@Module({
imports: [
TypeOrmModule.forRoot({
type: 'postgres',
host: 'localhost',
port: 5432,
username: 'realworld',
password: '123456',
database: 'nestjs',
entities: ['dist/**/*.entity{.ts,.js}'],
synchronize: true,
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
阅读全文 »

emmm, 最近计划着学习后端,本来想从 Java 开始,奈何新的知识点一股脑涌进来,只知道教程怎么做,而不说为什么,很是迷茫。

于是想从熟悉的技术栈开始,结合最近学习的知识(TDD、Docker、GitHub Actions)和想学的知识(NestJS、Postgres、Swagger)一步一步巩固和学习。

顺便记录下来沉淀和输出自己的知识,也希望能帮到大家少走一些弯路,告别 2019,迎接 2020!

0. 内容预告

我们这次将要实现的系统是 Conduit 的 API 部分,前些时间我已经 TDD 实践实现了 Conduit 的前端部分,技术栈选择了 Preact.

Conduit 是什么,这是一个基于 Realworld 的示例项目。Realworld 集合了现今大部分的前后端框架,他们用不同的语言和技术展实现了同一个系统,也就是我们这次要做的 Conduit。

当然,Realworld 现在也有 NestJS 的实现,不过既然是结合自己的知识点来学,当然不能照抄啦,假装网友们还没用 NestJS 实现它好啦,[偷笑][偷笑]

这次用到的技术栈有:Nestjs TypeScript Postgres Jest Docker Github Actions Swagger ESLint , 然后我们会以 TDD 的方式进行开发,遵循“红-绿-重构”的方式一步一步的完成我们的项目。

好,话不多说,赶紧进入实战演练吧!

阅读全文 »

文件共享

文件挂载

如果你想将宿主机中的文件或目录挂载至容器中,可以在启动容器时使用 --volume-v 参数.

--volume|-v <host_path>:<container_path>[:permission]

host_path 为要挂载的宿主机目录路径,必须为绝对路径
container_path 为挂载在容器中的目标路径
permission 为挂载目录的权限,默认为读写 rw; 可指定为只读 ro

一些例子

1
2
3
4
# 将宿主机的 Downloads 目录挂载在容器的 `/var/downloads` 中
docker run -v /home/$(whoami)/Downloads:/var/downloads ubuntu /bin/bash
# 挂载时指定为只读模式
docker run -v /var/log:/var/host_log/*:ro ubuntu /bin/bash

从容器中复制文件到宿主机

如果你想将容器中的文件复制到宿主机中,可以使用 docker cp 命令

docker cp <container_id>:<container_source_path> <host_target_path> container_id 容器 ID container_source_path 要拷贝容器内文件的路径 host_target_path 要放置在宿主机内的路径

一些例子

1
2
# 将容器 nginx 中的配置文件复制到宿主机的家目录中
docker cp nginx:/etc/nginx/nginx.conf $HOME

从宿主机复制文件到容器中

从宿主机复制文件到容器中可以使用 文件挂载 (--volume) 的方法,但这种方法不能在运行中的容器中使用,如果你真的想这么做,也有一些稍微麻烦些的办法。

  1. 将宿主机的文件系统直接挂载在容器内,可以参考这篇文章 http://dockerone.com/article/149

  2. 先将容器中需要备份的文件使用 docker cp 复制出来,然后重新运行该容器并使 用 docker run --volume 挂载文件.

  3. 通过 sshfs 挂载文件目录.

  4. 先将容器停掉,然后使用 docker commit 创建一个新的镜像,最后启动该镜像进行文件挂载.

通信

容器间通信的一些 Q&A

  1. 如果不声明 network 字段, 在同一个 docker compose 之间可以通信吗?

    可以

    如果不指定一个特定的 network, docker 会指定容器在一个名为 docker0 的默认网桥中

  2. 如果不声明 expose 选项, 那么同一网络中的其他容器可以访问该容器吗?

    可以

    但如果启动docker守护进程时指定了--icc=false选项,则不可以。
    建议声明, 声明该选项有助于使用者了解容器内暴露了那些端口出来供使用。

  3. 在同一个网络中不同的容器使用了同一个端口会有冲突吗?

    不会

    不同的容器使用不同的 host, 所以哪怕两个容器使用了同样的端口,也不会与其他容器冲突。

  4. exposeports 字段有什么区别?

    如果容器内的端口需要在宿主环境访问,则需提供 ports 字段

    expose 字段在默认情况下是可选的,即使不声明也可以在同一个网络中使用该容器的端口

容器相关

健康检查

HEALTHCHECK 官方文档

如果你想知道容器内应用是否被正常启动了,你可以在启动时指定健康检查测试相关参数.

比如你在启动一个 web 应用容器后,开开心心准备用这个服务,却发现访问不到,启动成功了但没有完全成功,有点气人。

这时候你可以通过设置健康检查命令 curl -f http://localhost:8080 || exit 1 来检查应用真的启动了,而不是因为各种原因导致容器启动了但是内部的应用没启动成功,而造成一种成功了的假象。

用于健康检查的测试命令会在容器内执行,命令退出代码为 非零 或超时没有成功则视为测试失败,等待下一次测试。

参数 作用 (涉及时间的选项均可指定 秒:s 分:m 时:h 等)
interval 执行测试间隔时间。默认 30s
timeout 超时时间,执行测试如果超过了这个时间,则视为失败。默认 30s
retries 重试次数,如果测试连续失败次数如果超过了该值,则容器健康状态会被置为不健康 (unhealthy)
start_period 在容器启动多久之后开始第一次检查。默认 0s

Dockerfile 中的用法示例

1
2
HEALTHCHECK --interval=10s --timeout=3s \
CMD curl -f http://localhost:8080/health || exit 1

docker-compose.yml 中的示例

docker-compose.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
version: '3.8'
services:
grafana:
image: grafana/grafana
healthcheck:
test: wget -nv -t1 --spider http://192.168.5.1:3000 || exit 1
interval: 1m
retries: 3
start_period: 10s # 需要 version >= 3.4

loki:
image: grafana/loki
depends-on:
grafana: # 条件语法需要 version >= 3.8
condition: service_healthy # 在 grafana 健康检查通过后才启动该容器

docker-compose.yml 中使用时

  • 会覆盖镜像中自带的健康检查命令
  • start_period 参数需要 version >= 3.4
  • 可根据容器的健康状态来成为 depends-on 执行条件(条件语法需要 version >= 3.8)

设置了健康检查后,会在容器列表中看到一列多的内容

1
docker ps

health check docker ps

还可以在 docker inspect 中帮助排查问题

1
docker inspect --format '{{json .State.Health}}' loki | jq .

health check docker inspect

需要注意的是,健康检查的测试命令会在重启内执行,所以测试命令容器内可执行。比如在 alpine 版本的镜像中是不含 curl 程序的,这时你可以用 wget 来代替。

一些连通性检查的例子

1
2
3
4
5
6
7
8
9
# 检查 本机 8080 端口是否能连通
curl -f http://localhost:8080 || exit 1

# 在 alpine 镜像中检查 8080 端口是否能连通
wget -nv -t1 --spider http://localhost:8080 || exit 1

# Loki 中检查是否启动成功 (Loki 的 health check API 很奇怪,wget with --spider 时会报 405,只好另辟蹊径)
# Update: 是因为 wget with --spider 会先发送一个 HEAD 请求,Loki 不认
[ "$(wget -nv -t1 -o /dev/null -O - http://localhost:3100/ready)" = "ready" ] || exit 1

参考文章

最近在使用 Cypress 作为前端项目的 E2E 测试,发布到 CI 环境时自动运行。

运行时发现在 Pipeline 测试报告中有生成 mp4 格式的视频,这才想起来 Cypress 自带生成视频快照的功能,结合 Jenkins 收集报告产物,不就可以拿到视频快照了吗?

哈哈,talk is cheap, show my code!

下面是 jenkins 配置

Jenkinsfile
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
pipeline {
agent any

environment {
CHROME_BIN = '/bin/google-chrome'
}

stages {
stage('Environment') {
steps {
sh 'uname -a'
sh 'apt-get update'
sh 'apt-get install -y xvfb libgtk-3-dev libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 ttf-wqy-zenhei'
sh 'fc-cache -v'
sh 'yarn install'
}
}
stage('Test') {
steps {
sh 'yarn test:e2e --headless'
junit 'reporter/output.xml'
archiveArtifacts 'tests/e2e/videos/*.mp4'
}
}
}
}

其中 ttf-wqy-zenhei 是用来解决 Ubuntu 系统中没有中文字体等问题,否则生成的视频报告中的中文都为方框乱码。

如果是在 CentOS 中,执行 yum -y groupinstall chinese-support 解决中文问题

如果不知道 Jenkins 所属运行环境,使用 uname -a 查看系统信息

archiveArtifacts 用来收集报告产物

junit 用来收集测试报告,但 Cypress 默认是不生成报告的,需要在 cypress.json 中增加以下内容

cypress.json
1
2
3
4
5
6
7
{
"reporter": "junit",
"reporterOptions": {
"mochaFile": "reporter/output.xml",
"toConsole": true
}
}
阅读全文 »

我们在写 shell 脚本时经常会遇到一些需要交互的操作,比如修改某个文件,或是使用 yum install ssh-keygen certbot --nginx 等操作时,需要输入一些指令如 “y”, “Enter” 和其他的一些信息。

我们写脚本就是为了自动操作,怎么可以等命令执行一会之后在按个回车进行下一步呢?既然我知道接下来要输入什么命令,我告诉你你帮我输入了不就得了?

聪明tōu lǎn的我们想到了一些办法来避免这种无谓的等待,记录下来分享给大家

阅读全文 »

最近在学习 Flutter 补充移动端开发的技术栈,刚好换电脑,特此从 0 开始重新搭建 Flutter 开发环境

整个安装环境是基于 macOS 操作系统的,如果你使用的是 Windows 操作系统,可以参考其他教程或本博文大致思路

1. 环境安装包下载

以下几个安装包体积较大,所以在观看本教程前需要提前进行下载

2. Flutter SDK 安装

下载好 Flutter SDK 后,解压到一个存放 SDK 的目录,我这里存放在 ~/.flutter-sdk

1
2
3
mkdir ~/.flutter-sdk
cd ~/.flutter-sdk
unzip ~/Downloads/flutter_macos_v1.7.8+hotfix.4-stable.zip

然后将 Flutter SDK 的安装目录暴露给环境变量,在 ~/.zshrc~/.bashrc 文件中增加以下内容

1
2
# Flutter
export PATH="$PATH:$HOME/.flutter-sdk/flutter/bin"

然后你还可以下载 Flutter 在未来会需要的二进制包(可选的,也可以在未来下载)

1
flutter precache

最后运行检查工具 flutter doctor ,来检查你的 Flutter 是否可以正常运行, 如果出现下面的信息,就说明你安装 Flutter SDK 成功啦

1
2
3
4
5
6
7
8
9
flutter doctor

➜ .flutter-sdk flutter doctor
[✓] Flutter (Channel stable, v1.7.8+hotfix.4, on Mac OS X 10.14.5 18F132, locale en-CN)
• Flutter version 1.7.8+hotfix.4 at /Users/yourusername/.flutter-sdk/flutter
• Framework revision 20e59316b8 (6 weeks ago), 2019-07-18 20:04:33 -0700
• Engine revision fee001c93f
• Dart version 2.4.0
...
阅读全文 »
0%