首页
/ Halo 公共 API 库(api/)开发指南:扩展模型、服务契约与安全抽象的兼容性治理

Halo 公共 API 库(api/)开发指南:扩展模型、服务契约与安全抽象的兼容性治理

2026-09-08 19:24:06作者:俞予舒Fleming

api/ 是 Halo 项目中独立成库的公共 API 模块,承载了整套扩展模型(Extension Model)、服务契约(Service Contracts)与安全抽象(Security Abstractions),既被后端应用 application/ 消费,也是所有第三方插件在编译期唯一依赖的 ABI。本指南围绕仓库内 api/AGENTS.md 的治理约定展开,说明该模块的职责边界、构建命令、编码规范与评审红线,并结合源码给出可落地的实践依据;读完你既能理解"改哪个包会影响谁",也能掌握如何在不破坏下游兼容的前提下向 API 库提交变更。

api/ 是什么:全项目共享的兼容性表面

从模块治理的角度看,Halo 是一个前后端同仓的全栈 Monorepo,根目录 AGENTS.md 将其划分为 API 库、后端应用、前端与两组 BOM(platform/application/platform/plugin/)。其中 api/AGENTS.md 开宗明义地定义了 api/ 的本质:

api/ is the public API library: the extension model, service contracts, and security abstractions consumed by the application and by plugins. Changes here affect downstream compatibility.

这句话包含三层信息:

  1. 它是"库"而非"应用":不承载可独立启动的服务逻辑,而是被应用与其他工程链接的 Java Library。
  2. 它是插件的唯一编译面:第三方插件基于 Halo 构建时,接触到的类型、接口全部来自这里,因此该目录的每一次变更都会波及所有已发布的插件。
  3. 它必须保持向后兼容:后续小节中的"偏好增量变更""变更需显式批准"等约定,均由这一特性推导而来。

工程配置层面同样印证了它的定位:api/build.gradlegroup = 'run.halo.app'description = 'API of halo project, connecting by other projects.',并通过 halo.publishpublishing { publications.named('mavenJava') } 把模块发布为独立 Maven 构件,同时启用 withSourcesJar()withJavadocJar(),说明该库对外还承担了文档(Javadoc)供给的职责。也就是说,模块内的类型注释会直接成为下游插件开发者阅读的 API 文档。

代码布局:五大职责与对应的包结构

文档规定源码位于 api/src/main/java/run/halo/app/,测试位于 api/src/test/。从实际目录看,源码按领域进一步组织为以下包(下表为真实存在的顶层包与代表性类型):

职责 代表性类型(源码级佐证)
extension 扩展模型核心:自定义资源类型定义、客户端、索引与 Watch ExtensionExtensionClientReactiveExtensionClientGVKSchemeWatcherListResultPageRequest(见 extension 包
extension.index.query 扩展索引的查询条件 DSL AndOrEqualConditionLabelConditionQueries 等(见 index/query 包
extension.controller 声明式控制器框架(Kubernetes 风格的 reconcile 循环) ControllerControllerBuilderReconcilerExtensionWatcherRequeueException(见 controller 包
core.extension Halo 内建的具体扩展资源类型 PostCategoryTagSinglePageCommentMenuThemeUserRolePluginSettingAttachment 等(见 core/extension 包
security 认证/授权与安全过滤器抽象 AdditionalWebFilterAuthenticationSecurityWebFilterPersonalAccessTokenCryptoServiceDeviceService(见 security 包
plugin 插件 SPI 与生命周期 BasePluginPluginContextSettingFetcherReactiveSettingFetcherSpringPluginManagerextensionpoint.ExtensionGetter(见 plugin 包
notification 通知中心的契约 NotificationCenterNotificationReasonEmitterReactiveNotifierReasonPayloadUserIdentity(见 notification 包
search 搜索引擎抽象与文档事件 SearchEngineSearchServiceHaloDocumentsProviderHaloDocument 及增删重建事件(见 search 包
theme 主题侧扩展点:Finder、模板头部/页脚处理 FinderTemplateHeadProcessorTemplateFooterProcessorCommentWidgetReactivePostContentHandler(见 theme 包
event / infra / content / migration 领域事件、基础设施抽象、内容契约与迁移 PostPublishedEventSystemInfoGetterExternalUrlSupplierPostContentServiceBackup

需要特别指出的是,api/ 中大量接口以响应式(Reactive) 形态存在,例如 ReactiveExtensionClientReactiveNotifierReactiveSettingFetcherReactivePostContentHandler。这与 Halo 后端 Spring Boot WebFlux + R2DBC 的技术栈一一对应——API 库并非"贫血的 DTO 仓库",而是直接承载了与运行时异步语义强绑定的契约。

常用命令:在仓库根目录执行

api/AGENTS.md 只约定两条必需命令,均需在仓库根目录通过 Gradle Wrapper 执行:

./gradlew :api:test        # 运行 API 库的单元/集成测试
./gradlew spotlessApply    # 格式化 Java 与 Markdown

结合工程配置可以补充以下细节:

  • 测试任务api/build.gradletasks.named('test') 配置了 useJUnitPlatform(),即以 JUnit Platform(JUnit 5)运行;测试结束后 finalizedBy jacocoTestReport 会自动生成覆盖率报告,其中 XML 报告被开启(xml.required = true),供 CI 做覆盖率门禁消费。
  • Java 版本api/build.gradle 通过 Java Toolchain 声明 languageVersion = JavaLanguageVersion.of(21),同时 options.release = 21 强制以 release 21 编译,确保运行在 Java 21 及以上环境;源码编码固定为 UTF-8。
  • 格式化spotless { java { palantirJavaFormat("2.90.0").formatJavadoc(true) } },即使用 Palantir Java Format 2.90.0,且会顺带格式化 Javadoc——这也呼应了上文"Javadoc 即对外文档"的定位;根目录 AGENTS.md 则说明仓库级 spotlessApply 还会覆盖 Markdown、JSON 与 properties。
  • 可选的扩展命令:若改动影响后端,可运行 ./gradlew :application:test;前端改动可用 pnpm -C ui typecheck && pnpm -C ui lint 校验(详见 根目录 AGENTS.md 的 Quick Commands)。

编码约定:JVM 版本、格式与增量原则

文档将 api/ 的编码约定浓缩为四点,其中兼容性策略是全篇的灵魂:

1. Java 21 + 4 空格缩进 + Spotless 强制格式。 这与整个后端应用保持一致(同受根目录 AGENTS.md 约束),并依赖 platform/application/ BOM 统一依赖版本管理,避免在 api/build.gradle 中散落硬编码版本号。

2. API 是兼容性表面,偏好"加法式变更"。 允许新增类型与方法(additive changes),但任何破坏性变更——例如修改既有方法签名、删除公开类型、改变接口语义——必须在合并前显式提出并评审。推荐的动作是:

  • 新增能力优先通过新增重载/新接口/新资源类型表达,而不是改动旧签名;
  • 确需破坏兼容时,先在对齐的 PR/讨论中说明受影响范围(如哪些插件会编译失败)再动手。

3. 扩展定义与 Schema 的落地位置在 application 侧。 文档明确:"Extension definitions and schemas live in application/src/main/resources/extensions/; when you change them, update application/ and ui/ together." 这一点很容易被误读——api/ 里的 core.extension 是 Java 类型定义,而真正声明"系统中有哪些扩展资源、有哪些扩展点实现"的 YAML 全部收敛在 extensions 目录,例如 extension-definitions.yamlextensionpoint-definitions.yaml 及一批 role-template-*.yaml。详见下一节。

4. 测试使用 JUnit 5,命名规范化。 单元测试命名 XxxTest,集成测试命名 XxxIntegrationTest。从 api/src/test/java/run/halo/app/ 的真实文件可以看出这套规则的实际落地,例如模型校验类 CategoryTestPostTestMenuTestThemeTest,查询与选择器类 QueriesTestLabelSelectorTestOperatorTest,工具类 JsonUtilsTestPathUtilsTestHttpSecurityUtilsTest,以及控制器框架的 ControllerBuilderTestRequestSynchronizerTest 等。

扩展定义与 Schema 的三侧联动:api、application、ui

扩展模型虽然是 api/ 的核心职责,但 schema 的"权威 YAML"并不在本模块,而是统一收敛在 application/src/main/resources/extensions/extension-definitions.yaml。该文件的典型结构如下(以通知与搜索为例):

apiVersion: plugin.halo.run/v1alpha1
kind: ExtensionDefinition
metadata:
  name: halo-email-notifier
spec:
  className: run.halo.app.notification.EmailNotifier
  extensionPointName: reactive-notifier
  displayName: "邮件通知器"
  description: "支持通过电子邮件向用户发送通知"
---
apiVersion: plugin.halo.run/v1alpha1
kind: ExtensionDefinition
metadata:
  name: search-engine-lucene
spec:
  className: run.halo.app.search.lucene.LuceneSearchEngine
  extensionPointName: search-engine
  displayName: "Lucene 搜索引擎"
  description: "Halo 自带的本地搜索引擎"

从中可以提炼出 api/ 契约与 application 实现之间的映射规律

  • YAML 中 spec.className 指向的类(如 run.halo.app.security.authentication.login.UsernamePasswordLogoutHandlerrun.halo.app.theme.dialect.TemplateGlobalHeadProcessor)大多实现或消费了 api/ 中的接口(如 AdditionalWebFilterTemplateHeadProcessor);
  • extensionPointName 声明它挂在哪个扩展点上(如 reactive-notifiersearch-enginetemplate-head-processorattachment-handlercomment-subject),扩展点的注册元数据见 extensionpoint-definitions.yaml
  • 因此,当你在 api/ 中新增"扩展点接口",通常要同步做三件事:① 在 api/ 完成契约类型;② 在 extensions 目录ExtensionDefinition 元数据;③ 在 ui/ 侧对齐该扩展点的管理界面或渲染逻辑。这正是 AGENTS.md 强调"更新 extension definitions 时必须 application 与 ui 一起改"的原因。

同目录下还存放着角色模板(role-template-post.yamlrole-template-theme.yamlrole-template-uc-attachment.yaml 等)与 authproviders.yamlnotification.yamlsystem-setting.yaml,它们共同构成系统启动时加载的"声明式配置面",与 api/ 中的 Java 契约面互为表里。

评审红线:为什么 API 变更需要显式批准

api/AGENTS.md 的 Boundaries 一节给出了全篇最强的一条治理约束:

Public API changes in api/ require explicit approval — treat them as affecting every plugin built against Halo.

这一条是前面所有约定的收口。在协作实践中,它意味着:

  • 涉及 api/ 中公开类型、方法签名、扩展点接口、资源字段的改动,应在动手前先说明动机与替代方案,等待明确批准,而不是直接随功能 PR 一起合并;
  • 评审人应把 api/ 变更视为"面向所有未知插件"的破坏性风险来评估,而不仅是当前仓库内的应用与前端能否编译通过;
  • 结合仓库根目录 AGENTS.md 的 Cross-Module Rules("Ask before public API changes, new dependencies…"),这类变更通常还需要与版本号策略联动——当前仓库版本号 2.26.0-SNAPSHOT 记录在 gradle.properties 中,由 platform/application/ BOM 与 gradle/libs.versions.toml 统一约束依赖版本,任何版本能力层面的承诺都应以此为准。

源码级延伸:api/ 里值得深入研读的五个设计骨架

围绕 api/ 的目录与测试,可以再给出几条帮助你快速上手的源码阅读线索(均只涉及查看与理解,不需要改动仓库):

  1. 自定义资源类型如何定义:从 core/extension/content/Post.javaCategory.java 入手,配合 CategoryTest.javaPostTest.java,可以理解 GVK(GroupVersionKind)、Metadata 注解与校验规则在类型上的写法;测试即最佳使用范例。

  2. 扩展客户端与查询 DSLextension 下的 ReactiveExtensionClientListOptionsPageRequestImpl 配合 ListOptionsTest.javaPageRequestImplTest.javaindex/query 下的条件类可参考 QueriesTest.javaLabelSelectorTest.java

  3. 控制器(Reconciler)框架extension/controllerControllerBuilder 用于构建声明式控制器,测试侧 ControllerBuilderTest.java 展示了如何装配与验证。

  4. 安全过滤器扩展面security 下的 BeforeSecurityWebFilterAuthenticationSecurityWebFilterAnonymousAuthenticationSecurityWebFilter 等构成了可插拔的过滤器链;工具侧可参考 HttpSecurityUtilsTest.java

  5. 主题与插件扩展点:theme 包的 FinderTemplateHeadProcessor 对应插件主题渲染时的挂载点,plugin 包的 SettingFetcher/ReactiveSettingFetcher 是插件读取配置的标准入口——它们也是 YAML 中 extensionPointName 对应契约的源头。

建议按"先读一个资源类型 + 它的测试,再读客户端与查询 DSL,最后看控制器与扩展点"的顺序,从 api/ 的最小闭环快速建立起对整个 Halo 扩展机制的完整认知。

小结

api/ 模块的设计哲学可以概括为三句话:类型在 api、schema 在 application、体验在 ui加法优先、破坏需批每一次公开变更都以"影响所有插件"为前提来评估。掌握了 api/AGENTS.md 中 Scope、Commands、Conventions、Boundaries 四节的管理思路,再结合 api/build.gradle 的工程配置与 extensions 目录 的 YAML 声明,你既能为 Halo 贡献契合其扩展机制的新能力,也能在本地或 CI 中稳定复现构建、格式化与测试结果——这才是这套 API 库能够长期作为生态兼容基石的原因所在。

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

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
898
5.82 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
921
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.8 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
596
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
519
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
391