首页
/ Strapi @strapi/openapi 扩展指南:从零创建自定义 Routes Matcher Rule 过滤规则

Strapi @strapi/openapi 扩展指南:从零创建自定义 Routes Matcher Rule 过滤规则

2026-09-04 11:30:17作者:申梦珏Efrain

在 Strapi 的 @strapi/openapi 包中,最终哪些路由会进入生成的 OpenAPI 文档,是由一条"路由收集 + 规则匹配"的管线决定的:RouteCollector 从多个 Provider 汇聚路由,再交由 RouteMatcher 依据一组可组合的 MatcherRule 逐条过滤。本文基于官方贡献指南《Routes Matcher Rule》,完整讲解如何新增一条匹配规则(以"只选择特定 HTTP 方法的路由"为例),并结合仓库源码剖析规则的类型定义、求值语义(every 全量通过 + 短路)及其在 generate 流程中的装配位置,帮助你在扩展 Strapi OpenAPI 生成器时准确控制被文档化的路由范围。

一、扩展定位:Matcher Rule 在生成管线中的角色

@strapi/openapi 的贡献者指南将扩展点按职责划分为六个方向,其中 Matcher Rule 专门用于"过滤哪些路由会被文档化":

扩展点 适用场景
Routes provider 从新的 Strapi 来源收集路由
Routes matcher rule(本文主题) 过滤哪些路由被写入文档
Assemblers 构建或扩展 OpenAPI 文档各部分
Context factory 增加新的装配层级及其上下文
Processors 在装配前后运行逻辑
Testing 编写或调试单元测试

要理解新增规则的落点,先看源码中匹配器的工作方式。RouteMatcher 的实现极其精简——它持有规则数组,匹配时任一路由必须通过全部规则:

// packages/core/openapi/src/routes/matcher.ts
export class RouteMatcher {
  private readonly _rules: MatcherRule[];

  constructor(rules: MatcherRule[] = []) {
    this._rules = rules;
  }

  match(route: Core.Route): boolean {
    return this._rules.every((rule) => rule(route));
  }
}

对应文件见 matcher.ts。这里有两个直接影响扩展行为的语义,均可由测试用例印证:

  • AND 组合语义every 意味着多条规则之间是"与"关系,任一规则返回 false,该路由即被过滤。route-matcher.test.tsshould fail to match if any rule fails 用例验证了这一点:path.startsWith('/search') 通过但 method === 'POST' 失败时,整体匹配结果为 false
  • 短路求值:源码注释明确写着 "Exits early if any rule fails"——前面的规则失败后,后续规则不再执行。因此新增规则时,把廉价判断(如方法比较)放在昂贵的判断之前,是合理的工程习惯。

而每条规则本身只是一个函数。其类型定义只有一行:

// packages/core/openapi/src/routes/types.ts
export type MatcherRule = (route: Core.Route) => boolean;

types.ts。这意味着一条 Matcher Rule 本质上是一个柯里化的谓词工厂:外层函数接收规则参数(如方法列表),返回一个内层函数接收 Core.Route 并返回布尔值。Core.Route 的关键字段包括 pathmethodinfo(含 type)与 handler,测试中的构造示例如下:

const route: Core.Route = {
  path: '/users/123',
  method: 'GET',
  info: { type: 'content-api' },
  handler: '',
};

二、按指南新增 isMethodIn 规则:三步完整操作

以下三步完整继承自官方指南 02-routes-matcher-rule.md,假设目标是为 @strapi/openapi 增加一条"只选择特定 HTTP 方法路由"的匹配规则。

1. 在 src/routes/rules 下新建规则文件

添加文件 src/routes/rules/is-method-in.ts,粘贴如下片段:

import type { MatcherRule } from '../types';

export const isMethodIn = (methods: string[]): MatcherRule => {
  return (route) => methods.includes(route.method);
};

注意两点细节:

  • 类型导入路径是 '../types',即规则文件位于 src/routes/rules/ 目录时,向上一级即可拿到 MatcherRule 类型定义;
  • 返回值的判断依据是 route.method,这决定了该规则与 isOfType(基于 route.info.type)职责互补,前者过滤"路由种类",后者过滤"请求方法"。

2. 从 src/routes/rules/index.ts 导出规则

在规则桶文件中追加导出:

// ... other exports

export { isMethodIn } from './is-method-in';
// ^ export the rule from here

对照现有实现,rules/index.ts 目前只导出一条内置规则:

export { isOfType } from './is-of-type';

is-of-type.ts 的写法与指南示例完全同构,可以直接作为新规则的参照模板:

import type { MatcherRule } from '../types';

export const isOfType = (type: string): MatcherRule => {
  return (route) => route.info.type === type;
};

3. 将新规则传给 generate 流程中的 RouteMatcher

最后一步在包入口 src/exports.ts 中完成。generate 函数负责把 Provider、Matcher 与装配器工厂装配成一个完整的生成管线,新规则需要传入 RouteMatcher 的规则数组:

// ...

export const generate = (strapi: Core.Strapi, options?: GenerationOptions): GeneratorOutput => {
  // ...

  const routeCollector = new RouteCollector(
    [
      /* ... */
    ],

    new RouteMatcher([
      // ... other rules
      rules.isMethodIn(['POST', 'PUT']),
      // ^ pass the new rule to the matcher instance
    ])
  );

  // ...
};

这里的 rules. 前缀并非随意书写。从 routes/index.ts 的导出方式可以看出,规则被打包成了一个命名空间后统一再导出:

export * as rules from './rules';

因此调用方以 rules.isOfType(...) 的命名空间形式访问所有规则,新增规则只要完成第 2 步的桶文件导出,即可立即通过 rules.isMethodIn 访问,无需改动 routes/index.ts 本身。

现有 generate 流程的真实代码

对照 exports.ts 中的实际实现,可以看到当前默认只装配了一条类型过滤规则,按 options.type(默认 'content-api')区分 Admin 与内容 API 路由:

export const generate = (strapi: Core.Strapi, options?: GenerationOptions): GeneratorOutput => {
  const { type = 'content-api' } = options ?? {};

  const config = {
    preProcessors: new PreProcessorFactory().createAll(),
    assemblers: new DocumentAssemblerFactory().createAll(),
    postProcessors: new PostProcessorsFactory().createAll(),
  };

  // Data sources for the Strapi routes
  const routeCollector = new RouteCollector(
    [
      new AdminRoutesProvider(strapi),
      new ApiRoutesProvider(strapi),
      new PluginRoutesProvider(strapi),
    ],

    new RouteMatcher([
      // Only match content-api routes
      rules.isOfType(type),
    ])
  );

  const contextFactory = new DocumentContextFactory();

  const generator = new OpenAPIGenerator(config, strapi, routeCollector, contextFactory);

  return generator.generate();
};

从源码结构看,若在 RouteMatcher 规则数组中追加 rules.isMethodIn(['POST', 'PUT']),效果是在现有"仅 content-api 路由"的基础上再叠加一层"仅 POST/PUT 路由"的过滤,最终进入文档的路径只保留写操作接口。

三、规则生效的完整调用链:从 Provider 到过滤结果

新增规则只是管线的"过滤条件",理解它如何被消费,有助于验证规则是否生效。整条链路由 RouteCollector 串联,核心逻辑见 collector.ts

public collect(): Core.Route[] {
  const routes = this._providers.flatMap((provider) => Array.from(provider));
  const sanitizedRoutes = this.filter(routes);

  debug(
    'collected %o/%o routes from %o providers %o',
    sanitizedRoutes.length,
    routes.length,
    this._providers.length,
    this._providers.map((provider) => provider.constructor.name)
  );

  return sanitizedRoutes;
}

private filter(routes: Core.Route[]): Core.Route[] {
  return routes.filter((route) => this._matcher.match(route));
}

调用链可以概括为:

  1. 汇聚collect()flatMap 从所有 RoutesProvider(当前内置 AdminRoutesProviderApiRoutesProviderPluginRoutesProvider)拉取全量路由;
  2. 过滤filter() 对每条路由调用 this._matcher.match(route),即前面剖析的"全部规则通过才保留";
  3. 观测:通过 debug('routes:collector') 输出 collected N/M routes from K providers 格式的日志,过滤前后数量对比会直接反映新规则的命中效果——这是验证 isMethodIn 是否按预期收窄路由集合的最直接手段。

RouteCollector 的构造函数签名也值得注意:matcher 参数缺省为一个无规则的 RouteMatcher,而无规则的 RouteMatcherevery 在空数组上恒为 true,等于不做任何过滤。这说明"不传规则"与"传空规则数组"语义等价,均为全量放行。

四、验证与测试:如何确认新规则正确

RouteMatcher 的行为已有专门的单元测试覆盖,位于 route-matcher.test.ts。四个用例分别验证:

  • 单条规则精确命中目标路由(route.path === '/users/123'match 为真);
  • 不满足规则的路由被拒绝(/products/123 不匹配);
  • 多条规则同时满足时整体匹配成功(startsWith('/search') + method === 'GET');
  • 任一规则失败则整体失败(AND 语义)。

这些用例恰好也演示了"内联匿名规则"与"工厂函数规则"两种等价写法:测试中直接用 (route) => route.method === 'GET' 这样的箭头函数作为规则,而指南推荐的 isMethodIn(['POST', 'PUT']) 只是把它封装成可复用、可传参的工厂。为新增的 isMethodIn 编写测试时,可以完全套用上述 Arrange-Act-Assert 结构,仅将规则替换为工厂返回值:

const matcher = new RouteMatcher([isMethodIn(['POST', 'PUT'])]);
expect(matcher.match({ path: '/posts', method: 'POST', info: { type: 'content-api' }, handler: '' })).toBeTruthy();
expect(matcher.match({ path: '/posts', method: 'GET', info: { type: 'content-api' }, handler: '' })).toBeFalsy();

相关测试文件与夹具(__tests__/fixtures/routes.ts__tests__/mocks/route-matcher.mock.ts 等)位于 packages/core/openapi/tests 目录,贡献指南的 Testing 文档 提供了更多调试与测试约定。

五、小结:扩展要点速查

步骤 文件 要点
定义规则 src/routes/rules/is-method-in.ts 规则是 (route: Core.Route) => boolean 的工厂,从 '../types' 导入 MatcherRule
桶导出 src/routes/rules/index.ts export { isMethodIn } from './is-method-in',之后自动进入 rules 命名空间
装配使用 src/exports.ts generateRouteMatcher([...]) 规则数组中调用 rules.isMethodIn([...])

需要牢记的约束:多条规则之间是 AND 关系且会短路,规则的求值顺序影响性能;规则只影响"哪些路由进入文档",不改变路由本身的内容,路由的产出仍由各 Provider 负责。掌握这条过滤规则后,再配合 Routes provider 指南 扩展路由来源,就能完整控制 Strapi OpenAPI 文档生成的输入边界。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
903
1.82 K
docsdocs
暂无描述
Markdown
888
5.78 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
527
590
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.51 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.33 K
1.45 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384
flutter_flutterflutter_flutter
本仓库是 Flutter SDK 与 Flutter Engine 的 OpenHarmony 适配版本,由 CPF-Flutter 团队维护。开发者可使用熟悉的 Flutter 技术栈开发 OpenHarmony 应用,3.35.7 及以后的适配版本可基于本仓库源码构建支持 OpenHarmony 的 Flutter Engine。
Dart
1.17 K
341