第八章 Nest ArgumentHost 和 ExecutionContext

在NestJS中,ArgumentHost和ExecutionContext是两个常用的类,用于处理请求和返回的上下文信息。
ArgumentHost类包含了请求的上下文信息,它提供了一个统一的方式来访问请求参数、头部、查询参数等。可以通过在处理器方法中使用@Args()装饰器来获取ArgumentHost实例,从而获取请求的参数和头部信息。
ExecutionContext类包含了处理器方法的上下文信息,它提供了一系列方法来获取请求的原始对象、路由处理器、控制器实例等。可以通过在拦截器、管道和异常过滤器中使用ExecutionContext类来访问这些上下文信息。

Nest 支持创建 HTTP 服务、WebSocket 服务,基于 TCP 通信的微服务
http 服务可以拿到 request、response 对象,ws 服务没有request、response 对象
但是不同的服务都可能需要Guard、Interceptor、Exception Filter 功能
所以我们可以使用 ArgumentHost 和 ExecutionContext 来实现 让 Guard、Interceptor、Exception Filter 跨多种上下文复用

首先我们创建一个项目:

nest new argument -p npm

1717300676558.png
创建 filter:

nest g filter test --flat --no-spec

1717300811270.png
1717321766057.png
接着我们创建一个异常类 待会会用到:
创建 TestException.ts:

export class TestException {
    constructor(public message1: string, public message2: string) {

    }
}

1717322325901.png
修改 test.filter.ts: 在@Catch 装饰器里声明filter 处理该异常

import { ArgumentsHost, Catch, ExceptionFilter } from '@nestjs/common';
import { TestException } from './TestException';

@Catch(TestException)
export class TestFilter<T> implements ExceptionFilter {
  catch(exception: TestException, host: ArgumentsHost) {
    console.log(exception, host);
  }
}

1717322863568.png
在Controller 里使用:
1717323320992.png
我们在接口里面抛出了一个 TestException 类型的异常
接着我们访问 http://localhost:3000/ 可以看到控制台打印出了一些信息
可以看到 打印出的信息就是 test.filter.ts 里的console.log 打印出来的信息
第一个圈住的数据就是 exception
后面的数据就是 host
1717323569157.png
host.getArgs 方法就是取出当前上下文的 reqeust、response、next 参数
修改 test.filter.ts:

import { ArgumentsHost, Catch, ExceptionFilter } from '@nestjs/common';
import { TestException } from './TestException';
import { Response } from 'express';

@Catch(TestException)
export class TestFilter<T> implements ExceptionFilter {
  catch(exception: TestException, host: ArgumentsHost) {
    if (host.getType() === 'http') {
      const ctx = host.switchToHttp();
      const response = ctx.getResponse<Response>();
      const request = ctx.getRequest<Request>();

      response
        .status(500)
        .json({
          message1: exception.message1,
          message2: exception.message2,
        });
    } else if (host.getType() === 'ws') {

    } else if (host.getType() === 'rpc') {

    }
  }
}

刷新页面,就可以看到 filter 返回的响应
1717324199177.png
所以 ArgumentHost 是用于切换 http、ws、rpc 等上下文类型的,可以根据上下文类型取到对应的 argument

接着创建一个guard 看看在guard 中是怎么样的

nest g guard test --no-spec --flat

1717324404251.png
可以看到生成里的context 的类型就是为ExecutionContext
1717324535813.png
可以看到context 有下面这些方法:
1717324764586.png
可以看到源码里 ExecutionContext 是 ArgumentHost 的子类 并且扩展了 getClass、getHandler 方法
1717324871873.png
我们在controller 里修改代码如下:
新增 @UseGuards(TestGuard)

import { Controller, Get, UseFilters, UseGuards } from '@nestjs/common';
import { AppService } from './app.service';
import { TestFilter } from './test.filter';
import { TestException } from './TestException';
import { TestGuard } from './test.guard';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) { }

  @Get()
  @UseFilters(TestFilter)
  @UseGuards(TestGuard)
  getHello(): string {
    throw new TestException('测试', '异常')
    return this.appService.getHello();
  }
}

接着我们修改 test.guard.ts 的代码
打印一下 getClass、getHandler 方法

import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Observable } from 'rxjs';

@Injectable()
export class TestGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    console.log(context.getClass(), context.getHandler());

    return true;
  }
}

接着访问 http://localhost:3000/ 可以看到控制台打印了
1717325727266.png
为什么 ExecutionContext 里需要多出这俩方法呢?
因为 Guard、Interceptor 的逻辑可能要根据目标 class、handler 有没有某些装饰而决定怎么处理
.举个例子:
先创建一个role.ts

export enum Role {
    Admin = 'admin',
    User = 'user',
    Guest = 'guest'
}

1717325942578.png
接着定义一个装饰器 role.decorator.ts 作用是往修饰的目标上添加 roles 的 metadata

import { SetMetadata } from "@nestjs/common";
import { Role } from "./role";

export const Roles = (...roles: Role[]) => SetMetadata('roles', roles)

1717326167021.png
我们去controller里进行使用: 给这个 handler 添加了一个 roles 为 admin 的metadata。

import { Controller, Get, UseFilters, UseGuards } from '@nestjs/common';
import { AppService } from './app.service';
import { TestFilter } from './test.filter';
import { TestException } from './TestException';
import { TestGuard } from './test.guard';
import { Roles } from './role.decorator';
import { Role } from './role';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) { }

  @Get()
  @UseFilters(TestFilter)
  @UseGuards(TestGuard)
  @Roles(Role.User)
  getHello(): string {
    throw new TestException('测试', '异常')
    return this.appService.getHello();
  }
}

1717326454609.png
最后在 Guard 里 可以根据 metadata 决定是否放行:

import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Observable } from 'rxjs';
import { Role } from './role';

/**
 * `TestGuard` 类实现了 NestJS 的 `CanActivate` 接口,用于授权检查。
 * 它通过检查请求处理器上标注的权限角色,来决定当前请求是否被允许激活。
 */
@Injectable()
export class TestGuard implements CanActivate {
  /**
   * 构造函数初始化 `TestGuard`。
   * @param reflector `Reflector` 实例,用于获取请求处理器上的元数据。
   */
  constructor(private reflector: Reflector) {}

  /**
   * `canActivate` 方法决定当前请求是否应该被激活。
   * 它首先检查请求处理器是否标注了需要特定角色的元数据。
   * 如果没有标注任何角色要求,则认为请求是激活的。
   * 如果标注了角色要求,则检查请求中的用户是否拥有这些角色。
   * @param context `ExecutionContext` 实例,用于获取当前的执行上下文,包括请求和处理程序信息。
   * @returns `boolean` 或 `Promise<boolean>` 或 `Observable<boolean>`,表示请求是否应该被激活。
   */
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    // 从请求处理器上获取需要的角色列表
    const requiredRoles = this.reflector.get<Role[]>('roles', context.getHandler());
    
    // 如果没有指定角色要求,则自动激活请求
    if (!requiredRoles) {
      return true;
    }

    // 切换到HTTP上下文并获取请求对象
    const { user } = context.switchToHttp().getRequest();
    // 检查用户角色是否包含在所需角色列表中
    return requiredRoles.some((role) => user && user.roles?.includes(role));
  }
}

1717326789624.png
接着我们访问 http://localhost:3000/
1717326832384.png
说明 Guard 生效 就是 Guard 里的 ExecutionContext 参数的用法

interceptor 也是同样的,我们创建一个interceptor:

nest g interceptor test --no-spec --flat

1717327105983.png
可以看到也是 ExecutionContext 类型
1717327268535.png

  • 16
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

枫ゞ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值