有没有办法从 NestJS 应用程序收集所有方法及其路径?

Is there a way to collect all methods and their paths from NestJS application?

我需要写中间件来处理请求,但是应该排除一些路径。我不想手动硬编码所有这些,所以我有一个想法:

创建特殊装饰器,它将标记要排除的方法,如下所示:

import { ReflectMetadata } from '@nestjs/common';
export const Exclude = () =>
  ReflectMetadata('exclude', 'true');

有没有办法在创建 NestJS 应用程序后以某种方式递归地获取所有方法,用这个装饰器注释,自动添加它们的路径以排除在我的中间件中?

所以...请自便。

在深入研究 NestJS 资源后,我找到了一种方法,这里是有兴趣的人的方向:

import * as pathToRegexp from 'path-to-regexp';
import { INestApplication, RequestMethod } from '@nestjs/common';
import { NestContainer } from '@nestjs/core/injector/container';
import { MetadataScanner } from '@nestjs/core/metadata-scanner';
import { PATH_METADATA, MODULE_PATH, METHOD_METADATA } from '@nestjs/common/constants';

const trimSlashes = (str: string) => {
  if (str != null && str.length) {
    while (str.length && str[str.length - 1] === '/') {
      str = str.slice(0, str.length - 1);
    }
  }
  return str || '';
};

const joinPath = (...p: string[]) =>
  '/' + trimSlashes(p.map(trimSlashes).filter(x => x).join('/'));

// ---------------8<----------------

const app = await NestFactory.create(AppModule);

// ---------------8<----------------

const excludes = Object.create(null);
const container: NestContainer = (app as any).container; // this is "protected" field, so a bit hacky here
const modules = container.getModules();
const scanner = new MetadataScanner();

modules.forEach(({ routes, metatype }, moduleName) => {
  let modulePath = metatype ? Reflect.getMetadata(MODULE_PATH, metatype) : undefined;
  modulePath = modulePath ? modulePath + globalPrefix : globalPrefix;

  routes.forEach(({ instance, metatype }, controllerName) => {
    const controllerPath = Reflect.getMetadata(PATH_METADATA, metatype);
    const isExcludeController = Reflect.getMetadata('exclude', metatype) === 'true';
    const instancePrototype = Object.getPrototypeOf(instance);

    scanner.scanFromPrototype(instance, instancePrototype, method => {
      const targetCallback = instancePrototype[method];
      const isExcludeMethod = Reflect.getMetadata('exclude', targetCallback) === 'true';

      if (isExcludeController || isExcludeMethod) {
        const requestMethod: RequestMethod = Reflect.getMetadata(METHOD_METADATA, targetCallback);
        const routePath = Reflect.getMetadata(PATH_METADATA, targetCallback);

        // add request method to map, if doesn't exist already
        if (!excludes[RequestMethod[requestMethod]]) {
          excludes[RequestMethod[requestMethod]] = [];
        }

        // add path to excludes
        excludes[RequestMethod[requestMethod]].push(
          // transform path to regexp to match it later in middleware
          pathToRegexp(joinPath(modulePath, controllerPath, routePath)),
        );
      }
    });
  });
});

// now you can use `excludes` map in middleware

我已经发布了一个可重复使用的模块,用于在您的处理程序或 Injectable 类 上发现元数据,专门用于支持此模式。您可以从 NPM 获取 @nestjs-plus/common,然后使用 DiscoveryService 自动检索所有匹配的处理程序或基于您提供的元数据令牌 类。源代码是 available on Github。我将在短期内继续更新文档,但存储库中已经包含了它的几个示例用法。

在底层它使用 MetaDataScanner,但将内容包装在一个非常易于使用的环境中 API。查看您发布的代码片段可能有助于为您的特定用例减少大量样板文件。您可以在 @nestjs-plus/rabbitmq 模块(来自同一存储库)中看到更多高级用法,了解如何将其用于 glue together advanced functionality.

编辑: 我已经更新了库以支持用于发现控制器和控制器方法的场景以支持您的场景。 There's a complete test suite that mimics your setup with the @Roles decorator you can check out.。在导入中包含 DiscoveryModule 并注入 DiscoverService 后,您可以使用简化的 methodsAndControllerMethodsWithMeta API.

找到所有控制器方法
// Inject the service
constructor(private readonly discover: DiscoveryService) { }

// Discover all controller methods decorated with guest roles or 
// belonging to controllers with guest roles

const allMethods = this.discover.methodsAndControllerMethodsWithMeta<string[]>(
  rolesMetaKey,
  x => x.includes('guest')
);

在你发现了所有你想要的方法之后,你可以用它们做任何你想做的事情,在你的例子中,建立一个他们的 RequestMethodpath.[=25 的集合=]

const fullPaths = allGuestMethods.map(x => {
  const controllerPath = Reflect.getMetadata(
    PATH_METADATA,
    x.component.metatype
  );

  const methodPath = Reflect.getMetadata(PATH_METADATA, x.handler);
  const methodHttpVerb = Reflect.getMetadata(
    METHOD_METADATA,
    x.handler
  );

  return {
    verb: methodHttpVerb,
    path: `${controllerPath}/${methodPath}`
  }
});

这会给你这样的东西(取自链接的测试套件)。

expect(fullPaths).toContainEqual({verb: RequestMethod.GET, path: 'guest/route-path-one'});
expect(fullPaths).toContainEqual({verb: RequestMethod.GET, path: 'super/route-path-two'});
expect(fullPaths).toContainEqual({verb: RequestMethod.POST, path: 'admin/route-path-three'});

随时提供有关 approach/API 的反馈。