后端写完接口,都会提供一份接口文档给前端。
这节我们就来做下这件事情,通过 swagger 生成接口文档。
安装 swagger 的包:
npm install --save @nestjs/swagger
在 main.ts 添加这段代码:
const config = new DocumentBuilder()
.setTitle('会议室预订系统')
.setDescription('api 接口文档')
.setVersion('1.0')
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api-doc', app, document);
用 SwaggerModule 生成接口文档,url 是 /api-doc
访问下:
可以看到所有接口都列出来了:
还有用到的 schema,也就是对象的结构:
只不过很多接口的文档是不对的:
比如用户列表接口,这些参数都不是必选的,而且也没有响应相关的信息:
还有 schema 也没有具体的内容。
这些需要我们加一些装饰器来告诉 swagger。
在 UserController 添加一个 @ApiTags
这样这个 cotroller 的接口会被单独分组:
然后我们一个个接口来看:
先是 /user/register-captcha 接口
@ApiQuery({
name: 'address',
type: String,
description: '邮箱地址',
required: true,
example: 'xxx@xx.com'
})
@ApiResponse({
status: HttpStatus.OK,
description: '发送成功',
type: String
})
通过 @ApiQuery 描述 query 参数,通过 @ApiResponse 描述响应。
然后是 /user/register 接口:
它一共有 2 种状态码,200 和 400:
@ApiBody({type: RegisterUserDto})
@ApiResponse({
status: HttpStatus.BAD_REQUEST,
description: '验证码已失效/验证码不正确/用户已存在',
type: String
})
@ApiResponse({
status: HttpStatus.OK,
description: '注册成功/失败',
type: String
})
请求体的属性需要去 dto 里标识:
然后接口文档里就可看到请求体的信息了:
下面的 schema 里的 RegisterUserDto 也有了内容:
接下来是 /user/login 接口:
它也是有 400 和 200 两种响应:
@ApiBody({
type: LoginUserDto
})
@ApiResponse({
status: HttpStatus.BAD_REQUEST,
description: '用户不存在/密码错误',
type: String
})
@ApiResponse({
status: HttpStatus.OK,
description: '用户信息和 token',
type: LoginUserVo
})
通过 @ApiResponse 标识两种响应,通过 @ApiBody 标识请求体。
然后在 LoginUserDto 和 LoginUserVo 里标识下属性:
LoginUserDto:
LoginuserVo:
import { ApiProperty } from "@nestjs/swagger";
class UserInfo {
@ApiProperty()
id: number;
@ApiProperty({example: 'zhangsan'})
username: string;
@ApiProperty({example: '张三'})
nickName: string;
@ApiProperty({example: 'xx@xx.com'})
email: string;
@ApiProperty({example: 'xxx.png'})
headPic: string;
@ApiProperty({example: '13233333333'})
phoneNumber: string;
@ApiProperty()
isFrozen: boolean;
@ApiProperty()
isAdmin: boolean;
@ApiProperty()
createTime: number;
@ApiProperty({example: ['管理员']})
roles: string[];
@ApiProperty({example: 'query_aaa'})
permissions: string[]
}
export class LoginUserVo {
@ApiProperty()
userInfo: UserInfo;
@ApiProperty()
accessToken: string;
@ApiProperty()
refreshToken: string;
}
之前这里的 UserInfo 是 interface,这里要改成 class 才能加装饰器。
测试下:
/user/admin/login 的 swagger 装饰器和 /user/login 一样。
然后继续看 /user/refresh 接口:
@ApiQuery({
name: 'refreshToken',
type: String,
description: '刷新 token',
required: true,
example: 'xxxxxxxxyyyyyyyyzzzzz'
})
@ApiResponse({
status: HttpStatus.UNAUTHORIZED,
description: 'token 已失效,请重新登录'
})
@ApiResponse({
status: HttpStatus.OK,
description: '刷新成功'
})
用 @ApiQuery 标识 query 参数,用 @ApiResponse 标识两种响应。
但现在刷新成功的 access_token 和 refresh_token 没有显示。
所以我们也需要把这个返回值封装成 vo:
新建 src/user/vo/refresh-token.vo.ts
import { ApiProperty } from "@nestjs/swagger";
export class RefreshTokenVo {
@ApiProperty()
access_token: string;
@ApiProperty()
refresh_token: string;
}
把返回的结果封装成 vo:
const vo = new RefreshTokenVo();
vo.access_token = access_token;
vo.refresh_token = refresh_token;
return vo;
在 @ApiResponse 里标识这个 type
刷新下页面,可以看到现在接口文档里就有了返回数据的结构:
/user/admin/login 的处理方式一样。
接下来是 /user/info 接口:
加一下返回的数据的标识 @ApiResponse。
然后在 UserDetailVo 里加一下 @ApiProperty:
import { ApiProperty } from "@nestjs/swagger";
export class UserDetailVo {
@ApiProperty()
id: number;
@ApiProperty()
username: string;
@ApiProperty()
nickName: string;
@ApiProperty()
email: string;
@ApiProperty()
headPic: string;
@ApiProperty()
phoneNumber: string;
@ApiProperty()
isFrozen: boolean;
@ApiProperty()
createTime: Date;
}
这样返回的数据结构就对了:
但这个接口是需要登录的,我们加一下标识:
然后在 main.ts 里加一下这种 bearer 的认证方式:
这时候这个接口就有了锁的标记,代表需要登录了:
点击锁,填入 access_token,这样再测试接口的时候,会自动带上 token 标识:
比如我输入 xxx,然后点击 authorize
然后点击 try it out 和 execute,可以看到浏览器发送了这个请求,并且带上了 authorization 的 header
可以在 swagger 文档里测试这个接口。
接下来是 /user/update_password
@ApiBearerAuth()
@ApiBody({
type: UpdateUserPasswordDto
})
@ApiResponse({
type: String,
description: '验证码已失效/不正确'
})
在 UpdateUserPasswordDto 里加一下 @ApiProperty
接口文档没啥问题:
接下来是 /user/update_password/captcha 接口
这个接口是需要登录的,当时为了测试方便没有加,现在加一下:
@ApiBearerAuth()
@ApiQuery({
name: 'address',
description: '邮箱地址',
type: String
})
@ApiResponse({
type: String,
description: '发送成功'
})
@RequireLogin()
然后是 /user/update 接口:
@ApiBearerAuth()
@ApiBody({
type: UpdateUserDto
})
@ApiResponse({
status: HttpStatus.BAD_REQUEST,
description: '验证码已失效/不正确'
})
@ApiResponse({
status: HttpStatus.OK,
description: '更新成功',
type: String
})
在 UpdateUserDto 里标识下 @ApiProperty
刷新下,可以看到最新的接口文档:
然后是 /user/freeeze 接口
@ApiBearerAuth()
@ApiQuery({
name: 'id',
description: 'userId',
type: Number
})
@ApiResponse({
type: String,
description: 'success'
})
@RequireLogin()
刷新下:
没啥问题。
最后,还剩下 /user/list 接口:
@ApiBearerAuth()
@ApiQuery({
name: 'pageNo',
description: '第几页',
type: Number
})
@ApiQuery({
name: 'pageSize',
description: '每页多少条',
type: Number
})
@ApiQuery({
name: 'username',
description: '用户名',
type: Number
})
@ApiQuery({
name: 'nickName',
description: '昵称',
type: Number
})
@ApiQuery({
name: 'email',
description: '邮箱地址',
type: Number
})
@ApiResponse({
type: String,
description: '用户列表'
})
@RequireLogin()
这里的返回值需要封装个 vo:
创建 src/user/vo/user-list.vo.ts
import { ApiProperty } from "@nestjs/swagger";
class User {
@ApiProperty()
id: number;
@ApiProperty()
username: string;
@ApiProperty()
nickName: string;
@ApiProperty()
email: string;
@ApiProperty()
phoneNumber: string;
@ApiProperty()
isFrozen: boolean;
@ApiProperty()
headPic: string;
@ApiProperty()
createTime: Date;
}
export class UserListVo {
@ApiProperty({
type: [User]
})
users: User[];
@ApiProperty()
totalCount: number;
}
注意这里标识 User 数组要用 [User]
然后把 findUsers 的返回值改为 UserListVo
const vo = new UserListVo();
vo.users = users;
vo.totalCount = totalCount;
return vo;
刷新下接口文档:
没啥问题。
这样,我们就给所有的接口生成了 api 文档。
代码在小册仓库。
总结
这节我们用 swagger 生成了接口文档。
在 main.ts 里调用 SwaggerModule.setup 来生成接口文档。
然后用 @ApiQuery、@ApiBody、@ApiResponse、@ApiProperty 等来标识每个接口的参数和响应。
并且通过 @ApiBearerAuth 标识需要 jwt 认证的接口。
返回对象的接口需要把它封装成 vo,然后再添加 @ApiProperty。
接口文档提供给前端之后,前端就可以基于这个来写页面了。