nestjs之常用装饰器以及原理解析

发布时间:2024年01月23日

大概有五种常用的装饰器类型(类装饰器、方法装饰器、访问符装饰器、属性装饰器和参数装饰器)如下所示。

1. 类装饰器

类装饰器用于类声明。这里的例子是一个简单的日志记录装饰器,它在类被实例化时记录信息。

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) {}
}

2. 方法装饰器

方法装饰器用于方法的属性描述符。这个例子中的装饰器会记录方法的调用和返回值。

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

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() {
        // 你的业务逻辑
    }
}

在这个例子中,ItemsControllerfindAll 方法被 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 中处理路由保护的一种非常强大且灵活的方式。它允许你在控制器或路由级别轻松地应用认证和授权逻辑。通过自定义守卫,你可以实现复杂的权限管理逻辑,从而使你的应用更加安全和健壮。

3. 访问符装饰器

访问符装饰器用于类的属性的访问器。以下是一个记录属性获取和设置的例子。

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;
    }
}

4. 属性装饰器

属性装饰器用于类的属性。这里的例子是为属性添加元数据。

function DefaultValue(value: string) {
    return function (target: any, propertyKey: string) {
        target[propertyKey] = value;
    };
}

class MyClass {
    @DefaultValue("default value")
    public property: string;
}

5. 参数装饰器

参数装饰器用于类的方法参数。这个例子中的装饰器会记录参数的信息。

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

@Body() 是 NestJS 中的装饰器,用于从 HTTP 请求的正文(body)中提取数据。它通常与控制器的路由处理方法一起使用,以获取请求正文中的数据并将其传递给方法的参数。

以下是@Body() 的一些常见用法和示例:

  1. 基本用法
@Controller('example')
export class ExampleController {
  @Post()
  async create(@Body() data: any) {
    // 在这里使用data,它包含了请求正文中的数据
    return 'Data received: ' + JSON.stringify(data);
  }
}

在上述示例中,@Body() 装饰器被应用于 create 方法的参数 data 上,它会自动从请求正文中提取数据,并将其作为参数传递给 create 方法。

  1. 指定DTO(数据传输对象)

通常,你可以使用 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 对象中。

  1. 指定特定字段

你还可以使用 @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 中的高级特性,通过它们可以在不改变类、方法或属性的行为的情况下添加额外的逻辑。使用装饰器可以大幅提高代码的复用性和可维护性。

文章来源:https://blog.csdn.net/qq_35094120/article/details/135764789
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。