初始化 Nest 项目

使用 nest new <name> 创建一个新的 Nest 项目,然后根据自己的喜好进行一定的自定义。

用户实体

本文使用 Prisma 作为 ORM 框架,因此要先安装 Prisma 并初始化 Prisma schema 文件:

1
npm i prisma && npx prisma init

然后编写用户实体字段,这里以基础的 idusernamepassword 三个经典字段举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
generator client {
provider = "prisma-client-js"
}

datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}

model User {
id Int @id @default(autoincrement())
username String @unique
password String
}

然后使用 prisma migrate dev --name init 创建一个 migration,这个命令会自动同步上面的数据模型到数据库中。

配置 PrismaService

想要在项目的各种地方使用 Prisma,还需要创建一个 PrismaClient:

1
2
3
4
5
6
7
8
9
import { Injectable, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
async onModuleInit() {
await this.$connect();
}
}

然后在需要使用到 PrismaService 的模块中注入,以下面会提到的 Auth 模块为例:

1
2
3
4
5
6
7
8
import { AuthController } from './auth.controller'
import { AuthService } from './auth.service'

@Module({
controllers: [AuthController],
providers: [PrismaService],
})
export class AuthModule {}

注册

实现注册接口

使用 nest g 命令分别创建名为 auth 的 Controller、Module 和 Service。

新建 src/modules/auth/dto/create-user.dto.ts 文件。dto 文件是用于定义数据通过请求传入的样子。这里表示只需要用户名和密码即可调用注册接口,可根据自己需求变动。

1
2
3
4
export class CreateUserDto {
username: string
password: string
}

AuthController 中创建用于注册的接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { Body, Controller , Post, Req } from '@nestjs/common'
import { CreateUserDto } from '@/modules/user/dto/create-user.dto'
import { AuthService } from './auth.service'

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

@Post('register')
async register(@Body() user: CreateUserDto) {
return await this.authService.createUser(user.username, user.password)
}
}

这里直接调用了 AuthService.createUser 方法并将结果返回。在 AuthService 中实现这个方法:

1
2
3
4
5
6
7
8
9
10
11
@Injectable()
export class AuthService {
constructor(
private readonly prisma: PrismaService,
private readonly jwtService: JwtService,
) {}

async createUser(username: string, password: string) {
return await this.prisma.user.create({ data: { username, password } })
}
}

避免存储明文密码

上面的方法直接将 Prisma 得到的用户数据返回,实际上这是不安全的,因为返回的字段中包含了用户的密码,而且在目前的实现中,用户的密码也没有经过加密,而是直接明文存储,可以使用 bcrypt 这个工具来帮助解决这个问题。

安装 bcryptnpm i bcrypt && npm i @types/bcrypt -D

然后将实现更改为下面的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Injectable()
export class AuthService {
constructor(
private readonly prisma: PrismaService,
private readonly jwtService: JwtService,
) {}

async createUser(username: string, password: string) {
password = await hash(password, 10)
const user = await this.prisma.user.create({ data: { username, password } })
return { id: user.id, username: user.username }
}
}

其中 hashbcrypt 包提供的函数,用于对密码字符串进行哈希算法并且自动加盐。这样存储在数据库中的就是密码的哈希值而不是明文。但即使存储的不是明文,也应当避免将哈希直接暴露给前端。

登录

使用 passportjs 认证

由于咱们要使用 passportjs 来实现认证,passportjs 是一个使用策略模式的认证库,其支持了很多种的认证方式,咱们要用到的就是 localjwt 两种。前者从本地进行认证(比如数据库或其他你通过代码能进行认证的源),后者使用 JsonWebToken 进行用户认证。

用户在登录的时候使用用户名和密码进行认证,认证成功后服务器给用户发放 JWT,后续对限制的接口请求时通过 JWT 来认证用户。因此在实现登录的时候先实现 jwt 策略。

实现 LocalStrategy

首先安装 @nestjs/passportpassport-local 这两个包(当然也包含 @types/passport-local)。前者的作用是将 passportjs 整合到 Nest 中,而后者则是实现了 passport 的具体策略包。

创建一个 LocalStrategy 类并继承 PassportStrategyPassportStrategy 并不是一个具体的类,而是一个返回类(或者说可以使用 new 调用的函数)的函数,这个函数接收一个策略对象,而传入的具体策略就是咱们要使用的策略,因此这里传入从 passport-local 中导入的 Strategy

在构造函数中注入 PrismaService,因为后续在具体实现中会通过数据库查询用户。

构造函数中还调用的父类的构造函数,并且传入了关于这个策略的配置选项,下面的示例中配置选项为空效果也是一样的,默认就是使用 username 作为用户名字段和 password 作为密码字段,这里只是展示一下如果要配置策略选项应该怎么做。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { Injectable } from '@nestjs/common'
import { PassportStrategy } from '@nestjs/passport'
import { IStrategyOptions, Strategy } from 'passport-local'
import { PrismaService } from '@/prisma.service'

@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(
private readonly prisma: PrismaService,
) {
super(<IStrategyOptions>{
usernameField: 'username',
passwordField: 'password',
})
}
}

要实现具体的逻辑,咱们需要在类中添加一个名为 validate 的函数(参考),PassportStrategy 会调用这个函数,并将函数返回的结果设置在 Request.user 这个变量上。

下面是具体的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { Injectable, UnauthorizedException } from '@nestjs/common'
import { PassportStrategy } from '@nestjs/passport'
import { Strategy } from 'passport-local'
import { compare } from 'bcrypt'

@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
// constructor omitted...

public async validate(username: string, password: string) {
const user = await this.prisma.user.findFirst({ where: { username } })
if (!user) {
throw new UnauthorizedException()
}
if (!await compare(password, user.password)) {
throw new UnauthorizedException()
}
return {
id: user.id,
username: user.username,
} // avoid exposing the password field
}
}

首先咱们通过用户名查询用户,如果无法找到对应用户名的用户,则返回 401(实际上是抛出一个 UnauthorizedException)。得益于 Nest 的良好设计,在业务中的任何位置出现异常可以直接抛出一个异常,Nest 内置了一些常见的 HTTP 相关异常,在捕获到这些异常的时候,会直接响应对应的异常信息给客户端。

如果查找到了对应的用户,则验证密码是否正确,别忘了上面已经将明文存储密码改为了使用 bcrypt 处理过的哈希值,因此这里也使用 bcrypt 提供的 compare 对其密码进行验证。如果验证失败,也抛出一个 UnauthorizedException

若验证成功,则返回用户对象,这里同样只暴露了 id 和 username 字段,上文提到这个对象会被设置到 Request.user 上,也就是说用户是不能直接看到的,但最好还是尽量避免暴露这个字段在不必要的位置。

在 AuthGuard 中应用策略

实现了 LocalStrategy 之后,就可以配合 AuthGuard@nestjs/passport 提供的)在 Controller 中当作 Guard 应用了。

首先将 LocalStrategy 提供给 AuthModule,才能在 AuthController 中使用,而 LocalStrategy 需要注入 PrismaService,因此也要将 PrismaService 提供到这个模块中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { Module } from '@nestjs/common'
import { LocalStrategy } from '@/common/strategy/local.strategy'
import { PrismaService } from '@/prisma.service'
import { AuthService } from './auth.service'
import { AuthController } from './auth.controller'

@Module({
controllers: [AuthController],
providers: [
AuthService,
PrismaService,
LocalStrategy,
],
})
export class AuthModule {}

在 Controller 中应用 Guard:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { Controller, Post, Req, UseGuards } from '@nestjs/common'
import { AuthGuard } from '@nestjs/passport'
import { AuthedRequest } from '@/types'
import { AuthService } from './auth.service'

@Controller()
export class AuthController {
// constructor and register endpoint is omitted

@Post('auth/login')
@UseGuards(AuthGuard('local'))
login(@Req() req: AuthedRequest) {
return {
id: req.user.id,
username: req.user.username,
}
}
}

AuthedRequest 类型

细心的小伙伴肯定注意到上面出现了一个陌生的类型:AuthedRequest。这是咱的个人习惯,因为虽然代码能执行到这里,用户肯定经过了认证的,但 TypeScript 不知道,这会导致不能自由使用 user 下的属性,因此咱们需要指定一个具有特定的非空 user 属性的类型,并且在需要认证的接口中使用这个类型。新建 src/types.ts 文件并写入咱们的新类型:

1
2
3
4
5
6
import { User } from '@prisma/client'
import { Request } from 'express'

export type AuthedRequest = Omit<Request, 'user'> & {
user: User
}

生成 JWT

到目前就已经完成了用户登录时认证的功能了,但想要用户后续访问其他接口,还需要为用户颁发一个 JWT。可以通过 @nestjs/jwt 这个包方便地实现这一点,这个包是 Nest 官方提供的将 jsonwebtoken 整合为 Nest 中可以使用的包。

然后在 AuthModule 中注册 JwtModule,以便可以在 AuthService 中注入 JwtService

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { Module } from '@nestjs/common'
import { JwtModule } from '@nestjs/jwt'
import { ConfigModule, ConfigService } from '@nestjs/config'

@Module({
imports: [
JwtModule.registerAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configSerivce: ConfigService) => ({
secret: configSerivce.get('JWT_SECRET'),
signOptions: { expiresIn: '12h' },
}),
}),
],
// controllers and providers are omitted
})
export class AuthModule {}

上面使用了 ConfigModule 来异步注册 JwtModule,主要是因为 JWT_SECRET 需要从环境变量中读取,这个变量不应该被硬编码在代码中,否则别人就能使用同样的 SECRET 伪装成你的系统生成一个 JWT。ConfigModule 会读取项目中的 .env 文件并加载到环境变量中,然后可以通过 ConfigService.get 函数来获取指定的变量。关于 ConfigModule 的详细使用方式可以看官方文档

另外需要在模块中注册才能够使用,不过可以通过选项 isGlobal 让 ConfigModule 全局注册,这样咱们只需要在 AppModule 中注册一次即可,而不是在每一个使用到 ConfigService 的模块中都注册一次:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { resolve } from 'node:path'
import { Module } from '@nestjs/common'
import { ConfigModule } from '@nestjs/config'
import { AppController } from './app.controller'
import { AppService } from './app.service'
import { AuthModule } from './modules/auth/auth.module'

@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
envFilePath: [resolve('../.env')],
}),
AuthModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}

注入 JwtService 并使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { Injectable } from '@nestjs/common'
import { JwtService } from '@nestjs/jwt'
import { User } from '@prisma/client'
import { hash } from 'bcrypt'
import { PrismaService } from '@/prisma.service'

@Injectable()
export class AuthService {
constructor(
private readonly prisma: PrismaService,
private readonly jwtService: JwtService,
) {}

generateJwt(user: User): string {
return this.jwtService.sign({
username: user.username,
id: user.id,
})
}
}

直接调用 JwtService.sign 方法并将要需要保存在 JWT 中的 payload 传入即可,这个函数就会返回一个 JWT string。在 payload 中一般保存可以用来区分用户身份的字段,如用户名或用户 ID,如有需要也可以保存类似身份之类的字段,JWT 只是通过签名确保 payload 没有被第三方篡改,payload 是对任何人公开的,因此不应该保存任何敏感数据。

现在可以生成 JWT 了,再更改之前的 Controller 让其在登录成功的时候返回包含 JWT 的响应。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { Body, Controller, Get, Post, Req, UseGuards } from '@nestjs/common'
import { AuthGuard } from '@nestjs/passport'
import { AuthedRequest } from '@/types'
import { AuthService } from './auth.service'

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

@Post('auth/login')
@UseGuards(AuthGuard('local'))
login(@Req() req: AuthedRequest) {
return {
id: req.user.id,
token: this.authService.generateJwt(req.user),
}
}
}

这时如果用户成功登录,就能得到一个 JWT 了。

实现 JwtStrategy

用户登录成功后,就会使用 JWT 来请求之前访问受限的资源,在本文中,咱们将创建 JwtStrategy,并在 AuthGuard 上应用它来保护接口。

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
import { Injectable } from '@nestjs/common'
import { ConfigService } from '@nestjs/config'
import { PassportStrategy } from '@nestjs/passport'
import { User } from '@prisma/client'
import { ExtractJwt, Strategy, StrategyOptions } from 'passport-jwt'
import { PrismaService } from '@/prisma.service'

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(
private readonly prisma: PrismaService,
configSerivce: ConfigService,
) {
super(<StrategyOptions>{
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: configSerivce.get('JWT_SECRET'),
})
}

async validate(user: User) {
const id = user.id
user = await this.prisma.user.findFirst({ where: { id } })
return {
id: user.id,
username: user.username,
}
}
}

这里的实现套路跟 local 策略基本一样,在构造函数参数中注入 PrismaService,并在构造函数中调用父类构造传入配置选项,这里也使用了 ConfigService 来从环境变量中获取 JWT_SECRET

在 JwtStrategy 中同样有一个 validate 方法,但传入的参数不再是用户名和密码,而是一个对象,这个对象实际上是 JWT 的 payload 对象,但咱这里只需要用到一个 id,就偷懒借用了 @prisma/client 中的类型 User,需要知道实际上并不是这个类型。如果你乐意的话,也可以为上面生成 JWT 的时候的 payload 对象单独创建一个类型,那么这里 validate 传入的就是 payload 的类型。

最后 validate 方法也返回了一个对象,上文中提到过,返回的对象会被设置到 Request.user 这个属性上,因此最好和 local 策略中返回的结构保持一致,否则你就需要处理两种不同的 AuthedRequest 类型。

值得注意的的一个选项是 jwtFromRequest,这个选项决定了 JwtStrategy 会从请求的哪些位置读取 JWT,ExtractJwt 这个对象中包含了许多方法,上面代码中表示从 Authorization 头中取 Bearer Token。

当然,如果你点进去查看 ExtracJwt 的类型,会发现传入的其实就是一个函数,那么也就意味着你完全可以自定义获取 JWT 的逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
export interface JwtFromRequestFunction {
(req: express.Request): string | null;
}

export declare namespace ExtractJwt {
export function fromHeader(header_name: string): JwtFromRequestFunction;
export function fromBodyField(field_name: string): JwtFromRequestFunction;
export function fromUrlQueryParameter(param_name: string): JwtFromRequestFunction;
export function fromAuthHeaderWithScheme(auth_scheme: string): JwtFromRequestFunction;
export function fromAuthHeader(): JwtFromRequestFunction;
export function fromExtractors(extractors: JwtFromRequestFunction[]): JwtFromRequestFunction;
export function fromAuthHeaderAsBearerToken(): JwtFromRequestFunction;
}

使用 JWT 策略保护接口

在需要应用的接口上使用 AuthGuard('jwt') 即可,当然,在使用前也需要在对应的 Module 的 provide 中添加 JwtStrategy(当然也包含它所依赖的 PrismaService)。

1
2
3
4
5
6
7
8
9
10
11
12
13
import { Body, Controller, Get, Post, Req, UseGuards } from '@nestjs/common'
import { AuthGuard } from '@nestjs/passport'
import { AuthedRequest } from '@/types'

@Controller()
export class AuthController {
// constructor and other endpoints are omitted
@Get('user')
@UseGuards(AuthGuard('jwt'))
async getSelfInfo(@Req() req: AuthedRequest) {
return req.user
}
}

这时咱们可以测试一下这个接口,如果没有提供 JWT、JWT 有误或者 JWT 过期,都会返回 Unauthorized,如果 JWT 正确,则会返回用户的信息。

鉴权

除了 AuthGuard,在开发中还有一个很常见的需求就是鉴权,比如某些保护接口只允许管理员调用。业界比较常用的鉴权方案是 RBAC,不过这个有些复杂了,这里只实现一个最基本的 RoleGuard,并不实现完整的 RBAC 方案。

首先在 prisma.schema 中添加一个枚举,并且添加到 User 上:

1
2
3
4
5
6
7
8
9
10
11
enum UserRole {
admin
user
}

model User {
id Int @id @default(autoincrement())
username String @unique
password String
role UserRole @default(user)
}

在复杂一些的系统中可能允许用户拥有多个角色,本文为了关注在 Nest 中的实现,一个用户只允许有一个角色,以简化示例。

更改完 prisma.schema 之后,使用 prisma generate 生成类型。

创建基础装饰器

然后首先创建一个 Decorator

1
2
3
4
import { Reflector } from '@nestjs/core'
import { UserRole } from '@prisma/client'

export const Roles = Reflector.createDecorator<UserRole[]>()

这里使用到了 Nest 提供的 API 创建了一个装饰器,它的作用只是为被装饰的目标添加一些元数据,以便在运行时可以获取。而泛型表示的是这个装饰器接收的参数类型,这里本质上是一个字符串字面量的数组。这个装饰器用于标记被装饰的接口允许哪些角色的用户调用。

这里的字符串字面量就来自于刚刚生成的类型,也就是在 schema 中编写的 UserRole 枚举,比如上面 schema 的枚举示例,这里 UserRole 就可以当作 'admin' | 'user'

实现 RoleGuard

要在 Nest 中实现一个 Guard,只需要实现下面的接口即可:

1
2
3
interface CanActivate {
canActivate(context: ExecutionContext): boolean | Promise<boolean> | Observable<boolean>;
}

返回的值如果是 true(或者它的 Promise 包装或 Observable 包装),则放行请求,否则拦截这个请求并返回 403。

然后实现就很简单了:

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
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'
import { Reflector } from '@nestjs/core'
import { Request } from 'express'
import { PrismaService } from '@/prisma.service'
import { User } from '@prisma/client'
import { Roles } from '../decorators/roles.decorator'

@Injectable()
export class RolesGuard implements CanActivate {
constructor(
private readonly reflactor: Reflector,
private readonly prisma: PrismaService,
) {}

async canActivate(context: ExecutionContext): Promise<boolean> {
const roles = this.reflactor.get(Roles, context.getHandler())
if (!roles) {
return true
}
const request: Request = context.switchToHttp().getRequest()
const id = (request.user as User)?.id
if (!id) {
return false
}
const user = await this.prisma.user.findFirst({ where: { id } })
return roles.includes(user.role)
}
}

首先构造函数中要注入在这里会用到的对象。在 canActivate 函数里,使用了 Reflector 获取上面的 Roles 装饰器在装饰目标上添加的数据,也就是允许调用这个接口的角色字符串的数组。

后面则是从 Request 中获取 user 属性,这也就意味着这个 RolesGuard 需要配合上面的 AuthGuard 使用,否则没有谁会给请求对象上设置 user 属性呀!可能你觉得这很麻烦,但是没关系,这会在稍后解决。

在获取到用户之后,使用 Prisma 去数据库中查询对应 id 的用户,将查询到的用户中的 role 取出来,并查看是否在允许的角色列表中。

整个 RolesGuard 的逻辑还是很简单的,但是关于使用 Prisma 查询数据库来获取用户的角色这一点不同的人会有不同的做法。

RoleGuard 实现细节

由于这个 Guard 会在任何需要验证角色的接口上设置,这将导致这些接口的每一次调用都会查询一次数据库(因为你要知道用户的角色),这可能会导致数据库压力过大。

于是有的人会将 role 这个字段直接放在 JWT 的 payload 里,别忘了 request.user 并不是真正的 user,而是咱们在 JwtStrategy 中返回的那个玩意儿,因此实际上可以直接在 JwtStrategy 返回的对象中塞一个 role 字段,这样在 RoleGuard 中则直接通过 (request.user as User)?.role 就可以拿到用户的角色了,这样免去了数据库查询。

也可以考虑使用 Redis 缓存数据库查询的结果,当然这个方案又引入了新的复杂性,如果不是一定要查询数据库,通常不建议使用这个方案(如果你的 Nest 项目已经在集成 Redis 了也可以考虑一下)。

这里就直接使用最简单粗暴的直接查询数据库的实现了,才不会告诉你们这是为了方便一边写一边在数据库里直接修改用户角色来调试呢。

使用 Guard

实现完上面的部分,就可以在接口中使用了,但是目前的使用方式还有一点点复杂:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { Controller, Get, Param, UseGuards } from '@nestjs/common'
import { AuthGuard } from '@nestjs/passport'
import { RolesGuard } from '@/common/guard/roles.guard'
import { Roles } from '@/common/decorators/roles.decorator'

@Controller()
export class AuthController {
// other endpoints and constructor are omitted
@Get('user/:id')
@UseGuards(AuthGuard('jwt'), RolesGuard)
@Roles(['admin'])
async getUserInfo(@Param('id') id: string) {
return this.authService.getUserInfo(+id)
}
}

整合装饰器

在上面的例子中,咱们的需求只是要这个接口只能被管理员请求,但是却写了一长串无关的东西 @UseGuards(AuthGuard('jwt'), RolesGuard)

既然咱们要限定管理员才能请求,那自然用户也必须得是一个认证过的用户,AuthGuard('jwt') 便是多余的了;RolesGuardRoles 也存在语义重复,目前的写法很啰嗦。咱们可以使用 Nest 提供的 API 将这些装饰器整合在一起,使代码更加简洁、可读性更高。

首先要理解两点:

  1. 装饰器是一个包含特殊参数和返回值的函数。
  2. 在使用装饰器时,@ 的后面是一个表达式,表达式的结果必须为一个装饰器。

咱们可以通过定义一个函数 Auth,返回符合装饰器规则的返回值,这样就可以在目标上使用类似 @Auth() 这样的语法来使用了。好在 Nest 提供了一个 applyDecorators 函数方便咱们将多个装饰器整合到一起,下面就直接看看 Auth 函数的实现吧。

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 { UseGuards, applyDecorators } from '@nestjs/common'
import { UserRole } from '@prisma/client'
import { AuthGuard } from '@nestjs/passport'
import { RolesGuard } from '../guard/roles.guard'
import { Roles } from './roles.decorator'

export interface AuthDecoratorOption {
roles?: UserRole[]
}

export function Auth(option?: AuthDecoratorOption) {
option ??= {}
const decorators: (ClassDecorator | PropertyDecorator | MethodDecorator)[] = []
decorators.push(UseGuards(AuthGuard('jwt')))

const { roles } = option
if (roles) {
decorators.push(
UseGuards(RolesGuard),
Roles(roles),
)
}
return applyDecorators(...decorators)
}

对于直接使用 @Auth()decorators 中就只有一个 AuthGuard('jwt'),这时只验证用户是否登录。如果在参数中设置了角色,才会在 decorators 中添加 RolesGuardRoles 这两个装饰器。最后使用 Nest 提供的 applyDecorators 函数将上面数组中的装饰器整合到一起并返回。

实现了上面的函数后就可以这样使用了:

1
2
3
4
5
6
7
8
9
10
11
12
13
import { Controller, Get, Param } from '@nestjs/common'
import { Auth } from '@/common/decorators/auth.decorator'
import { AuthService } from './auth.service'

@Controller()
export class AuthController {
// other endpoints and constructor are omitted
@Get('user/:id')
@Auth({ roles: ['admin'] })
async getUserInfo(@Param('id') id: string) {
return this.authService.getUserInfo(+id)
}
}

这里的效果和上面的例子是完全相同的,但是使用起来更加方便,没有赘余的语义。

参考

  1. Prisma | NestJS - A progressive Node.js framework
  2. Guards | NestJS - A progressive Node.js framework
  3. passport-local
  4. passport-jwt