首页
/ Strapi OpenAPI 包架构解析:路由收集与文档生成的两大领域与核心设计模式

Strapi OpenAPI 包架构解析:路由收集与文档生成的两大领域与核心设计模式

2026-09-04 11:56:20作者:殷蕙予

本文基于 Strapi 仓库中 docs/docs/docs/01-core/openapi/02-architecture.md 架构文档,结合 packages/core/openapi 的实际源码,深入解析 @strapi/openapi 包的整体架构。读完后,你将理解该包如何划分为“路由收集(Route Collection)”与“OpenAPI 文档生成(Document Generation)”两个领域,掌握 RouteCollector、Provider、Matcher、Rule 与 Generator、Processor、Assembler、Context、Registry 各组件的职责边界、协作方式与关键调用链,从而能够在扩展 Strapi API 文档能力时准确定位代码位置。

一、包定位与整体概览

@strapi/openapi 是 Strapi 仓库中用于“为 Strapi 项目生成并校验 API 文档”的工具集(见 package.json 中的 description 字段,当前仓库版本为 5.52.2,核心依赖为 debugopenapi-typeszod)。

架构文档将包的组织结构概括为两大领域

1. The Route Collection(路由收集领域)

该领域提供一系列对象,用于从 Strapi 应用中提取不同集合的路由,相关源码位于 src/routes 目录(即 packages/core/openapi/src/routes)。

文档中特别指出:由于该领域并不直接依赖 OpenAPI 本身,未来有可能被迁移到 Strapi core 或 Strapi utils 中

2. The OpenAPI Document Generation(OpenAPI 文档生成领域)

该领域包含从 Strapi 应用生成合法 OpenAPI 文档所需的全部组件。文档给出的组件—路径对照表完整如下:

组件 路径
OpenAPI Generator src/generator
Assemblers src/assemblers
Contexts src/context
Registries src/registries
Processors src/pre-processorsrc/post-processor

3. 公共 API 与包根导出的解耦

此外,包将所有公共 API 的导出集中到一个 src/exports.ts 文件(packages/core/openapi/src/exports.ts)中。这种设计把包根部的导出与它对外暴露的公共可编程 API 解耦:包根 index.ts 只需再转发 exports.ts 的内容,而真正决定“对外承诺了什么接口”的是 exports.ts。当前该文件只导出了一个函数 generate 与两个类型 GenerationOptionsGeneratorOutput

二、设计原则:SOLID 与设计模式

架构文档明确指出,该包遵循面向对象设计原则与模式,以保证代码可维护、可扩展:

  • SOLID 原则:遵循单一职责、开闭、里氏替换、接口隔离与依赖倒置原则;
  • 依赖注入(Dependency Injection):组件通过构造函数接收依赖,而不是在内部自行创建依赖;
  • 工厂模式(Factory Pattern):用于实例化行为可配置的复杂对象;
  • 注册表模式(Registry Pattern):以受控的方式维护全局状态与配置(见 Registries 与 Context)。

文档强调该架构带来三个特性:

  • 原子化组件与单一职责,这在装配(assembly)过程中尤为突出(见 Assemblers);
  • 清晰的关注点分离(编排器、路由收集、装配器、生命周期等);
  • 可扩展的设计:无需修改既有代码即可添加新能力(见 Assembler、Processors)。

其结果是:一个模块化系统,每个组件聚焦于特定任务,同时与其他部分保持清晰的边界。下面两个核心组件(Route Collector 与 OpenAPI Generator)正是这些原则的集中体现。

三、Route Collector:路由收集组件深度解析

Route Collector 负责基于提供的 Providers 与 Matchers 编排路由收集过程,涉及三层抽象:

  • Providers:为特定用途收集路由集合(Admin、Core API、插件等);
  • Matchers:基于规则过滤路由集合;
  • Rules:基于特定条件匹配(或排除)单条路由。

文档用如下实体关系图描述了四者关系:

erDiagram
  RouteCollector ||--o{ Provider: orchestrates
  RouteCollector ||--o{ Matcher: uses
  Provider ||--|{ Route: collects
  Matcher ||--|{ Rule: contains
  Provider ||--o{ Matcher: routesFilteredBy
  Rule }o--|| Route: evaluates

  Provider {
    string type
    string source
  }

  Route {
    object info
    string path
    string method
    string handler
  }

  Matcher {
    array rules
    function match
  }

  Rule {
    function match
  }

  RouteCollector {
    array providers
    array matchers
    function collect
  }

3.1 RouteCollector 与 RouteMatcher 的实现

从源码看(collector.ts),RouteCollector 的构造函数接收一个 RoutesProvider[] 数组与一个 RouteMatcher(两者均有默认值:空数组与无规则的新 RouteMatcher),体现了典型的依赖注入。其 collect() 方法的流程是:

  1. flatMap 遍历所有 provider,把每个 provider 展平为路由(依赖 provider 实现的迭代器协议,见下文);
  2. 调用私有方法 filter(),用 this._matcher.match(route) 过滤出满足全部规则的路由;
  3. 通过 debug('routes:collector') 调试器记录“共收集到 %o/%o 条路由,来自 %o 个 provider”。

RouteMatchermatcher.ts)的实现非常简洁:match(route) 返回 this._rules.every((rule) => rule(route))——必须通过所有规则才算匹配,任一规则失败即短路排除。

当前内置的唯一规则是 rules.isOfTypeis-of-type.ts):

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

它通过比较路由元信息 route.info.type 与目标类型字符串,实现对 admin / content-api 等路由类型的筛选——这正是公共 API generate(strapi, { type: 'admin' | 'content-api' }) 选项能够生效的底层机制(见第五节)。

3.2 三个内置 Routes Provider

包的 src/routes/providers 目录提供了三个具体 Provider,均继承自抽象基类 AbstractRoutesProvider。该基类:

  • 在构造函数中注入 strapi 实例(Core.Strapi 类型),符合“依赖通过构造函数注入”的原则;
  • 声明抽象 getter routes: Core.Route[],由子类实现;
  • 实现了 [Symbol.iterator]() 生成器,可逐条 yield 路由——这正是 RouteCollector.collect()flatMap((provider) => Array.from(provider)) 能够直接遍历 provider 的原因。

三个 Provider 的差异在于数据来源与展平方式:

Provider 数据来源 展平逻辑
ApiRoutesProvider strapi.apis Object.values(apis)Object.values(api.routes)router.routes,两层 flatMap 将“API → 路由表 → 路由”展平为一维数组
AdminRoutesProvider strapi.admin.routes 对 admin 路由表做一层 flatMap(router.routes)
PluginRoutesProvider strapi.plugins 兼容两种插件路由形态(见下)

其中 PluginRoutesProvider 的实现最值得注意,它同时处理插件定义路由的两种方式:

  • 插件的 routes 直接就是 Core.Route[] 数组时,原样返回;
  • 插件的 routes 是“路由器记录”(record of routers)时,遍历每个 router,并计算有效前缀:若路由自身 config 上有 prefix 属性(通过 hasOwnProperty 显式判断),优先使用路由自身的前缀;否则回退到 router.prefix。随后把前缀与 route.path 拼接为完整路径,并用正则压缩重复斜杠、去除尾部斜杠,保证如 /users-permissions/roles 这类插件路径在最终文档中出现为规范形式。

3.3 测试佐证

路由收集领域有独立且完整的测试覆盖(packages/core/openapi/__tests__/routes/ 目录):collector.test.tsroute-matcher.test.tsapi-routes-provider.test.tsplugins-route-provider.test.ts,并配合 __tests__/mocks/route-matcher.mock.tsroutes-provider.mock.ts 等 mock 对象验证行为,可作为理解各组件契约的参考。

四、OpenAPI Generator:文档生成组件深度解析

OpenAPI Generator 负责从 Strapi 应用生成合法的 OpenAPI 文档,文档将其内部结构拆为四部分:

  • Processors(处理器):生命周期对象与方法
    • Pre-processors:在装配前准备数据与注册表;
    • Post-processors:在装配后清理并定稿输出;
  • Assemblers(装配器):生成最终文档的特定部分,每个装配器承担单一职责;
    • Sub-Assemblers(子装配器):面向嵌套组件(operations 等)的专用装配器,使用自定义 context;
  • Context(上下文)
    • 持有装配过程所需的信息与实例;
    • context.output.data 中维护最终构建对象的引用;
    • Registries(注册表):为共享装配状态而设(RegistriesFactory 当前返回空对象);

文档给出的实体关系图如下:

erDiagram
  OpenAPIGenerator ||--|{ PreProcessor: "uses before assembly"
  OpenAPIGenerator ||--|{ PostProcessor: "uses after assembly"
  OpenAPIGenerator ||--|{ Assembler: orchestrates
  OpenAPIGenerator ||--|| Context: maintains
  Context ||--|{ Registry: manages
  PreProcessor }o--|| Context: "prepares"
  PostProcessor }o--|| Context: "finalizes"
  Assembler }o--|| Context: "updates"
  Assembler ||--o{ Assembler: "contains nested"

  OpenAPIGenerator {
    function generate
    array assemblers
    array preProcessors
    array postProcessors
  }

  PreProcessor {
    function preProcess
  }

  PostProcessor {
    function postProcess
  }

  Assembler {
    function assemble
    Assembler[] children
  }

  Context {
    object output
    Strapi strapi
    array routes
    array registries
  }

  Registry {
    array entries
    function add
    function get
    function has
  }

4.1 generate() 的七步流水线

generator.ts 中,OpenAPIGenerator 的构造函数接收三组依赖:configpreProcessors / assemblers / postProcessors 三个数组)、strapi 实例、routeCollector,以及一个 DocumentContextFactory——全部经构造函数注入,与文档所述依赖注入原则一致。

generate() 方法是一条链式编排的流水线,每一步都以 context 为唯一数据载体:

  1. _initContext(strapi):先调用 routeCollector.collect() 收集路由,再用 contextFactory.create({ strapi, routes }) 创建初始文档生成上下文;
  2. _bootstrap(context):重置并启动 context.timer,记录开始时间戳;
  3. _preProcess(context):顺序执行所有 pre-processor 的 preProcess(context)
  4. _assemble(context):顺序执行所有 document assembler 的 assemble(context)
  5. _postProcess(context):顺序执行所有 post-processor 的 postProcess(context)
  6. _finalize(context):调用 timer.stop() 把耗时写入 context.output.stats.time
  7. 最终返回 { document: data, durationMs: stats.time.elapsedTime },其中 data 被断言为 OpenAPIV3_1.Document

整条链路中,每个阶段都通过 debug('generator') 输出(如 running pre-processor: %s...),为排查生成过程提供了可观测性。

4.2 Context:按文档层级参数化的上下文

types.ts 可以看到,Context 是一种按 OpenAPI 文档结构层级参数化的类型:

export type DocumentContextData = Partial<OpenAPIV3_1.Document>;
export type DocumentContext = Context<DocumentContextData>;

export type OperationContextData = Partial<OpenAPIV3_1.OperationObject>;
export type OperationContext = Context<OperationContextData>;

export type PathContextData = Partial<OpenAPIV3_1.PathsObject>;
export type PathContext = Context<PathContextData>;

export type PathItemContextData = Partial<OpenAPIV3_1.PathItemObject>;
export type PathItemContext = Context<PathItemContextData>;

这与文档中“Context 在 context.output.data 中维护最终构建对象的引用”以及“Sub-Assemblers 被提供自定义 contexts”的描述相印证:document 级装配器读写 DocumentContext,而下沉到 path / path-item / operation 层的子装配器则读写更小粒度的 context。相应的工厂实现集中在 src/context/factories 目录(abstract.tsdocument.tsoperation.tspath-item.tspath.ts)。

4.3 Registries 的当前状态

文档称 Registries “保留用于共享装配状态(RegistriesFactory 当前返回空对象)”。从 registries/factory.ts 看,RegistriesFactory.createAll() 目前返回 { extractedComponentSchemas: {} }——即仅预置一个空的 extractedComponentSchemas 记录(Record<string, OpenAPIV3_1.SchemaObject>)。可以推断:注册表机制的骨架(类型、工厂、目录结构)已经就位,但“跨装配器共享已抽取组件 schema”的具体填充逻辑尚未落地,这为后续扩展留出了明确的挂载点。

五、端到端串联:公共 API generate()

文档描述的两大领域最终在 exports.ts 导出的 generate() 函数中完成组装,这是理解整个架构的最短路径:

import { generate } from '@strapi/openapi';

// 只生成 content-api 路由的文档(type 的默认值即为 'content-api')
const output = generate(strapi, { type: 'content-api' });
console.log(output.document);

// 不传 options 时同样以 'content-api' 为默认
const all = generate(strapi);

其内部组装顺序清晰呈现了架构文档的组件划分:

  1. options 解构出 type,默认 'content-api'GenerationOptions.type 的取值域为 'admin' | 'content-api',见 types.ts);
  2. 通过三个工厂创建处理器与装配器实例:new PreProcessorFactory().createAll()new DocumentAssemblerFactory().createAll()new PostProcessorsFactory().createAll()——工厂模式在此集中体现:调用方只面对工厂,不关心具体装配器清单;
  3. 构造 RouteCollector,注入三个内置 provider(AdminRoutesProviderApiRoutesProviderPluginRoutesProvider)与一个只含 rules.isOfType(type) 规则的 RouteMatcher——同一份 provider 全集 + 不同 rule,即可切换生成 admin 或 content-api 文档;
  4. DocumentContextFactory 创建 OpenAPIGenerator 并调用 generator.generate()

返回值 GeneratorOutput 携带最终文档与生成耗时 durationMs(对应第四节的 _finalize 计时)。需要说明的是:源码中 generate 的 JSDoc 标注了 @experimental,且该 API 面向“在内存中生成 Strapi 路由的 OpenAPI 规范”,适用前提是需要一个已初始化的 Core.Strapi 实例。

装配器侧的文档结构同样按 OpenAPI 文档层级组织在 src/assemblers/document 目录中:info.tsserver.tssecurity.tsmetadata.tsquery-param-styles.ts,以及 path/ 下的 path.tspath-item/operation/ 内含 body.tsoperation.tsparameters.tsresponses.tstags.tsoperation-id.ts)——每一层一个目录、一个 factory.ts,与文档中“每个 Assembler 单一职责、Sub-Assembler 处理嵌套组件”的描述一一对应。相关单测如 __tests__/document-assemblers.test.tsoperation-assemblers.test.tsquery-param-styles.test.tszod-to-openapi.test.ts 可作为各装配器行为的验证依据。

六、架构总览与关键路径索引

综合架构文档与源码,@strapi/openapi 的运行时协作关系可以概括为一条主线:

generate()(exports.ts)→ 工厂创建 Processors / Assemblers → RouteCollector(3 个 Provider + 1 个 Matcher/Rule)收集并过滤路由 → DocumentContextFactory 建立上下文 → OpenAPIGenerator 按“bootstrap → preProcess → assemble → postProcess → finalize”流水线产出 OpenAPIV3_1.Document 与耗时。

这一设计把“从哪里取路由”(routes 领域,未来可迁移至 core/utils)与“如何生成文档”(generator 领域)彻底解耦:新增路由来源只需实现 RoutesProvider 抽象并加入 provider 数组,新增文档能力只需实现 Assembler/Processor 并注册到对应工厂,均无需改动既有代码——这正是文档所述开闭原则与“模块化、清晰边界”目标在工程上的落点。

关键源码索引(均以仓库根目录为基准):

关注点 路径
公共 API generate packages/core/openapi/src/exports.ts
生成器主流程 packages/core/openapi/src/generator/generator.ts
路由收集器 packages/core/openapi/src/routes/collector.ts
路由匹配器 packages/core/openapi/src/routes/matcher.ts
类型匹配规则 packages/core/openapi/src/routes/rules/is-of-type.ts
Provider 抽象基类 packages/core/openapi/src/routes/providers/abstract.ts
插件路由前缀处理 packages/core/openapi/src/routes/providers/plugin.ts
Context 类型定义 packages/core/openapi/src/types.ts
注册表工厂 packages/core/openapi/src/registries/factory.ts
测试用例 packages/core/openapi/tests
架构文档原文 docs/docs/docs/01-core/openapi/02-architecture.md
登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
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