大概有五种常用的装饰器类型(类装饰器、方法装饰器、访问符装饰器、属性装饰器和参数装饰器)如下所示。
类装饰器用于类声明。这里的例子是一个简单的日志记录装饰器,它在类被实例化时记录信息。
function LogClass(target: Function) {
// 保存原始构造函数的引用
const original = target;
// 生成一个新的构造函数,它会替代原始的构造函数
function construct(constructor, args) {
console.log("Created instance of", constructor.name);
return new constructor(...args);
}
// 用新的构造函数替换原始构造函数
const newConstructor: any = function (...args) {
return construct(original, args);
};
// 保证新的构造函数与原始构造函数的原型相同
newConstructor.prototype = original.prototype;
return newConstructor;
}
@LogClass
class MyClass {
constructor(public name: string) {}
}
方法装饰器用于方法的属性描述符。这个例子中的装饰器会记录方法的调用和返回值。
function LogMethod(target: any, propertyName: string, propertyDescriptor: PropertyDescriptor): PropertyDescriptor {
const originalMethod = propertyDescriptor.value;
propertyDescriptor.value = function (...args: any[]) {
console.log(`Calling "${propertyName}" with arguments:`, args);
const result = originalMethod.apply(this, args);
console.log(`"${propertyName}" returned`, result);
return result;
};
return propertyDescriptor;
}
class MyClass {
@LogMethod
myMethod(arg: string) {
return `Hello, ${arg}`;
}
}
UseGuards
是 NestJS 中的一个装饰器,用于在控制器或路由处理程序级别应用守卫(Guards)。守卫是 NestJS 中负责处理认证和授权的特殊类,它们实现了 CanActivate
接口。在 NestJS 中,守卫可以用来决定某个请求是否被允许执行下去。
UseGuards
装饰器UseGuards
装饰器可以用于单个路由处理程序或整个控制器。你可以将一个或多个守卫作为参数传递给 UseGuards
。当请求到达被 UseGuards
装饰的路由或控制器时,传递给 UseGuards
的守卫将会依次被调用。
下面是一个简单的示例,展示了如何在 NestJS 中使用 UseGuards
装饰器:
import { Controller, Get, UseGuards } from '@nestjs/common';
import { AuthGuard } from './auth.guard';
@Controller('items')
export class ItemsController {
@Get()
@UseGuards(AuthGuard)
findAll() {
// 你的业务逻辑
}
}
在这个例子中,ItemsController
的 findAll
方法被 UseGuards
装饰器修饰,传递了 AuthGuard
作为参数。这意味着,每当有请求调用 findAll
方法时,AuthGuard
会首先被执行。如果 AuthGuard
允许请求继续执行,那么 findAll
方法将会处理该请求;如果 AuthGuard
拒绝请求,则请求将会被中断。
你可以创建自定义守卫来处理特定的认证和授权逻辑。自定义守卫需要实现 CanActivate
接口:
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
// 你的认证和授权逻辑
return true; // 如果认证通过,则返回 true
}
}
export function UseGuards(
...guards: (CanActivate | Function)[]
): MethodDecorator & ClassDecorator {
return (
target: any,
key?: string | symbol,
descriptor?: TypedPropertyDescriptor<any>,
) => {
// 是否是函数或者类,或者是有canActivate方法
const isGuardValid = <T extends Function | Record<string, any>>(guard: T) =>
guard &&
(isFunction(guard) ||
isFunction((guard as Record<string, any>).canActivate));
// 执行上述校验函数
if (descriptor) {
validateEach(
target.constructor,
guards,
isGuardValid,
'@UseGuards',
'guard',
);
// 存储当前函数的guards列表
extendArrayMetadata(GUARDS_METADATA, guards, descriptor.value);
return descriptor;
}
validateEach(target, guards, isGuardValid, '@UseGuards', 'guard');
extendArrayMetadata(GUARDS_METADATA, guards, target);
return target;
};
}
使用 UseGuards
装饰器是 NestJS 中处理路由保护的一种非常强大且灵活的方式。它允许你在控制器或路由级别轻松地应用认证和授权逻辑。通过自定义守卫,你可以实现复杂的权限管理逻辑,从而使你的应用更加安全和健壮。
访问符装饰器用于类的属性的访问器。以下是一个记录属性获取和设置的例子。
function LogAccessor(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
const originalGetter = descriptor.get;
const originalSetter = descriptor.set;
descriptor.get = function () {
console.log(`Getting ${propertyKey}`);
return originalGetter.apply(this);
};
descriptor.set = function (value: any) {
console.log(`Setting ${propertyKey} to ${value}`);
originalSetter.apply(this, [value]);
};
}
class MyClass {
private _name: string;
@LogAccessor
get name() {
return this._name;
}
set name(value: string) {
this._name = value;
}
}
属性装饰器用于类的属性。这里的例子是为属性添加元数据。
function DefaultValue(value: string) {
return function (target: any, propertyKey: string) {
target[propertyKey] = value;
};
}
class MyClass {
@DefaultValue("default value")
public property: string;
}
参数装饰器用于类的方法参数。这个例子中的装饰器会记录参数的信息。
function LogParameter(target: any, propertyKey: string, parameterIndex: number) {
const methodName = propertyKey || target.constructor.name;
console.log(`Parameter in ${methodName} at position ${parameterIndex} has been decorated`);
}
class MyClass {
myMethod(@LogParameter param1: string, @LogParameter param2: number) {
console.log(param1, param2);
}
}
@Body()
是 NestJS 中的装饰器,用于从 HTTP 请求的正文(body)中提取数据。它通常与控制器的路由处理方法一起使用,以获取请求正文中的数据并将其传递给方法的参数。
以下是@Body()
的一些常见用法和示例:
@Controller('example')
export class ExampleController {
@Post()
async create(@Body() data: any) {
// 在这里使用data,它包含了请求正文中的数据
return 'Data received: ' + JSON.stringify(data);
}
}
在上述示例中,@Body()
装饰器被应用于 create
方法的参数 data
上,它会自动从请求正文中提取数据,并将其作为参数传递给 create
方法。
通常,你可以使用 DTO 类来指定请求正文的数据结构,然后在路由处理方法中使用 @Body()
来自动将数据映射到DTO中:
export class CreateUserDto {
username: string;
email: string;
}
@Controller('users')
export class UsersController {
@Post()
async createUser(@Body() createUserDto: CreateUserDto) {
// 在这里使用createUserDto,它包含了请求正文中的数据
return 'User created: ' + JSON.stringify(createUserDto);
}
}
在上面的示例中,@Body()
装饰器自动将请求正文中的数据映射到 createUserDto
对象中。
你还可以使用 @Body('fieldName')
来指定要提取的正文中的特定字段:
@Controller('example')
export class ExampleController {
@Post()
async create(@Body('name') name: string, @Body('age') age: number) {
// 在这里使用name和age,它们分别从请求正文中的"name"和"age"字段提取
return `Name: ${name}, Age: ${age}`;
}
}
总之,@Body()
装饰器在 NestJS 中用于从请求正文中提取数据,可以用于接收和处理客户端发送的数据,通常与路由处理方法一起使用。
export function Body(
property?: string | (Type<PipeTransform> | PipeTransform),
...pipes: (Type<PipeTransform> | PipeTransform)[]
): ParameterDecorator {
return createPipesRouteParamDecorator(RouteParamtypes.BODY)(
property,
...pipes,
);
}
相当于执行createPipesRouteParamDecorator
并传入两波参数。@Body
其实就是记录了当前被修饰的函数的参数位置以及值。在执行该函数的时候又会被取出来。
export function Body(
property?: string | (Type<PipeTransform> | PipeTransform),
...pipes: (Type<PipeTransform> | PipeTransform)[]
): ParameterDecorator {
// RouteParamtypes.BODY固定值3,
return createPipesRouteParamDecorator(RouteParamtypes.BODY)(
property,
...pipes,
);
}
const createPipesRouteParamDecorator =
(paramtype: RouteParamtypes) =>
(
data?: any,
...pipes: (Type<PipeTransform> | PipeTransform)[]
): ParameterDecorator =>
(target, key, index) => {
// 获取当前函数是否已经被@Body修饰,如果有则取出当前数组,比如register(@Body(ValidationPipe) createUserDto: CreateUserDto,@Body('123') createUserDto2: CreateUserDto) 这个函数有两个@Body,此时args就会有多个值
const args =
Reflect.getMetadata(ROUTE_ARGS_METADATA, target.constructor, key) || {};
// 第一个参数是null或者undefined或者string
const hasParamData = isNil(data) || isString(data);
// 判断是否有第一个参数
const paramData = hasParamData ? data : undefined;
// 合并参数和pipes
const paramPipes = hasParamData ? pipes : [data, ...pipes];
// 保存当前被修饰的函数参数以及参数位置
Reflect.defineMetadata(
ROUTE_ARGS_METADATA,
assignMetadata(args, paramtype, index, paramData, ...paramPipes),
target.constructor,
key,
);
};
以上就是五种类型的装饰器的具体例子。装饰器是 TypeScript 和 NestJS 中的高级特性,通过它们可以在不改变类、方法或属性的行为的情况下添加额外的逻辑。使用装饰器可以大幅提高代码的复用性和可维护性。