Gowhich

Durban's Blog

一个控制器的目的是接收来自应用的一个请求

路由机制控制了控制器接收哪个请求

通常每个控制器都有多于一个的路由,而且不同的路由能够执行不同的操作

为了创建一个基本的控制器,Nest.js使用了一个类和装饰器。

装饰器关联类然后允许Nest.js创建一个路由Map

路由

下面看个简单的例子,如下代码

1
2
3
4
5
6
7
8
9
import { Controller, Get, Render, Res } from '@nestjs/common';

@Controller('cats')
export class CatsController {
@Get()
findAll(): string {
return 'This action will returns all cats';
}
}

这里提示一点

Nest提供了一个非常方便的创建控制的命令

1
nest g controller cats

执行后得到下面的结果

1
2
3
4
$ nest g controller cats
CREATE src/cats/cats.controller.spec.ts (478 bytes)
CREATE src/cats/cats.controller.ts (97 bytes)
UPDATE src/app.module.ts (322 bytes)

我们看下src/app.module.ts文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
diff --git a/src/app.module.ts b/src/app.module.ts
index 8662803..7bc3188 100644
--- a/src/app.module.ts
+++ b/src/app.module.ts
@@ -1,10 +1,11 @@
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
+import { CatsController } from './cats/cats.controller';

@Module({
imports: [],
- controllers: [AppController],
+ controllers: [AppController, CatsController],
providers: [AppService],
})
export class AppModule {}

其实自动帮我们更新module中的代码,然后创建了对应的控制器,非常方便,大大提高了开发效率

然后运行

1
npm run start:dev

得到如下输出

1
2
3
4
5
6
7
8
9
[Nest] 9167   - 2020-09-26 11:41:33 PM   [NestFactory] Starting Nest application...
[Nest] 9167 - 2020-09-26 11:41:33 PM [InstanceLoader] AppModule dependencies initialized +21ms
[Nest] 9167 - 2020-09-26 11:41:33 PM [RoutesResolver] AppController {}: +9ms
[Nest] 9167 - 2020-09-26 11:41:33 PM [RouterExplorer] Mapped {, GET} route +13ms
[Nest] 9167 - 2020-09-26 11:41:33 PM [RouterExplorer] Mapped {/index, GET} route +4ms
[Nest] 9167 - 2020-09-26 11:41:33 PM [RouterExplorer] Mapped {/dynamic, GET} route +5ms
[Nest] 9167 - 2020-09-26 11:41:33 PM [RoutesResolver] CatsController {/cats}: +4ms
[Nest] 9167 - 2020-09-26 11:41:33 PM [RouterExplorer] Mapped {/cats, GET} route +4ms
[Nest] 9167 - 2020-09-26 11:41:33 PM [NestApplication] Nest application successfully started +4ms

从输出中可以看出,/cats路由已经添加,当访问/cats的时候,会执行findAll()的方法,同时还是一个Get请求

mac下挂载移动硬盘

对于支持mac读写格式的硬盘,本记录应该不值得参考

本记录只要针对于window下格式化的硬盘挂载到mac下,导致无法写入的问题

1、确认下是否有mount_ntfs这个工具

1
2
$ mount_ntfs -h
mount_ntfs: usage: mount_ntfs [-s] [-o options] special-device filesystem-node

2、将硬盘插入use接口(type-c的没有确认过),运行下面命令查看挂载信息

1
2
$ mount | grep ntfs
/dev/disk2s1 on /Volumes/Elements SE (ntfs, local, nodev, nosuid, read-only, noowners)

3、使用unmount命令,将硬盘软解除挂载

1
sudo umount /dev/disk2s1

4、本地创建一个挂载目录,然后使用mount_ntfs再次挂载

1
mkdir ~/mnt
1
sudo mount_ntfs -o rw,nobrowse /dev/disk2s1  ~/mnt

前面记录了如何安装Nest.js以及如何创建项目

都是很简单,没有太多可以介绍的,但是作为web开发,最关系的是如何调用视图(View)

官方也有文档,点击这里

1、如何展示一个页面

方式也很简单,主要原理是整合了express框架,然后通过express设置模版引擎,这里使用的模板引擎是hbs

我们看下如何配置

先安装hbs

1
npm install --save hbs

然后打开项目根目录下面的main.ts

将里面的bootstrap()方法修改如下

1
2
3
4
5
6
7
8
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);

app.setBaseViewsDir(join(__dirname, '..', 'views'));
app.setViewEngine('hbs');

await app.listen(3000);
}

上面的代码就是配置模版引擎

main.ts最后代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { Controller, Get, Render } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}

@Get()
getHello(): string {
return this.appService.getHello();
}

@Get('/index')
@Render('index')
getIndex() {
return {};
}
}

如何调用视图

1、在根目录(不是src目录)下创建public和views文件夹

2、在views目录中创建index.hbs

添加代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8" />
<title>App</title>
</head>

<body>
Message
</body>

</html>

3、controller中调用视图(View)

代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { Controller, Get, Render, Res } from '@nestjs/common';
import { AppService } from './app.service';
import { Response } from 'express';

@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}

@Get()
getHello(): string {
return this.appService.getHello();
}

@Get('/index')
@Render('index')
getIndex() {
return {};
}

@Get('/dynamic')
getDynamic(@Res() res: Response) {
return res.render('index');
}
}

其中官网也介绍了两种调用视图的方式

第一种,静态视图

1
2
3
4
5
@Get('/index')
@Render('index')
getIndex() {
return {};
}

将视图文件的名称index,添加到Render修饰器里面,然后函数返回对应的变量值就可以了

第二种,动态视图,访问的路由一样的情况,需要根据逻辑来展示不同的视图

1
2
3
4
@Get('/dynamic')
getDynamic(@Res() res: Response) {
return res.render('index');
}

方法中传入res参数并在参数前面加上@Res修饰器,然后调用res.render就可以了,这样配置后就可以正常写View了,后面记录下静态文件相关的使用

2、如何添加静态文件

修改main.ts

1
2
3
4
5
6
7
8
9
10
11
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);

// 添加静态文件目录配置
app.useStaticAssets(join(__dirname, '..', 'public'));
// 添加视图文件目录配置
app.setBaseViewsDir(join(__dirname, '..', 'views'));
app.setViewEngine('hbs');

await app.listen(3000);
}

修改index.hbs

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
<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>App</title>
<link href="/bootstrap/css/bootstrap.min.css" rel="stylesheet" />
</head>

<body>
<header>
<nav class="navbar navbar-light bg-light">
<div class="container-fluid">
<a class="navbar-brand" href="#">Navbar</a>
</div>
</nav>
</header>
<div class='container'>
Message
</div>
<script src="/bootstrap/js/bootstrap.min.js"></script>
</body>

</html>

同时在项目根目录下面建立public目录,添加自己的静态文件,我这里添加的是bootstrap的静态文件

3、如何动态的展示一个页面(页面参数传递)

修改app.controller.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { Controller, Get, Render } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}

@Get()
getHello(): string {
return this.appService.getHello();
}

@Get('/index')
@Render('index')
getIndex() {
return { message: 'Index Page' };
}
}

修改index.hbs

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
<!DOCTYPE html>
<html>

<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>App</title>
<link href="/bootstrap/css/bootstrap.min.css" rel="stylesheet" />
</head>

<body>
<header>
<nav class="navbar navbar-light bg-light">
<div class="container-fluid">
<a class="navbar-brand" href="#">Navbar</a>
</div>
</nav>
</header>

<div class="container">
{{ message }}
</div>

<script src="/bootstrap/js/bootstrap.min.js"></script>
</body>

</html>

Nest.js如何安装

这个过程其实官网也有 - 点击这里

这里做个简单的概述

1、安装nodejs

2、全局安装nest.js,命令如下

1
npm i -g @nestjs/cli

3、创建项目

1
nest new project-name

是不是很简单

4、运行项目

1
npm run start

如果是开发的话

1
npm run start:dev

最近开发一个小的项目
出于几点考虑,最后选择了这个Nest.js

1、方便部署,部署不复杂
2、语法不难
3、提供了很多够用的工具

首先我想到的是用PHP的框架,毕竟用起来很方便,但是想来想去似乎php的安装有点复杂,也许有人会说,那么多提供了现成的php服务器,随便搞一个,部署起来很方便。
说的没错,但我的情况是,我想自己买台服务器,需要自己部署,因为别人部署的我也不是很放心
另外php的安装说实话,我头痛的几个问题就是,安装php的时候,你要知道自己项目用到了哪些扩展,需要根据这个扩展指定对应的安装参数,这个第一次安装还好,后期项目迁移就不好了
如果我有记录能一下子找到还好如果找不到,就比较头痛
估计也有人会说,现在的docker不是很好用,我想说的是,作为一个初来乍到的开发者,如果能给我足够的空间而且性价比高的话我会考虑的,一般一个docker安装下来出去本身代码大小,加上docker镜像的大小,空间很快被占用了
所以我比较熟悉的Laravel、Yii2等框架,我放弃了

于是开始思考我熟悉的另外一门用Python开发的框架,一个是Flask,一个是Django
思来想去,最后也是放弃了
第一个Django的重量级,以及配置的复杂性,Django是不考虑了。另外我自己以为很精简的Flask,虽然可扩展性高,但是社区似乎不活跃,经常用的库也是没有怎么更新了,关键是flask并没有经常更新,感觉不够火热
所以Flask,Laravel被我放弃了

最后熟悉的另外一门Javascript,后端Nodejs
让我眼前一亮,但是最近几年也不是很火
而且比较火的Express、Koajs等框架,也都是非常精简,至于工具的话,还是没有一个好的生态圈
但是唯一让我发下Nest.js这个框架还是不错的选择

第一个部署上就不说了,安装好node之后,代码库拉过来,install一下就可以部署完成
第二个语法真的不难,现在不管前端后端,稍微会程序开发的,Javascript还是多多少少就会的,外加Typescript的协助,可以在开发上很顺手
第三个工具够用,可以去官网看看文档,这里不多介绍
之后我在继续分享关于Nest.js的使用体验

实现元组转换为对象,学习记录

题目简介


给定数组,转换为对象类型,键/值必须在给定数组中。

例如

1
2
3
const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const

const result: TupleToObject<typeof tuple> // expected { tesla: 'tesla', 'model 3': 'model 3', 'model X': 'model X', 'model Y': 'model Y'}

测试用例


1
2
3
4
5
6
7
import { Equal, Expect } from '@type-challenges/utils'

const tuple = ['tesla', 'model 3', 'model X', 'model Y'] as const

type cases = [
Expect<Equal<TupleToObject<typeof tuple>, { tesla: 'tesla'; 'model 3': 'model 3'; 'model X': 'model X'; 'model Y': 'model Y'}>>,
]

答案


1
2
3
type TupleToObject<T extends readonly any[]> = {
[k in T[number]]: k
}

看完这个答案我是蒙蔽状态

实现 Readonly,学习记录

题目简介


无需使用内置的Readonly<T>泛型即可。

构造一个类型,并将T的所有属性设置为只读,这意味着无法重新分配所构造类型的属性。

例如

1
2
3
4
5
6
7
8
9
10
11
12
interface Todo {
title: string
description: string
}

const todo: MyReadonly<Todo> = {
title: "Hey",
description: "foobar"
}

todo.title = "Hello" // Error: cannot reassign a readonly property
todo.description = "barFoo" // Error: cannot reassign a readonly property

测试用例如下

1
2
3
4
5
6
7
8
9
10
11
import { Equal, Expect } from '@type-challenges/utils'

type cases = [
Expect<Equal<MyReadonly<Todo1>, Readonly<Todo1>>>,
]

interface Todo1 {
title: string
description: string
completed: boolean
}

答案如下

1
type MyReadonly<T> = { readonly [K in keyof T]: T[K] }

TypeScript 4.1 Beta 发布

2020年9月18日,Daniel发布了一篇文章,原文点击这里,文中宣布TypeScript 4.1 Beta发布了

真的是上个版本还没了解透彻新的版本就来了,技术更新的太快,往往很多新奇的玩法还没掌握,可能就被新版本替代了

如果想使用新版本的话,可以通过下面的命令进行安装

1
npm install typescript@beta

此次Beta版本带来了一些新的特性、新的检查标志,具体的特性如下

之前一直使用MySQL,后面因为其他原因,需要使用mongodb,但是自己对mongodb又不是很熟悉

今天经历了一个算是我任务超级复杂的案例了

下面看下这个聚合的查询语句

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
db.table_name.aggregate([
{
'$match': {
'dtime': 0
}
},
{
'$lookup': {
'as': 'content',
'localField': 'rule_id',
'foreignField': 'rule_id',
'from': 'table2'
}
},
{
'$addFields': {
starIDs: '$star_ids'
}
},

{
'$unwind': "$starIDs"
},

{
'$addFields': {
starIDStr: {$concat: [ "star_id::", {$toString: "$starIDs"}]}
}
},

{
'$unwind': "$content"
},

{
'$match': {
'content.dtime': 0
}
},
{
'$lookup': {
'as': 'audit',
'localField': 'starIDStr',
'foreignField': 'person_id',
'from': 'table3'
}
},

{
'$group': {
'_id': '$_id',
'doc': {"$first":"$$ROOT"},

}
},

{
"$replaceRoot":{"newRoot":"$doc"}
},

{
'$sort': {
'content.contents.0.ctime': -1,
'content.group_id': -1
}
},

{
'$skip': 0
},

{
'$limit': 15
},
]);

需求是这样的

首先用table_name表去关联table1,别名为content,通过rule_id去关联,之后需要关联另外一个表table3,但是table3这个表的字段跟table2表字段类型不一致

table2表字段是一个数组star_ids: [123,456]

table3表需要关联的字段是一个字符串,字段值举例子(person_id: 'star_id::123'

这个时候需要进行字段的类型转换,于是我们用到了addFields管道工具,从上面的执行语句中可以看到,我们执行了一个unmind操作,主要是为了将数组值转为字符串,

具体效果如下,从如下

1
2
a----1
|----2

变成了

1
2
a----1
a----2

也就是将

1
star_ids: [123,456]

变成了

1
2
star_ids: 123
star_ids: 456

这里注意下,转换后的结果是两条记录

但是我们有个要求就是不能更改原有字段类型,也就是

1
star_ids: [123,456]

这个是不能修改的

于是我们重命名之后在进行字符串的拼接

最后 star_ids: [123,456](这个依然存在不会更改) 分身出来一个 starIDs: [123,456],同时因为unwind了starIDs,最后starIDS变成了

1
2
starIDs: 123
starIDs: 456

然后就可以进行字符串的拼接了

1
2
3
'$addFields': {
starIDStr: {$concat: [ "star_id::", {$toString: "$starIDs"}]}
}

最后starIDs变成了

1
2
starIDs: 'star_id::123'
starIDs:'star_id::456'

最后整理成了与person_id字段值规则一致的了,这样就可以就行查询了

也就有了

1
2
3
4
5
6
7
8
{
'$lookup': {
'as': 'audit',
'localField': 'starIDStr',
'foreignField': 'person_id',
'from': 'table3'
}
},

最后由于由

star_ids: [123, 456]

变成

star_ids: 123

star_ids: 456

的时候

记录条数由一条变成了两条,需要进行去重处理

于是用到了

1
2
3
4
5
{
'$group': {
'_id': '$_id',
}
},

之后由于用到了group,是的记录不能返回所有的字段

于是由了下面这个管道

1
2
3
4
5
6
7
8
9
10
11
{
'$group': {
'_id': '$_id',
'doc': {"$first":"$$ROOT"},

}
},

{
"$replaceRoot":{"newRoot":"$doc"}
},

最终解决了一个复杂的查询,可以说mongodb的管道工具还是很强大的,但是用起来也是超级复杂

记录下学习过程

题目是这样的


无需使用内置的Pick<T, K>泛型即可。

通过从K中选择属性T来构造类型

例如

1
2
3
4
5
6
7
8
9
10
11
12
interface Todo {
title: string
description: string
completed: boolean
}

type TodoPreview = MyPick<Todo, 'title' | 'completed'>

const todo: TodoPreview = {
title: 'Clean room',
completed: false,
}

继续之前推荐大家去typescript官网的playground,因为里面提供了自动语法检测的功能

比如上面的题目,可以去这里,点击这里打开就能跳转过去

默认情况下,测试用例如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/* _____________ 测试用例 _____________ */
import { Equal, Expect } from '@type-challenges/utils'

type cases = [
Expect<Equal<Expected1, MyPick<Todo, 'title'>>>,
Expect<Equal<Expected2, MyPick<Todo, 'title' | 'completed'>>>,
// @ts-expect-error
MyPick<Todo, 'title' | 'completed' | 'invalid'>,
]

interface Todo {
title: string
description: string
completed: boolean
}

interface Expected1 {
title: string
}

interface Expected2 {
title: string
completed: boolean
}

然后需要我们完成的代码如下

1
2
3
/* _____________ 你的代码 _____________ */

type MyPick<T, K> = any

只要完成“你的代码部分”,测试用例部分的错误提示就会消失

答案我公布下,参考别人的

这个值得仔细斟酌下,我感觉我都看不懂了

1
type MyPick<T, K extends number | string | symbol> = { [k in K]: k extends keyof T ? T[k] : never}
0%