插件清单

在 AuraBoot 中,插件是交付的最小单元。无论你交付一个小型字典表、一个端到端的 CRM,还是一套完整的行业解决方案,包的形态都是一致的:一个目录,内含 plugin.json 清单文件以及一组待平台导入的资源文件。

清单不仅仅是名字和版本号。它是一份类型化声明,描述了插件贡献的每一项资源 —— 模型、字段、命令、权限、菜单、页面、命名查询等 —— 同时声明了插件向他人提供的能力,以及对他人的依赖。正是这份声明,让插件具备了升级安全可组合可被工具和 Agent 分析的属性。

为什么需要清单

没有清单,插件不过是一堆代码和 JSON。两个问题会立刻浮现:

  1. 升级不再安全。当新版本到来,平台无法分清哪些资源归插件所有,哪些是客户或别的插件加进来的,差异分析变成靠猜。
  2. 组合变得随意。一个想扩展 CRM 的插件,没有任何机器可读的方式声明"我需要 crm_account 模型"或"我提供 mfg:scrap 命令",跨插件契约只能依赖人类描述。

插件清单同时解决这两个问题。它充当贡献与能力的类型化注册表,平台在安装时读取它,并在升级、依赖解析、校验、打包、授权门控时反复使用它。

清单同时也是下游工具依赖的真值源:

  • 插件加载器使用 pluginTypedependenciesresourceDirs 决定加载顺序和导入哪些文件。
  • DSL 设计器使用 providesrequires 提示引用、在目标模型缺失时给出告警。
  • 权限系统读取 requiredPermissions 为角色预填权限。
  • 许可层根据 licenseModeplansfeaturesplanFeatures 控制行为。
  • AI Agent 通过清单推断如何建议配置,或者直接生成新插件。

因此,完备的清单不是打包的细节,而是让其他每一层都能把插件当作一等对象处理的契约。

三种插件类型

AuraBoot 在 pluginType 字段中识别三种插件类型。在选型阶段就明确类型,可以让打包、部署、评审都更简单。

config 插件只包含 JSON 资源文件。它通过声明式 DSL 定义模型、字段、命令、页面、权限、菜单、字典、命名查询。没有 Java 代码,没有编译产物。AuraBoot 中绝大多数插件 —— 包括大部分业务模块和模板 —— 都是 config 类型。只要你的行为可以通过命令、校验规则、副作用、BPM 流程声明式表达,就应选 config

hybrid 插件在声明式 JSON 之上叠加由 PF4J 运行时加载的 Java 代码。当你需要自定义命令处理器调用外部系统、需要数据提供者对接非关系型数据源、或者需要一个无法通过标准执行模式表达的专用动作时,就选这种类型。Hybrid 插件依然带有 config/ 目录;Java 代码以 JAR 形式提供,并在清单的 backend 块中通过 entryClass 指向入口类。只有当 JSON 表达不了相关操作时,才选 hybrid

solution 插件是行业垂直解决方案包。Solution 通常不引入自己的模型;取而代之的是声明对若干 config 或 hybrid 插件的依赖,补充跨插件的粘合资源,并为某个行业(制造、资产管理、项目交付等)交付预先调好的配置。当你要把一组精挑细选的插件打包成可部署的行业栈,而不是构建单个功能时,才选 solution

如果一个插件从 config 起步,后来需要自定义 Java 行为,可以通过添加 backend 块和 entryClass 提升为 hybrid。从 configsolution 的转换很少见;solution 通常一开始就按 solution 设计。

清单顶层字段

当前官方 hybrid 插件示例使用 backend.jarPathbackend.entryClassplugin-manifest.schema.json 中仍保留的旧字段名是待统一的 schema 债务;新文档和新插件应按实际官方插件写法 author。最重要的字段如下:

字段类型必填用途
pluginIdstring反向域名形式的全局唯一标识(例:com.auraboot.asset-management)。
namespacestring用于资源隔离的短前缀,在模型 code、命令 code、权限 code 中复用。小写,允许下划线。
versionstring语义化版本号,格式 MAJOR.MINOR.PATCH,可带预发布或构建元数据。
pluginTypeenumconfighybridsolution,默认 config
displayNamestring默认显示名;配合 displayName:zh-CNdisplayName:en 做本地化。
descriptionstring面向人的简介。
authorstring作者或组织名。
homepagestring文档或产品主页 URL。
minPlatformVersionstring所要求的 AuraBoot 平台最低版本。
dslVersioninteger插件页面定义所使用的 DSL schema 版本,默认 1
dependenciesarray必须先于本插件安装的其他插件列表,见下文。
providesarray本插件向他人提供的能力。
requiresarray本插件依赖他人提供的能力。
resourceDirsobject各类资源(模型、字段、命令等)的文件路径映射。
backend.jarPathstringhybrid 时是插件包内 JAR 路径。
backend.entryClassstringhybrid 时是Java 类全限定名,hybrid 插件入口类。
backendobjecthybrid 插件的 JAR 路径与入口类。
clientobject前端插件配置,包含暴露的组件。
importOptionsobject导入行为开关(冲突策略、校验、自动发布)。
requiredPermissionsarray插件安装时需要授予的权限 code 列表。
providedModelsarray插件将注册的模型 code 列表。
providedCommandsarray插件将注册的命令 code 列表。
licenseModeenumfreeplatformvendor,驱动授权行为。
plansarray本插件可用的订阅方案。
featuresarray可被方案门控的能力开关。
planFeaturesobject方案 code 到启用能力 key 的映射。

清单还允许在顶层直接内联各类资源数组(modelsfieldscommandspermissionsrolesmenuspagesprocessesnamedQueriesdictsmodelFieldBindingsbindingRules)。大多数插件把资源放在专用文件中,由 resourceDirs 指向;内联数组只适合极小的插件或测试夹具。

资源目录

resourceDirs 对象把"逻辑资源类型"映射到插件包内的文件或目录路径。加载器会遍历每个声明项并导入其中的资源。schema 识别以下类别:

  • models — 模型定义,描述业务实体、分类、是否抽象以及父模型继承。
  • fields — 可复用的元字段定义:数据类型、约束、UI 提示、查询与校验 schema。
  • modelFieldBindings — 将字段绑定到模型,并附带上下文级覆盖(必填、可见、可编辑、顺序)。
  • commands — 命令定义,覆盖 CRUD、状态流转、批量操作和自定义动作,以及处理器、副作用、校验规则与后置动作。
  • processes — BPM 流程定义,可以是 BPMN 文件,也可以是工作流引擎消费的设计器 JSON。
  • permissions — 权限 code,含分类、资源类型、动作、模块和数据范围。
  • roles — 预置 RBAC 角色,聚合一组权限 code。
  • menus — 导航树节点:路径、图标、父级 code、权限门控、页面绑定。
  • pages — DSL 驱动的页面定义:list、form、detail、dashboard 或 custom 类型,带有完整 schema。
  • reports — 报表子系统所用的报表定义。
  • namedQueries — 已存储的参数化查询,声明字段、默认排序、允许的算子。
  • dicts — 字典(枚举集合)定义,由 dict 类型字段引用。
  • data — 与元数据一并导入的种子数据。

每一项既可以是单个 JSON 文件(config/commands.json),也可以是 JSON 目录(config/commands/),凡是支持目录的类别均会递归遍历。

能力声明:dependenciesprovidesrequires

AuraBoot 区分两种表达跨插件关系的方式:

dependencies 是插件对插件的链接。它表达"插件 B 必须先于我安装"。可以是纯插件 ID 列表(匹配任意版本),也可以是带语义化版本范围的对象列表:

{
  "dependencies": [
    "com.auraboot.org-management",
    { "pluginId": "com.auraboot.crm", "version": ">=1.2.0" }
  ]
}

providesrequires 是能力级声明,表达"我提供这个模型 / 命令 / 查询 / 自动化 / api","我需要别人提供这一项"。能力有类型(modelcommandqueryautomationapi)和 code:

{
  "provides": [
    { "type": "model",   "code": "asset_unit" },
    { "type": "command", "code": "asset:retire" }
  ],
  "requires": [
    { "type": "model", "code": "org_employee", "optional": true }
  ]
}

解析器在安装与升级时使用这些信息:按 dependencies 做拓扑排序,校验每项必需能力都有某个已安装插件提供,任何非可选要求缺失即拒绝加载。可选要求缺失时,仅停用相关功能,不会让安装失败。

能力声明对工具也有帮助:DSL 设计器只在已安装的提供方之间建议引用,AI Agent 也能推断"哪个插件缺口阻断了端到端工作流"。

插件生命周期

无论首次安装还是升级,插件都会走过相同的阶段:

上传安装包
  -> 校验清单
  -> 解析依赖与能力
  -> 加载 Java 入口(仅 hybrid)
  -> 导入字典
  -> 导入字段与模型
  -> 导入模型-字段绑定
  -> 导入命令与 binding rules
  -> 导入权限与角色
  -> 导入命名查询
  -> 导入页面与菜单
  -> 导入流程(autoDeploy 时部署)
  -> 注册前端组件(若 client.enabled)
  -> 激活

升级时,平台会对比新清单与已安装状态。归插件所有的资源按 importOptions.conflictStrategy 协调处理:

  • error — 如果本地版本被改过,直接中止。
  • skip — 保留本地版本并记录冲突。
  • overwrite — 用新清单的版本覆盖本地版本。

新版本中被移除的资源不会被无条件删除;平台会标记为孤立项,由管理员决定是否退役。这样可以避免在引用仍存在时丢失数据。

走查:一个最小 config 插件

设想一个微型 Notes 插件,只含一个模型、一个命令、一个页面、一个菜单。

目录结构:

com.acme.notes/
  plugin.json
  config/
    models.json
    commands.json
    permissions.json
    pages/
      notes-list.json
    menus.json

plugin.json:

{
  "pluginId": "com.acme.notes",
  "namespace": "note",
  "version": "1.0.0",
  "pluginType": "config",
  "displayName": "Notes",
  "displayName:en": "Notes",
  "displayName:zh-CN": "便签",
  "description": "Lightweight personal notes",
  "author": "Acme",
  "minPlatformVersion": "1.0.0",
  "provides": [
    { "type": "model",   "code": "note_item" },
    { "type": "command", "code": "note:create" }
  ],
  "resourceDirs": {
    "models":      "config/models.json",
    "commands":    "config/commands.json",
    "permissions": "config/permissions.json",
    "menus":       "config/menus.json",
    "pages":       "config/pages"
  },
  "importOptions": {
    "conflictStrategy": "overwrite",
    "validateReferences": true,
    "autoPublishPages": true
  }
}

config/models.json 声明一个模型:

[
  {
    "code": "note_item",
    "displayName:en": "Note",
    "displayName:zh-CN": "便签",
    "modelType": "entity",
    "modelCategory": "document"
  }
]

config/commands.json 注册创建命令:

[
  {
    "code": "note:create",
    "modelCode": "note_item",
    "type": "create",
    "displayName:en": "Create Note",
    "displayName:zh-CN": "新建便签",
    "permissions": ["note.note_item.create"]
  }
]

config/permissions.json 声明权限:

[
  {
    "code": "note.note_item.create",
    "name:en": "Create Note",
    "name:zh-CN": "新建便签",
    "resourceType": "MODEL",
    "resourceCode": "note_item",
    "action": "CREATE",
    "module": "note"
  }
]

config/pages/notes-list.json 承载 list 页的 DSL schema(此处略),config/menus.json 把菜单挂上这页:

[
  {
    "code": "note.menu.list",
    "name:en": "Notes",
    "name:zh-CN": "便签",
    "path": "/notes",
    "type": 1,
    "pageKey": "note.notes-list",
    "permissionCode": "note.note_item.create",
    "orderNo": 100
  }
]

导入该包后,平台已经具备一个可从侧边栏抵达的可用 list 页,背后是一个类型化模型与一个创建命令。没有任何 Java 代码,也没有 React 代码。其它 CRUD 命令(update、delete、view)都可以通过在 commands.json 中追加条目并绑定到页面来添加。

Hybrid 插件扩展

有时 JSON 不够用。一个要调用外部计价服务的命令、一个从传感器网关流式取数的数据提供者、一个要产出二进制文件的动作,都没法靠声明式表达。Hybrid 插件用 Java JAR 与 JSON 并行的方式解决这个问题。

Hybrid 插件的清单增加 backend 块:

{
  "pluginType": "hybrid",
  "backend": {
    "jarPath": "backend/notes-extras-1.0.0.jar",
    "entryClass": "com.acme.notes.NotesExtrasPlugin"
  }
}

平台通过 PF4J 加载 JAR。entryClass 类声明插件的生命周期钩子(startstop)。JAR 内部可以注册 PF4J extensions,实现平台定义的 extension point —— 最常见的是 CommandHandlerDataProvider,或者自定义动作接口。

JSON 命令与其 Java handler 之间的桥梁是 binding rule。commands.json 中的命令带有 executionConfig.handler 字段,指向 handler 的 bean 或类名;bindingRules.json 文件(与 commands 一起在 resourceDirs 中注册)声明额外的绑定元信息,例如字段映射、事件 handler、触发条件。

一条关键打包纪律:binding rules 必须放在自己的文件里。commands.json 内联的 bindingRules 不会被导入。独立的 bindingRules.json 文件是唯一支持的位置,且必须经由 resourceDirs 注册。

Hybrid 插件启动时,平台会:

  1. 在隔离 classloader 中加载 JAR。
  2. 调用入口类的 start() 钩子。
  3. 发现 @Extension 注解类并向宿主注册。
  4. 通过 binding rules 把 JSON 命令的 handler 与 Java 实现连起来。
  5. 通过 SPI 缝(background accessor、credential resolver、tenant resolver 等)把插件的服务暴露给宿主代码。

停用与卸载则反向执行:拆除 classloader、反注册 extensions、清掉宿主中以插件资源为键的缓存。

解决方案包

Solution 插件把其他插件打成可部署的行业栈。清单将 pluginType 设为 solution,并用 dependencies 列出组成它的插件:

{
  "pluginId": "com.auraboot.solution.pcba",
  "namespace": "pcba",
  "version": "1.0.0",
  "pluginType": "solution",
  "displayName": "PCBA 制造解决方案",
  "dependencies": [
    { "pluginId": "com.auraboot.bom-standardization",     "version": "^1.0.0" },
    { "pluginId": "com.auraboot.manufacturing-execution", "version": "^1.0.0" },
    { "pluginId": "com.auraboot.quality-management",      "version": "^1.0.0" }
  ],
  "resourceDirs": {
    "menus":         "config/menus.json",
    "permissions":   "config/permissions.json",
    "pages":         "config/pages",
    "namedQueries":  "config/named-queries.json"
  }
}

Solution 依然带有自己的 resourceDirs,但这些资源通常是跨插件的粘合层:一个精心安排的着陆 dashboard、把各模块串起来的顶层菜单、跨多个模型的命名查询,以及为行业典型角色调好的权限与角色。

Solution 与普通插件有三点差异:

  • 部署。安装 solution 会传递安装它所有依赖,卸载亦对称。
  • 升级工具。Solution 升级会对所有依赖统一编排,平台拒绝半成品状态。
  • 授权姿态。Solution 通常是售卖授权的单位,plansfeaturesplanFeatures 一般声明在 solution 这一层,而非每个组成插件上。

如果你只是要交付单个功能,做 config 或 hybrid 插件即可。只有当你确实在打包一组精选的行业栈时,才动用 solution

校验与门禁

每个插件导入都必须通过两道门。

静态清单校验是预检。平台用 JSON Schema 解析 plugin.json、遍历 resourceDirs 确认每个引用的文件存在、检查内联资源是否合规,并暴露常见的编写错误:

  • S-PAGE-LABEL —— 某按钮或列的 label 不规范或缺失,可能在 UI 上泄漏 raw code。
  • S-PAGE-FORM-REQUIRED —— 某个必填字段没有在绑定和 form schema 中双重声明。
  • S-PAGE-TABLE-DICT —— list 列引用了一个未声明的字典 code。
  • S-PAGE-BUTTONS 为空 —— 工具栏 block 中没有按钮。
  • S-EXT-HANDLER —— binding rule 指向了插件并未注册的 Java handler。

静态检查通过是必要但不充分的。

服务端导入是权威门禁。平台的插件导入 API 解析包、解决依赖与能力、在单个事务内按拓扑顺序导入每项资源、运行跨资源校验(引用、权限完整性、页面-模型一致性、命令-权限覆盖)。只有当导入 API 返回 success: true 时,插件才算真正安装成功。

日常开发中,尽早、频繁跑静态检查,把导入 API 当作最终门禁。静态检查能拦下大部分书写错误;导入 API 能拦下只有当所有资源一起加载时才暴露的结构性错误。

企业版扩展

AuraBoot 企业版发行在清单之上为"把插件当作产品交付"的组织提供额外能力:带签名包与多版本通道的私有 Marketplace、按租户对 plansfeatures 做门控的 Entitlement 服务、带分批灰度和一键回滚的解决方案升级工具,以及多租户部署中谁可发布、谁可安装、谁可更新插件的策略管控。插件清单本身保持不变,这些行为在其之上叠加。

下一步