nestjs-执行上下文

72 阅读3分钟

前言

Nest提供了几个实用的类,帮助您轻松编写跨多个应用程序上下文(例如基于Nest HTTP服务器的、微服务和WebSockets应用程序上下文)运行的应用程序。这些实用工具提供有关当前执行上下文的信息,可用于构建通用的守卫、过滤器和拦截器,可以适用于广泛的控制器、方法和执行上下文。

在本章中,我们将介绍两个这样的类:ArgumentsHostExecutionContext

ps:细心的话可以发现,之前我们的守卫、拦截器、自定义装饰器都有用到,这里面就主要了解一下他的使用

执行上下文

ArgumentsHost 类提供了一些方法,用于检索传递给处理程序的参数。它允许选择适当的上下文(例如HTTP、RPC(微服务)或 WebSockets)来从中检索参数

我们会在很多地方看到他的实例,这也是 nest 给我们创建好的实例,这就是我们的当前应用上下文,例如: CanActivateExceptionFiltercreateParamDecorator 也就是我们前面介绍的 guard校验http拦截自定义装饰器 等等

ps: ExecutionContext 它继承自 ArgumentsHost 哈

@Injectable()
export class UserGuard implements CanActivate {
  constructor(
    private reflector: Reflector,
  ) { }

  canActivate(
    context: ExecutionContext,
  ): boolean { }
  
}


@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp(); // 获取请求上下文
    const response = ctx.getResponse(); // 获取请求上下文中的 response对象
    const status = exception.getStatus(); // 获取异常状态码
  }
}


export const User = createParamDecorator(
    (data: string | undefined | null, ctx: ExecutionContext) => {
        const request = ctx.switchToHttp().getRequest();
		const user = request.user
        return data ? user?.[data] : user
    },
);

其有三个方法,我们用的最多的是 http,另外两个是看自己需要获取

/**
 * Switch context to RPC.
 */
switchToRpc(): RpcArgumentsHost;
/**
 * Switch context to HTTP.
 */
switchToHttp(): HttpArgumentsHost;
/**
 * Switch context to WebSockets.
 */
switchToWs(): WsArgumentsHost;

其也可以帮助我们更好切换到我们需要的上下文,例如:获取我们 http 请求信息

const ctx = host.switchToHttp();
const request = ctx.getRequest<Request>();
//request.headers,headers.token 是不是很熟悉
const response = ctx.getResponse<Response>();

行上下文类 ExecutionContext 能够提供有关当前执行过程的更多详细信息,可以获取我们的类和方法

export interface ExecutionContext extends ArgumentsHost {
  /**
   * Returns the type of the controller class which the current handler belongs to.
   */
  getClass<T>(): Type<T>;
  /**
   * Returns a reference to the handler (method) that will be invoked next in the
   * request pipeline.
   */
  getHandler(): Function;
}

我们在通过 @SetMetadata() 装饰器 + Reflector 反射 直接获取到我们设置的元数据,也就是我们前面讲过的授权相关

//设置一个权限装饰器
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);

//使用权限装饰器
@Post()
@Roles('admin')
async create(@Body() createCatDto: CreateCatDto) {
  this.catsService.create(createCatDto);
}

//我们在guard汇总使用
@Injectable()
export class UserGuard implements CanActivate {
  constructor(
    private reflector: Reflector,
  ) { }

  canActivate(
    context: ExecutionContext,
  ): boolean {
    //这个getHandler可以帮助我们获取路由函数的引用,但是不太符合我们的要求
    //const roles = this.reflector.get<string[]>('roles', context.getHandler());
    //我们可以通过 getClass 获取我们设置的元数据,以便于我们更好使用权限
    const roles = this.reflector.get<string[]>('roles', context.getClass());
    //获得权限 'admin'
  }
}

使用 getAllAndOverride 合并我们 controller 和 内部方法设置的元数据(一般都用这个),如下所示

const roles = this.reflector.getAllAndOverride<string[]>('roles', [
  context.getHandler(),
  context.getClass(),
]);

使用如下所示

//设置一个权限装饰器
export const Roles = (...roles: string[]) => SetMetadata('roles', roles);

//控制器中使用权限装饰器,同时在controller和内部方法上
@Roles('user')
@Controller('user')
export class UserController {
    @Post()
    @Roles('admin')
    async create(@Body() createCatDto: CreateCatDto) {
      this.catsService.create(createCatDto);
    }
}

//我们在guard汇总使用
@Injectable()
export class UserGuard implements CanActivate {
  constructor(
    private reflector: Reflector,
  ) { }

  canActivate(
    context: ExecutionContext,
  ): boolean {
    //获取混合后的权限['user','admin]
    const roles = this.reflector.getAllAndOverride<string[]>('roles', [
      context.getHandler(),
      context.getClass(),
    ]);
  }
}