为什么学习NestJS
你以为我会跟你说些大道理吗?你错了,我的故事是这样的。。。
那晚,我还在畅快的玩着手机,媳妇走过来了,说:”老公,你不是做IT的吗,我想做个项目,感觉很有前景的样子,你也可以自己练练手“ 然后就这样了
我的工作范围从一个专业Bug前端程序猿 到 修手机 到 修电脑 然后又到了 写后台做整个项目了。为了不影响家庭和睦,我也是抱着练练手的态度,跟媳妇在床上畅聊了一夜。。。
进入正题
这是我们家Java写的,反正我看着挺爽
我的最终目标,虽然没有java搞的漂亮,但是我尽力了
起步
关于SwaggerUI,别的装饰器在这里就不写了,大家可以参考文章开始推荐的链接。直接3挡起步,Zoom~!Zoom~!Zoom~!
@ApiResponse({ status: 200, type: UserItemDto, isArray: true })
刚开始 我的文档返回示例是这个鸟样子,感觉少了点什么。哦~~ code 和 msg 呢?
深挖1
没关系,我们有泛型,机智如我
content.dto.ts
class ResponseDto {
@ApiProperty()
code: number
@ApiProperty()
msg: string
}
export class ResponseObjDto<TData> extends ResponseDto {
@ApiProperty()
data: TData
}
export class ResponseListDto<TData> extends ResponseDto {
@ApiProperty()
data: TData[]
}
她好像让我new一下,满足她!
然后,她不愿意,还说我不是她稀饭的类型
深挖2
我艹,不支持泛型!突然感觉知识很匮乏,幸好官方文档给出了方案,需要使用 Schema
@ApiOkResponse({
schema: {
allOf: [
{ $ref: getSchemaPath(ResponseListDto) },
{
properties: {
data: {
type: 'array',
items: { $ref: getSchemaPath(UserItemDto) },
},
},
},
],
},
})
async findAll() {
return await this.userService.findAll()
}
getSchemaPath()
函数从一个给定模型的OpenAPI指定文件返回OpenAPI原型路径allOf
是一个OAS3的概念,包括各种各样相关用例的继承。
看,她来了~!😄
深挖3
你以为这样就好了吗?官网都说了,你不得封装一下子,来复用啊?!于是乎我就有了下面这三个自定义装饰器 公共的DTO我都定义在了这里 content.dto.ts
api-list-response.decorator.ts
import { applyDecorators, Type } from '@nestjs/common'
import { ApiOkResponse, getSchemaPath } from '@nestjs/swagger'
import { ResponseListDto } from '../logical/content/dto/content.dto'
export const ApiListResponse = <TModel extends Type<any>>(model: TModel) => {
return applyDecorators(
ApiOkResponse({
schema: {
title: '响应示例',
allOf: [
{ $ref: getSchemaPath(ResponseListDto) },
{
properties: {
code: { type: 'number', default: '0' },
msg: { type: 'string', default: 'success' },
data: {
type: 'array',
items: { $ref: getSchemaPath(model) },
},
},
},
],
},
}),
)
}
api-obj-response.decorator.ts
import { applyDecorators, Type } from '@nestjs/common'
import { ApiOkResponse, getSchemaPath } from '@nestjs/swagger'
import { ResponseObjDto } from '../logical/content/dto/content.dto'
export const ApiObjResponse = <TModel extends Type<any>>(model: TModel) => {
return applyDecorators(
ApiOkResponse({
schema: {
title: '响应示例',
allOf: [
{ $ref: getSchemaPath(ResponseObjDto) },
{
properties: {
code: { type: 'number', default: '0' },
msg: { type: 'string', default: 'success' },
data: {
$ref: getSchemaPath(model),
},
},
},
],
},
}),
)
}
套娃现场👇👇👇👇👇
api-paginated-response.decorator.ts
import { applyDecorators, Type } from '@nestjs/common'
import { ApiOkResponse, getSchemaPath } from '@nestjs/swagger'
import {
PaginatedDto,
ResponsePaginatedDto,
} from '../logical/content/dto/content.dto'
export const ApiPaginatedResponse = <TModel extends Type<any>>(
model: TModel,
) => {
return applyDecorators(
ApiOkResponse({
schema: {
title: '响应示例',
allOf: [
{ $ref: getSchemaPath(ResponsePaginatedDto) },
{
properties: {
code: { type: 'number', default: '0' },
msg: { type: 'string', default: 'success' },
data: {
allOf: [
{ $ref: getSchemaPath(PaginatedDto) },
{
properties: {
results: {
type: 'array',
items: { $ref: getSchemaPath(model) },
},
},
},
],
type: 'object',
items: { $ref: getSchemaPath(PaginatedDto) },
},
},
},
],
},
}),
)
}
我 用 上了之后,她更美了🤓
@ApiListResponse(UserItemDto)
async findAll() {
return await this.userService.findAll()
}
@ApiPaginatedResponse(UserItemDto)
async findAll() {
return await this.userService.findAll()
}
@ApiObjResponse(UserItemDto)
async findOne(@Query('username') username) {
return await this.userService.findOne(username)
}
贴个分页的示例吧
深挖4 - CLI Plugin
到了这里,我决定扛着铁锹再挖一下,有意想不到的收获(以下内容来自 NestJS中文文档)因为有你们,我才可以安心的当个屌丝
Swagger插件可以自动:
- 使用
@ApiProperty
注释所有除了用@ApiHideProperty
装饰的DTO属性。 - 根据问号符号确定
required
属性(例如name?: string
将设置required: false
) - 根据类型配置
type
为enum
(也支持数组) - 基于给定的默认值配置默认参数
- 基于
class-validator
装饰器配置一些验证策略(如果classValidatorShim
配置为true
) - 为每个终端添加一个响应装饰器,包括合适的状态和类型(响应模式)
- 根据注释生成属性和终端的描述(如果
introspectComments
配置为true
) - 基于注释生成属性的示例数据(如果
introspectComments
配置为true
)
注意,你的文件名必须有如下后缀: ['.dto.ts', '.entity.ts']
(例如create-user.dto.ts
) 才能被插件分析
- 首先你要启用Swagger插件
nest-cli.json
{
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"plugins": [
{
"name": "@nestjs/swagger",
"options": {
"classValidatorShim": false,
"introspectComments": true,
"controllerKeyOfComment": "summary"
}
}
]
}
}
Opiton选项:
Option | Default | Description |
---|---|---|
dtoFileNameSuffix | ['.dto.ts', '.entity.ts'] | DTO (数据传输对象)文件后缀 |
controllerFileNameSuffix | .controller.ts | 控制文件后缀 |
classValidatorShim | true | 如果设置为true,则模块将重用class-validator验证装饰器(例如,@max(10)将max:10添加到schema定义) |
dtoKeyOfComment | 'description' | 将注释文本设置为ApiProperty 的属性键。 |
controllerKeyOfComment | 'description' | 将注释文本设置为ApiOperation 的属性键。 |
introspectComments | false | 如果配置为true ,插件将根据描述注释生成说明和示例 |
- 然后我就开始对DTO下手了。。。 脱了她们一层”衣服“ 👗👠👒 👉👉 👙
export class UserItemDto extends ContentDto {
/**
* 用户名
* @example Chaos
*/
@IsNotEmpty({ message: '用户名不能为空' })
@IsString({ message: '用户名必须是 String 类型' })
username?: string
/**
* 邮箱
*/
email?: string
...此处省略1万行
/**
* 用户状态: 1-可用 0-不可用
*/
status: number
/**
* 手机号
* @example 18383838383
*/
@IsNotEmpty({ message: '手机号不能为空' })
phone: string
/**
* 真实姓名
* @example: '郭百万'
*/
@IsNotEmpty({ message: '真实姓名不能为空' })
@IsString({ message: '真实姓名必须是 String 类型' })
trueName: string
}
- 然后又一阵翻云覆雨,就可以这么漂亮了
同样的,控制器中的方法也可以去掉一个ApiOperation
装饰器了
/**
* 根据用户名查询用户信息
* @param username
*/
@Get()
// @ApiOperation({ description: '根据用户名查询用户信息' })
@ApiQuery({ name: 'username', description: '用户名' })
@ApiObjResponse(UserItemDto)
async findOne(@Query('username') username) {
return await this.userService.findOne(username)
}
深挖4
报应来了,Swagger文档报错了,虽然不影响使用,但是我坐不住了
Resolver error at paths./lease-mgr/user/list.get.responses.200.content.application/json.schema.allOf.0.$ref Could not resolve reference: #/components/schemas/ResponseListDto
阅读文档过程中有强调过,但是当时并没有明白这样做的用意,然后就有了血的教训。就感觉NestJS文档鄙视了我一下,不听老人言,死在我面前。
因为
ResponseListDto
没有被任何控制器直接引用,SwaggerModule
还不能生成一个相应的模型定义。我们需要一个额外的模型,可以在控制器水平使用@ApiExtraModels()
装饰器。
@ApiTags('user')
@ApiBearerAuth()
@Controller('user')
@ApiExtraModels(
PaginatedDto,
UserItemDto,
ResponseObjDto,
ResponseListDto,
ResponsePaginatedDto,
)
export class UserController {
...
}
深挖5
搁浅了。。。
其实想把文档搞成我们家Java的那样,奈何没有找到解决方案,希望可以有高人来指点!
总结
对于大佬们来说,可能研究的还不算深。我说深 她就深!要你管!?还请大佬们多多指点,多多担待,自己学习的路还很长。