首页
/ Strapi @strapi/openapi 扩展指南:路由 Provider、匹配规则、装配器与处理器的贡献路径

Strapi @strapi/openapi 扩展指南:路由 Provider、匹配规则、装配器与处理器的贡献路径

2026-09-04 12:28:20作者:袁立春Spencer

本文以 Strapi 仓库中 @strapi/openapi 包的官方贡献指南 Overview 为主体,完整覆盖该包的全部 6 个扩展点——路由 Provider、匹配规则、装配器、上下文工厂、前后处理器与测试方法,并结合 packages/core/openapi 下的源码实现,说明每个扩展点在生成管线中的确切位置、注册方式与验证手段。读完后,你可以独立完成"让 OpenAPI 文档收录新来源的路由、过滤特定路由、为文档新增字段、新增装配层级"这一整套贡献工作。

一、定位:从生成管线看扩展点

官方 Overview 文档给出的核心信息是:@strapi/openapi 的扩展入口有且仅有 6 个,并按"改动频率"给出了优先级判断:

大多数改动集中在装配器(尤其是 operation 级的叶子装配器)或路由收集。上下文工厂和处理器用到的频率低得多。(原文:Most changes touch assemblers … or route collection. Context factories and processors are needed less often.

原文档中的扩展点总览表如下(链接已转换为仓库内全局路径):

指南 适用场景
Routes provider 从一个新的 Strapi 来源收集路由
Routes matcher rule 过滤哪些路由会被写入文档
Assemblers 构建或扩展 OpenAPI 文档的各个部分
Context factory 添加带有自己上下文的新装配层级
Processors 在装配前后运行逻辑
Testing 编写或调试单元测试

这 6 个扩展点并非孤立存在,它们都挂载在同一条生成管线上。从入口函数 generate 的实现可以看到真实编排:

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

由此可以确认整条管线的执行顺序(与 Processing 指南 中"Pre-processors → Assemblers → Post-processors"的三段式描述一致),并对应到 OpenAPIGenerator.generate() 中的链式调用:

  1. _initContext —— 调用 RouteCollector.collect() 收集路由,再由 DocumentContextFactory 创建文档级上下文;
  2. _bootstrap —— 重置计时器并开始计时;
  3. _preProcess —— 依次运行所有注册的 pre-processor;
  4. _assemble —— 依次运行所有 document 级装配器(其内部再递归到 path → path-item → operation 各级子装配器);
  5. _postProcess —— 依次运行所有注册的 post-processor;
  6. _finalize —— 停止计时,将 context.output.data 作为 document、计时结果作为 durationMs 返回。

也就是说:路由 Provider 与匹配规则决定了"哪些路由进入管线",装配器决定"文档每个部分怎么写",上下文工厂决定"新层级怎么携带数据",处理器决定"整份文档在装配前后还要做什么全局处理"。下面逐一展开各扩展点的官方操作步骤与源码依据。

二、扩展点 1:Routes Provider(收集新来源的路由)

官方指南 Routes Provider 描述了完整的 4 步流程。以新增一个名为 xxx 的 Provider 为例:

第 1 步:在 src/routes/providers 下新增 xxx.ts

import type { Core } from '@strapi/types';

import { createDebugger } from '../../utils';

import { AbstractRoutesProvider } from './abstract';

const debug = createDebugger('routes:provider:xxx');

export class XXXRoutesProvider extends AbstractRoutesProvider {
  public get routes(): Core.Route[] {
    const routes = [];
    // ^ 路由收集逻辑写在这里
    //   通过 this._strapi 与 Strapi 应用交互

    debug('found %o routes in xxx', routes.length);

    return routes;
  }
}

抽象基类 AbstractRoutesProvider 的要求与文档完全吻合:构造时注入 Core.Strapi 实例并保存为 protected readonly _strapi,子类必须实现 get routes(): Core.Route[];基类还额外提供了一个 [Symbol.iterator],使 Provider 本身可直接用 for...of 逐条遍历路由。

第 2 步:从 src/routes/providers/index.ts 导出

// ...
export { XXXRoutesProvider } from './xxx';
// ^ 从这里导出

当前的 providers/index.ts 实际导出了 4 个成员:AbstractRoutesProviderAdminRoutesProviderApiRoutesProviderPluginRoutesProvider 以及类型 RoutesProvider——新 Provider 只需按同一模式追加一行。

第 3 步:从 src/routes/index.ts 重新导出

export {
  // ... 其他 providers
  XXXRoutesProvider,
  // ^ 从这里重新导出
} from './providers';

第 4 步:在 src/exports.tsgenerate 函数中接入

import {
  // ...
  XXXRoutesProvider,
  // ^ 导入新创建的 provider
} from './routes';

export const generate = (strapi: Core.Strapi, options?: GenerationOptions): GeneratorOutput => {
  // ...
  const routeCollector = new RouteCollector(
    [
      // ... 其他 providers
      new XXXRoutesProvider(strapi),
      // ^ 在这里实例化新 provider
    ]
    // ...
  );
  // ...
};

对照 exports.ts 现状,RouteCollector 目前固定接收三个内置 Provider(Admin / API / Plugin),这正是新 Provider 的接入位置。架构文档 Architecture 中还特别指出:Route Collection 域对 OpenAPI 本身没有直接依赖,未来可能迁移到 Strapi 核心或 utils 包——这也解释了为什么"新增路由来源"是相对独立、影响面最小的扩展。

三、扩展点 2:Routes Matcher Rule(过滤被收录的路由)

官方指南 Routes Matcher Rule 以"只保留特定 HTTP 方法的 route"为例,给出 3 步流程:

第 1 步:在 src/routes/rules 下新增 is-method-in.ts

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

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

规则(Rule)的本质是一个 (route) => boolean 的判定函数。仓库中现存的参考实现是 isOfType

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

第 2 步:从 src/routes/rules/index.ts 导出

// ... 其他导出
export { isMethodIn } from './is-method-in';
// ^ 从这里导出

第 3 步:在 generate 中创建的 RouteMatcher 实例里传入新规则

export const generate = (strapi: Core.Strapi, options?: GenerationOptions): GeneratorOutput => {
  // ...
  const routeCollector = new RouteCollector(
    [
      /* ... */
    ],
    new RouteMatcher([
      // ... 其他规则
      rules.isMethodIn(['POST', 'PUT']),
      // ^ 把新规则传给 matcher 实例
    ])
  );
  // ...
};

一个容易忽略的语义细节RouteMatcher.match() 的实现是 this._rules.every((rule) => rule(route))——即所有规则必须同时满足(AND 逻辑),任一规则不通过即被排除,并且短路求值。因此新增的规则只会进一步收窄被文档化的路由集合,不能用来"放行"已被其他规则排除的路由。当前 generate 默认只挂载 rules.isOfType(type)type 默认 'content-api'),所以若想让 isMethodIn 生效,需确认它与类型规则的组合关系符合预期。

四、扩展点 3:Assemblers(构建文档的各个部分)

这是官方 Overview 明确指出"大多数改动集中于此"的扩展点。指南 Assemblers 将装配器分为 4 个层级,每个层级有对应的接口、代表实现与工厂:

层级 接口 示例 工厂
Document Assembler.Document DocumentInfoAssembler DocumentAssemblerFactory
Path Assembler.Path PathItemAssembler PathAssemblerFactory
Path item Assembler.PathItem OperationAssembler PathItemAssemblerFactory
Operation Assembler.Operation OperationParametersAssembler OperationAssemblerFactory

从源码结构可以印证这一分层:packages/core/openapi/src/assemblers/document/ 下嵌套着 path/path-item/operation/ 目录,operation 层包含 operation-id.tsparameters.tsresponses.tstags.tsbody.ts 等叶子装配器,与 OperationAssemblerFactory.createAll() 返回的 5 个装配器一一对应(OperationIDAssemblerOperationParametersAssemblerOperationResponsesAssemblerOperationTagsAssemblerBodyAssembler)。document 层则由 DocumentAssemblerFactory 产出 Metadata、Info、Server、Security、Paths 五个装配器,其中 DocumentPathsAssembler 就是一个向下注入子装配器与 PathContextFactory 的复合装配器。

4.1 添加叶子装配器(日常最常用的操作)

指南以新增 OperationSummaryAssembler(把 summary 写入 operation)为例。官方原文提示:日常改动大多是 operation 级叶子装配器(parameters、body、responses 等);只有在需要编排新的嵌套结构时才用复合装配器。

第 1 步:在 src/assemblers/document/path/path-item/operation 下创建 summary.ts

import type { Core } from '@strapi/types';

import type { OperationContext } from '../../../../../types';
import { createDebugger } from '../../../../../utils';
import type { Assembler } from '../../../..';

const debug = createDebugger('assembler:summary');

export class OperationSummaryAssembler implements Assembler.Operation {
  assemble(context: OperationContext, route: Core.Route): void {
    const summary = route.info.apiName ?? route.handler;

    debug('assembling summary for %o %o: %o', route.method, route.path, summary);

    context.output.data.summary = summary;
  }
}

两条关键约定(来自原文档):

  • 叶子装配器接收本层级的 context,外加接口定义的额外参数——operation 级装配器额外接收 route
  • 结果一律写入 context.output.data。该对象会被调用你的那个复合装配器合并进父级输出。

第 2 步:从 operation/index.ts 导出

export { OperationSummaryAssembler } from './summary';

第 3 步:注册进 OperationAssemblerFactory.createAll()

import { OperationSummaryAssembler } from './summary';

export class OperationAssemblerFactory {
  createAll(): Assembler.Operation[] {
    return [
      // ... 现有装配器
      new OperationSummaryAssembler(),
    ];
  }
}

若添加的是 document 级叶子装配器(例如新增一个 OpenAPI 顶层字段),同样模式应用在 src/assemblers/document/ 下,并注册进 DocumentAssemblerFactory

4.2 添加复合装配器

复合装配器的职责是:创建子上下文 → 运行子装配器 → 把结果合并回父级输出。官方指定的参考实现是 OperationAssemblersrc/assemblers/document/path/path-item/operation/operation.ts),指南给出了完整示例:

import type { Core } from '@strapi/types';

import { OperationContextFactory } from '../../../../../context';
import type { PathItemContext } from '../../../../../types';
import type { Assembler } from '../../../..';

export class OperationAssembler implements Assembler.PathItem {
  constructor(
    private readonly _assemblers: Assembler.Operation[],
    private readonly _contextFactory: OperationContextFactory = new OperationContextFactory()
  ) {}

  assemble(context: PathItemContext, path: string, routes: Core.Route[]): void {
    const { output, ...sharedProps } = context;

    for (const route of routes) {
      const operationContext = this._contextFactory.create(sharedProps);

      for (const assembler of this._assemblers) {
        assembler.assemble(operationContext, route);
      }

      Object.assign(output.data, { [route.method.toLowerCase()]: operationContext.output.data });
    }
  }
}

接线方式:创建或扩展一个工厂(例如 PathItemAssemblerFactory),由它把子装配器与 context factory 一并注入你的复合装配器,再把这个工厂注册到上一层级(PathAssemblerFactoryDocumentAssemblerFactory)。仓库中现成的范本是 DocumentAssemblerFactory._createPathsAssembler()——它默认创建 PathAssemblerFactoryPathContextFactory(依赖注入 + 工厂模式的典型写法),将子装配器数组传给 DocumentPathsAssembler 构造函数。

官方 Tip 强调:创建子上下文时应复用父上下文的 timerregistries(通过 PartialContext 传入),保证计时与共享状态在整棵装配树中一致。

五、扩展点 4:Context Factory(新增装配层级)

指南 Context Factory 先划定了使用边界:

  • 何时新增工厂:引入一个拥有自己输出结构(output shape)的新装配层级时;
  • 何时不用新增:只在现有层级添加叶子装配器时,直接复用该层级的工厂(例如 OperationContextFactory)。

新增流程共 4 步,以虚构的 Widget 层级为例:

第 1 步:在 src/types.ts 定义 context 数据类型

import type { Context } from './context';

export type WidgetContextData = Partial<{ widgets: Record<string, unknown> }>;
export type WidgetContext = Context<WidgetContextData>;

第 2 步:创建 src/context/factories/widget.ts

import { RegistriesFactory } from '../../registries';
import type { WidgetContext, WidgetContextData } from '../../types';
import { TimerFactory } from '../../utils';
import type { PartialContext } from '../types';

import { AbstractContextFactory } from './abstract';

export class WidgetContextFactory extends AbstractContextFactory<WidgetContextData> {
  constructor(
    registriesFactory: RegistriesFactory = new RegistriesFactory(),
    timerFactory: TimerFactory = new TimerFactory()
  ) {
    super(registriesFactory, timerFactory);
  }

  create(context: PartialContext<WidgetContextData>): WidgetContext {
    return super.create(context, {});
  }
}

第 3 步:从 src/context/factories/index.ts 导出

export { WidgetContextFactory } from './widget';

第 4 步:在复合装配器中使用

把父上下文的共享属性传入子上下文工厂:

const childContext = this._contextFactory.create({
  strapi: context.strapi,
  routes: context.routes,
  timer: context.timer,
  registries: context.registries,
});

AbstractContextFactory.create() 的实现可以精确确认原文档所说的"工厂构建的 context 包含什么":

  • strapiroutes 为必填(直接从入参解构);
  • timerregistries 优先复用父级传入的实例context.timer ?? this._timerFactory.create()),否则新建——这正是"计时与共享状态跨层级一致"的实现基础;
  • output.data 初始化为 super.create() 第二个参数 defaultValue,同时 output.stats.time 被置为零值占位。

Registries 的现状

官方文档明确说明:RegistriesFactory.createAll() 当前返回空对象,ContextRegistries 是空接口;registries 是为未来跨装配器共享状态(去重 schema、跨装配器缓存等)预留的扩展位,目前不需要任何额外配置。唯一已在用的"类注册表"机制是处理器文档提到的 context.registries.extractedComponentSchemas(见下节)。

六、扩展点 5:Pre/Post Processors(装配前后的全局逻辑)

指南 Processors 的核心事实:

  • 两种处理器都接收完整的 DocumentContext
  • 实例注册在 PreProcessorFactory / PostProcessorsFactory 中,这两个工厂由 src/exports.tsgenerate 内实例化并传入 OpenAPIGeneratorconfig
  • 执行顺序由生成器固定为:pre-processors → assemblers → post-processors(对应 generator.ts 的链式调用)。

以添加 post-processor 为例。指南先交代了现有 post-processor 的语义:ComponentsWriter 在装配完成后,把 strapi.contentAPISchemaRegistry 写入 components.schemas;路由转换期间采集到的嵌套 Zod .meta({ id }) schema 通过 context.registries.extractedComponentSchemas 合并进来,以保证 $ref 可以解析——这也印证了 registries 是跨装配阶段传递采集状态的通道。

第 1 步:创建处理器类

import type { DocumentContext } from '../types';
import type { PostProcessor } from './types';

export class ExamplePostProcessor implements PostProcessor {
  postProcess(context: DocumentContext): void {
    // 直接修改 context.output.data
  }
}

第 2 步:注册进 PostProcessorsFactory

import { ComponentsWriter } from './component-writer';
import { ExamplePostProcessor } from './example';

export class PostProcessorsFactory {
  createAll(): PostProcessor[] {
    return [new ComponentsWriter(), new ExamplePostProcessor()];
  }
}

Pre-processor 则实现带 preProcess(context: DocumentContext) 方法的 PreProcessor 接口,以同样方式注册进 PreProcessorFactory

官方给出的选型原则值得原样遵循:能用装配器解决的文档分区构建,优先用装配器;处理器只用于必须在整轮装配之前/之后执行的横切逻辑。

七、扩展点 6:测试

指南 Testing 规定:单元测试位于 packages/core/openapi/__tests__/,从包目录运行:

cd packages/core/openapi && yarn test:unit

共享辅助代码在 __tests__/fixtures/__tests__/mocks/。仓库中对应的真实测试文件包括 document-assemblers.test.tsoperation-assemblers.test.tsroutes 目录以及 zod-to-openapi.test.ts 等。

官方文档给出三类被测对象的写法:

7.1 测试路由 Provider

只 mock 你的 Provider 实际读取的 Strapi 属性;__tests__/mocks/strapi.mock.ts 中的 StrapiMock 覆盖了常见的 apisplugins 结构:

import type { Core } from '@strapi/types';

import { ApiRoutesProvider } from '../../src/routes';
import { StrapiMock } from '../mocks';

it('returns registered API routes', () => {
  const strapi = new StrapiMock() as unknown as Core.Strapi;
  const provider = new ApiRoutesProvider(strapi);

  expect(provider.routes.length).toBeGreaterThan(0);
});

7.2 测试匹配规则

构造最小 Core.Route 直接喂给 RouteMatcher

import type { Core } from '@strapi/types';

import { RouteMatcher } from '../../src/routes';

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

expect(new RouteMatcher([(r) => r.method === 'GET']).match(route)).toBe(true);

7.3 测试装配器

用对应层级的工厂创建 context,运行装配器,断言 context.output.data

import type { Core } from '@strapi/types';
import * as z from 'zod/v4';

import { OperationParametersAssembler } from '../../src/assemblers/document/path/path-item/operation';
import { OperationContextFactory } from '../../src/context';

const context = new OperationContextFactory().create({ strapi: {} as Core.Strapi, routes: [] }, {});

new OperationParametersAssembler().assemble(context, {
  method: 'GET',
  path: '/api/articles/:id',
  handler: '',
  info: { type: 'content-api' },
  request: {
    params: { id: z.string() },
    query: { locale: z.string().optional() },
  },
});

expect(context.output.data.parameters).toEqual(
  expect.arrayContaining([
    expect.objectContaining({ name: 'id', in: 'path' }),
    expect.objectContaining({ name: 'locale', in: 'query' }),
  ])
);

指南特别指出:route 上的 requestresponse Zod schema 是 operation 级装配器的主要输入__tests__/operation-assemblers.test.ts 中有镜像 content API addQueryParams / addInputParams 行为的完整用例可参照。

7.4 调试失败用例

在包目录启用该包的 debug 输出:

cd packages/core/openapi && DEBUG=strapi:core:openapi:* yarn test:unit

这与源码中各模块使用的命名空间(如 generatorroutes:provider:xxxassembler:summary)相互印证。

八、选型决策与扩展点速查

结合 Overview 的优先级提示与各指南,可以把"该改哪里"压缩为一张决策表:

你想做的事 扩展点 主要改动文件
让文档收录新的路由来源(如新的 Strapi 子系统) Routes Provider src/routes/providers/*src/routes/providers/index.tssrc/routes/index.tssrc/exports.ts
排除/收窄被文档化的路由 Matcher Rule src/routes/rules/*src/routes/rules/index.tssrc/exports.ts
为文档新增字段或 section(operation 级最常见) 叶子装配器 src/assemblers/document/** 对应层级目录 + 对应 Factory.createAll()
引入新的嵌套文档结构 复合装配器(+ 新 Context Factory) src/assemblers/**src/context/factories/*src/types.ts
整份文档装配前/后的全局处理 Processor src/pre-processor/*src/post-processor/* 的工厂
验证以上任何改动 单元测试 packages/core/openapi/__tests__/

两个跨扩展点的重要约定值得最后强调:

  1. 一切改动都在工厂的 createAll() 中登记,最终由 generate() 消费。 generate 集中实例化了 PreProcessorFactoryDocumentAssemblerFactoryPostProcessorsFactoryRouteCollector(含三个内置 Provider)与 DocumentContextFactory,因此新组件若没有出现在相应工厂的返回列表中,管线不会感知到它。
  2. 包根导出与公开 API 解耦。 架构文档说明该包在 src/exports.ts 中集中管理公开 API 的全部导出,包根导出与面向编程调用的公共 API 相互独立——这也意味着对 generate 的签名或行为调整属于 @experimental 范畴(Usage 文档 明确声明 OpenAPI 生成功能是实验性的,其行为与输出可能不遵循 semver 变化),实验性 CLI 为 strapi openapi generate [-o, --output <path>]

以上各扩展点均位于 @strapi/openapi 包内,源码根目录为 packages/core/openapi/src/,测试位于 packages/core/openapi/__tests__/。按"装配器/路由收集优先,处理器与上下文工厂次之"的顺序理解这 6 个扩展点,即可覆盖对 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++
902
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