装饰器 (Decorator) 是一种设计模式,有些程序语言会直接将此设计模式实作出来,TypeScript 与 JavaScript 在近年也添加了此功能,而 Nest 将装饰器发挥到淋漓尽致,透过装饰器就可以很轻易地套用功能,不论是针对开发速度、易读性等都很有帮助。
Nest 有提供许多装饰器,但在某些情况下内建的装饰器可能没办法很有效地解决问题,於是 Nest 提供了 自订装饰器 (Custom Decorator) 的功能,其分成下方三种:
有些资料可能无法透过内建装饰器直接取得,比如:授权认证机制所带入的资料。如果对 Express 不陌生的话应该看过下方的写法,为什麽会有自订义的资料放在请求物件中呢?主要是透过 Middleware 进行扩充,在授权认证机制是非常常见的:
const user = req.user;
试想,如果要透过内建装饰器要如何取得该资料?必需要使用 @Request
装饰器先取得请求物件,再从请求物件中提取,这样的方式并不是特别理想,於是可以自行设计参数装饰器来取得,而 Decorator 可以透过 CLI 产生:
$ nest generate decorator <DECORATOR_NAME>
注意:
<DECORATOR_NAME>
可以含有路径,如:decorators/user
,这样就会在src
资料夹下建立该路径并含有 Decorator。
这边我建立一个 User
在 decorators
资料夹下:
$ nest generate decorator decorators/user
在 src
底下会看见一个名为 decorators
的资料夹,里面有 user.decorator.ts
:
建立出来的装饰器骨架如下,会发现是一个回传 SetMetadata
的函式:
import { SetMetadata } from '@nestjs/common';
export const User = (...args: string[]) => SetMetadata('user', args);
不过参数装饰器并不是使用 SetMetadata
,而是使用 createParamDecorator
,透过 createParamDecorator
来产生参数装饰器,并使用 Callback 里面的 ExecutionContext
来取得请求物件再从中取得要取出的资料。下方为修改後的 user.decorator.ts
:
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const User = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.user;
},
);
在设计完 User
装饰器後,要设计一个 Middleware 来添加 user
到请求物件中。透过 CLI 产生 AddUserMiddleware
:
$ nest generate middleware middlewares/add-user
将 add-user.middleware.ts
的内容修改如下:
import { Injectable, NestMiddleware } from '@nestjs/common';
@Injectable()
export class AddUserMiddleware implements NestMiddleware {
use(req: any, res: any, next: () => void) {
req.user = { name: 'HAO' };
next();
}
}
接着,在 AppModule
中套用 AddUserMiddleware
,修改 app.module.ts
如下:
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { AddUserMiddleware } from './middlewares/add-user.middleware';
@Module({
imports: [],
controllers: [AppController],
providers: [
AppService
]
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(AddUserMiddleware).forRoutes('');
}
}
这样就能够将 user
添加到请求物件里了,接下来就要使用 User
装饰器来将 user
内容取出并返回给客户端,这里我们修改 app.controller.ts
:
import { Controller, Get } from '@nestjs/common';
import { User } from './decorators/user.decorator';
@Controller()
export class AppController {
constructor() {}
@Get()
getHello(@User() user: any): string {
return user;
}
}
透过浏览器查看 http://localhost:3000 会看到下方结果,表示有成功将 user
取出:
那如果想要像 @Param('id')
一样只取出特定资料的话该如何设计呢?createParamDecorator
中的 Callback 里面除了 ExecutionContext
之外,还有一个 data
,这个 data
事实上就是带到装饰器中的参数,所以要运用 data
来取出 user
中的资料。这里修改一下 user.decorator.ts
:
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const User = createParamDecorator(
(data: string, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
const user = request.user;
return data ? user[data] : user;
},
);
修改 app.controller.ts
来指定取出 user
中的 name
:
import { Controller, Get } from '@nestjs/common';
import { User } from './decorators/user.decorator';
@Controller()
export class AppController {
constructor() {}
@Get()
getHello(@User('name') name: string): string {
return name;
}
}
透过浏览器查看 http://localhost:3000 会得到下方结果:
有时候需要针对某个方法设置特定的 Metadata,比如:角色权限控管,透过设置 Metadata 来表示该方法仅能由特定角色来存取。这里来实作一个简单的角色权限控管功能,透过 CLI 产生 Roles
:
$ nest generate decorator decoractors/roles
建立出来的骨架即为自订 Metadata 装饰器的格式,SetMetadata
即产生自订 Metadata 的装饰器:
import { SetMetadata } from '@nestjs/common';
export const Roles = (...args: string[]) => SetMetadata('roles', args);
这个范例的意思为:Roles
即为装饰器,透过 @Roles('admin')
将 admin
字串带入装饰器中,SetMetadata
指定 roles
为 key
值,并令 ['admin']
为其值,最後设置为 Metadata。
接着来设置一个 RoleGuard
模拟角色权限管理的效果:
$ nest generate guards/role
为了要取得 Metadata 的内容,必须透过 Nest 提供的 Reflector
来取得,其引入方式即透过依赖注入,并呼叫 get(metadataKey: any, target: Function | Type<any>)
来取得指定的 Metadata,其中 metadataKey
即要指定的 Metadata Key,而 target
则为装饰器装饰之目标,经常会使用 ExecutionContext
里面的 getHandler
来作为 target
的值。将 role.guard.ts
的内容修改如下:
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Observable } from 'rxjs';
@Injectable()
export class RoleGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {}
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const roles = this.reflector.get<string[]>('roles', context.getHandler());
const request = context.switchToHttp().getRequest();
const user = request.user;
return this.matchRoles(roles, user.roles);
}
private matchRoles(resources: string[], target: string[]): boolean {
return !!resources.find(x => target.find(y => y === x));
}
}
在设置好 Roles
与 RoleGuard
之後,就来调整一下 AddUserMiddleware
的内容,添加角色 staff
到 user
里面:
import { Injectable, NestMiddleware } from '@nestjs/common';
@Injectable()
export class AddUserMiddleware implements NestMiddleware {
use(req: any, res: any, next: () => void) {
req.user = { name: 'HAO', roles: ['staff'] };
next();
}
}
最後,调整一下 app.controller.ts
的内容,指定 getHello
只有 admin
身份可以存取:
import { Controller, Get, UseGuards } from '@nestjs/common';
import { User } from './decorators/user.decorator';
import { Roles } from './decorators/roles.decorator';
import { RoleGuard } from './guards/role.guard';
@Controller()
export class AppController {
constructor() {}
@UseGuards(RoleGuard)
@Roles('admin')
@Get()
getHello(@User('name') name: string): string {
return name;
}
}
透过浏览器查看 http://localhost:3000 会发现无法存取:
有些装饰器它们之间是有相关的,比如:授权验证需要使用 Guard、添加自订 Metadata 等,每次要实作都要重复将这些装饰器带入,会使得重复性的操作变多,於是 Nest 还有设计一个叫 applyDecorators
的函式来将多个装饰器整合成一个装饰器,每当要实作该功能就只要带入整合装饰器即可。下方会简单模拟授权验证的整合装饰器,先透过 CLI 产生 Auth
Decorator:
$ nest generate decorators/auth
接着,Auth
需包含 UseGuards
、Roles
这两个装饰器的功能,在设计整合装饰器之前需要先透过 CLI 产生 AuthGuard
以便後续使用:
$ nest generate guard guards/auth
注意:本节主要是将焦点放在整合功能上,所以这里就不特别去改
AuthGuard
的内容了,让它们回传true
即可。
产生完 AuthGuard
之後,来修改一下 auth.decorator.ts
的内容,透过 applyDecorators
将 UseGuards
、Roles
整合成一个装饰器:
import { applyDecorators, UseGuards } from '@nestjs/common';
import { RoleGuard } from '../guards/role.guard';
import { AuthGuard } from '../guards/auth.guard';
import { Roles } from './roles.decorator';
export const Auth = (...roles: string[]) => applyDecorators(
Roles(...roles),
UseGuards(AuthGuard, RoleGuard)
);
最後来调整一下 app.controller.ts
,套用 Auth
装饰器并指定 getHello
只有 staff
可以存取:
import { Controller, Get } from '@nestjs/common';
import { Auth } from './decorators/auth.decorator';
import { User } from './decorators/user.decorator';
@Controller()
export class AppController {
constructor() {}
@Auth('staff')
@Get()
getHello(@User('name') name: string): string {
return name;
}
}
透过浏览器查看 http://localhost:3000 会得到下方结果:
Custom Decorator 可以补足 Nest 内建装饰器不足的部分,且具有相当大的弹性,是非常实用的功能。这里附上今天的懒人包:
createParamDecorator
来产生。SetMetadata
的扩展。applyDecorators
来产生。Nest 各元件的基本功能与使用方式皆介绍完毕,下一篇开始将会进入到 进阶功能 单元,敬请期待!
DAY3 MongoDB 连线与 IDE MongoDB 的连线方式主要有三种,分别是: legac...
EXCEL VBA SQL 将资料 汇出 到dBASEIII .dbf档案 PS. : Proper...
##让我们来学习演算法吧,此为阅读[https://pjchender.blogspot.com/2...
不难发现,问题在於该用什麽标准来做决定呢?梁晓声曾讲过,友谊,好比一瓶酒,封存的时间越长,价值则越高...
TCP 是一种要求资料正确性的传输方式, 这表示它需要一些特殊机制, 来确保传输的数据不会出错。 其...