SYSTEM OK | ACTION CONTRACTS 247 DOMAINS 35 DEMO ENVIRONMENT 无需申请 | 服务热线 18926139835
首页/洞察/技术系列/插件化架构设计

插件化架构设计

插件化架构的实现要点集中在两处:契约的定义方式,以及模块的加载顺序。 本文说明 EAMX 的 PluginManifest 契约、PluginRegistry 的注册与聚合流程、 LifecycleBus 的观察与拦截两种事件模式,以及 Tier 0 至 Tier 3 分层所决定的加载与卸载影响范围。

作者 白杨 发布 2026-09-28 阅读 约 9 分钟 专题 技术系列

01微内核的两条设计原则

EAMX 的插件化架构遵守两条设计原则。其一,中心只做注册和调度: PluginRegistry 不介入任何业务逻辑,其职责限于记录哪些插件完成了注册、汇总各插件声明的权限、登记各插件使用的路由前缀。 其二,一切通过契约交互:插件只需实现 PluginManifest 接口,无需感知其他插件的存在,也不需要知道自身在系统中的位置。

这一做法与微内核架构一致,其价值在于影响面。核心的每一次变更都会作用于全部业务模块,因此核心保留的范围越小,变更所波及的范围越小; 业务逻辑分散在各插件内部之后,插件之间的故障不会相互扩散。系统的稳定性来自核心的克制。

规模口径如下:PluginRegistry 74 行,LifecycleBus 79 行,两处合计 153 行核心实现, 支撑 Tier 1 至 Tier 3 共 11 个插件模块的注册、路由挂载、权限聚合与定时任务。

行数本身不构成结论。上述两处实现之所以精简,原因在于它们只保留了两件事:把模块登记进来,以及在确定的时机把事件分发出去。 状态机、流程定义、界面配置与数据模型均位于插件内部,不占用核心的行数。

02PluginManifest 契约

每个插件在启动阶段提交一份 PluginManifest。契约字段按用途分为四组。

身份与版本:id 为该插件的唯一标识,例如 "equipment";name 为显示名称; version 采用语义化版本;description 为可选说明。

依赖与分层:dependencies 声明该插件所依赖的其他插件,构成加载顺序约束——所列依赖必须已完成注册; tier 声明该插件所处的层级,取值为 0 至 3。

生命周期钩子:lifecycleHandlers 为事件到处理器的映射,插件可在其中注册多个事件处理器。

资源注册:createRouter 返回一个 Express Router 实例; routePrefix 声明该实例挂载的路由前缀,例如 "/api/equipment"; permissions 以 CASL 权限定义的形式声明该插件涉及的动作与对象; navItems 提供前端导航项;cronJobs 声明定时任务。

表 1 · Tier 分层与卸载影响范围
Tier定位卸载与降级
Tier 0平台核心不可卸载。平台认证与租户能力位于该层
Tier 1基础业务(EAM 基础功能)影响范围最大,后续层级依赖此层提供的模块
Tier 2业务增强(可选)影响范围限于该层所提供的增强功能
Tier 3实验与社区可热拔插,加载失败不影响系统启动

Tier 决定的是卸载时的影响范围与降级策略,因此插件在提交契约时就已声明自身属于哪一层。 层级的作用是把模块之间的依赖方向固定下来,供启动阶段排序使用。

03PluginRegistry 的注册与聚合

register() 在一个方法内完成三项检查与登记,全部发生在启动阶段。

  • 幂等检查:同一 id 重复注册时直接抛出错误,以避免同一模块被两次登记。
  • 依赖检查:遍历 dependencies 中的每个标识,确认其已完成注册;未完成时抛出错误,并指出缺失的依赖名称。
  • 生命周期钩子登记:将 lifecycleHandlers 中的每个事件与处理器注册到 LifecycleBus,插件无需在其他位置重复声明。

三项检查的作用在于把配置错误留在启动阶段。在注册期失败,是一项可在测试环境解决的问题; 在运行期失败,则表现为某个业务动作在特定路径下失效,定位成本明显上升。

注册完成后,PluginRegistry 提供三处聚合能力。

  • mountAll(router) 遍历全部已注册插件,将 createRouter() 返回的 Router 按 routePrefix 挂载到父 Router;未声明 createRouter 的插件被跳过,其路由前缀同样不会出现在系统中。
  • getAllPermissions() 将所有插件的权限定义合并为一个数组,交由权限层统一裁决。全系统使用同一套 CASL 规则,插件因此无需各自实现判定逻辑。
  • getAllNavItems() 将所有插件的导航项合并,并按各导航项的 order 排序;未声明 order 的项按 99 参与排序,排在显式声明顺序的项之后。

聚合结果与插件数量无关:核心只做合并与排序,不解析插件所声明的业务语义。

这一节的权限聚合结果可以核对

权限聚合决定的是工具清单的可见范围:动作契约共 247 条,覆盖 35 个业务域。权限判定在服务端完成, 工具清单由服务端按账号权限生成,客户端拿到的清单即为服务端的授权结果,前端不存在可绕过的调用路径。 演示账号为全部试用权限,其清单覆盖全部动作。上述范围可由演示环境当场核对总数与业务域覆盖,无需申请。查看核验方式 →

04LifecycleBus:观察与拦截两种模式

LifecycleBus 承担插件与业务动作之间的事件分发。两种模式的差别集中在执行顺序与失败的后果上。

emit 为观察型事件:全部处理器并行执行,结果由 Promise.allSettled 收集; 单个处理器失败只记录日志,不抛出异常,也不阻断主流程。该模式适用于通知、日志与缓存失效一类动作, 其处理结果不应改变业务动作是否成立。

intercept 为拦截型事件:处理器按注册顺序串行执行,可以在执行过程中修改 payload,也可以中断整个动作。 实现方式是把当前 payload 依次传入处理器,处理器通过 intercept 回调提交对 payload 的修改,修改结果作为下一个处理器的输入; 任一处理器调用 abort 时,总线抛出 AbortError,动作终止。

表 2 · 两种事件模式的用例与行为
模式用例行为
emit(观察)通知、日志、缓存失效并行执行,失败仅记录日志,不阻断主流程
intercept(拦截)审批流、数据校验、权限注入串行执行,可修改 payload,可抛出 AbortError 中断

典型拦截场景:设备创建前,审批流插件检查该设备的审批策略;条件满足时流程继续,条件不满足时以 abort 中断,并给出中断原因。 该场景中,审批能力由插件提供,业务动作本身不需要知道审批规则的存在。

05插件示例:审批流插件

以审批流插件为例,可以看到一份完整契约的形态。其 id 为 approval,显示名称为审批流,版本为 1.0.0,tier 为 1, dependencies 声明依赖 auth——该字段同时决定了 auth 必须先完成注册。

permissions 声明两条权限:对 WorkOrder 的 approve 动作,以及对 PurchaseRequest 的 approve 动作。 routePrefix 声明为 "/api/approval",createRouter 在该前缀下注册三个端点: GET /pending 返回待办审批,POST /:id/approve 执行通过,POST /:id/reject 执行驳回。

lifecycleHandlers 在 equipment:beforeCreate 事件上注册处理器:设备价值超过 100000 元时调用 abort 中断创建流程, 中断原因为设备价值超过 10 万元、需要主管审批;设备价值未超过阈值时,处理器不作干预,创建流程按原路径继续。

该示例中,PluginRegistry 不需要知道审批流的存在。它接收的是一份符合契约的 manifest,注册流程与其余模块完全一致。 新增能力通过提交契约完成接入,核心代码不随之变更。

06分层与加载顺序

各层之间存在明确的依赖方向:Tier 1 的模块依赖 Tier 0 提供的平台能力,Tier 2 依赖 Tier 1 的模块,Tier 3 位于末端。

启动阶段按依赖关系完成拓扑排序,Tier 0 最先完成注册。任何前置模块未完成注册时,后续模块会在依赖检查处失败, 进程不会带着不完整的依赖关系继续启动。

Tier 1 至 Tier 3 的模块数如下:Tier 1 七个插件模块,Tier 2 三个,Tier 3 一个,合计 11 个。 Tier 0 的 auth 与 tenant 属平台核心,不参与模块计数。完整清单见下文「接口与实现片段」中的加载顺序片段。

Tier 3 的模块为实验性模块,以热拔插方式启用;加载失败时不阻断系统启动,影响范围限于该模块自身所提供的功能。

07边界与适用范围

该内核提供的范围可归纳为五项:登记插件身份、汇总权限声明、挂载路由、生成导航、分发事件。 业务状态机、流程定义、界面配置与数据模型均位于插件内部,不进入核心。

注册阶段只执行幂等检查与依赖检查两项判定,核心不对插件声明的业务语义作进一步校验,契约字段的准确性由插件自身承担; 因此模块清单与权限清单的可信度,取决于插件实现与评审流程。

数字口径:Tier 1 至 Tier 3 共 11 个模块,Tier 0 不参与计数;核心实现的行数按文件计量,见 表 3。 本文只说明注册、加载与事件分发的实现方式;接入通道、权限裁决规则与工具清单分别由 AI 原生与 Agent 接入 与 CASL 同构权限系统 说明。

表 3 · 核心文件索引(行数为该文件的实现口径)
文件行数职责
packages/core/src/plugin-registry.ts74插件注册、路由挂载、权限与导航聚合
packages/core/src/lifecycle-bus.ts79emit 与拦截两种模式的事件总线
packages/core/src/types.ts—PluginManifest 接口定义

08作者与依据

作者
EAMX 产品团队 · 产品与解决方案
依据
EAMX 插件化架构的实现记录:PluginManifest 契约定义、PluginRegistry 与 LifecycleBus 核心实现、Tier 分层与加载顺序
数据来源
核心文件行数口径(plugin-registry.ts 74 行、lifecycle-bus.ts 79 行);模块清单(Tier 1 至 Tier 3 共 11 个)
引用标准
ISO 55000 系列仅为方法论依据,不构成认证;认证对象是组织,而非软件
更新日期
2026-09-28
01接口与实现片段

契约、注册表、事件总线与加载顺序

下列片段取自实现记录,为便于阅读省略了与本文无关的日志与异常处理。片段中的字段名、事件名与方法名即为该实现中的名称。

PluginManifest 契约

interface PluginManifest {
  id: string;                      // 唯一标识,如 "equipment"
  name: string;                    // 显示名称
  version: string;                 // 语义化版本
  description?: string;
  dependencies?: string[];       // 插件依赖(加载顺序约束)
  tier: 0 | 1 | 2 | 3;             // 插件分层

  // 生命周期钩子
  lifecycleHandlers?: Partial<LifecycleHandlerMap>;

  // 资源注册
  createRouter?: () => Router;     // Express Router 工厂
  routePrefix?: string;         // 路由前缀,如 "/api/equipment"
  permissions: PermissionDefinition[];  // CASL 权限定义
  navItems?: NavItem[];         // 前端导航项
  cronJobs?: CronJobDefinition[];  // 定时任务
}

PluginRegistry 核心实现

class PluginRegistry {
  private plugins = new Map<string, PluginManifest>();

  register(plugin: PluginManifest): void {
    // ① 幂等检查:不允许重复注册
    if (this.plugins.has(plugin.id))
      throw new Error(`Plugin "${plugin.id}" is already registered`);

    // ② 依赖检查:所有 dependencies 必须已注册
    for (const dep of plugin.dependencies ?? []) {
      if (!this.plugins.has(dep))
        throw new Error(
          `Plugin "${plugin.id}" depends on "${dep}" which is not registered`
        );
    }

    // ③ 自动注册生命周期钩子
    if (plugin.lifecycleHandlers) {
      for (const [event, handler] of Object.entries(plugin.lifecycleHandlers)) {
        lifecycle.on(event as LifecycleEvent, handler);
      }
    }

    this.plugins.set(plugin.id, plugin);
  }

  // 将所有已注册插件的路由挂载到父 Router
  mountAll(router: Router): void {
    for (const [, plugin] of this.plugins) {
      if (!plugin.createRouter) continue;
      router.use(plugin.routePrefix ?? "/", plugin.createRouter());
    }
  }

  // 聚合所有插件的权限定义
  getAllPermissions(): PermissionDefinition[] {
    return [...this.plugins.values()].flatMap(p => p.permissions);
  }

  // 聚合所有导航项,按 order 排序
  getAllNavItems(): NavItem[] {
    return [...this.plugins.values()]
      .flatMap(p => p.navItems ?? [])
      .sort((a, b) => (a.order ?? 99) - (b.order ?? 99));
  }
}

LifecycleBus:观察与拦截

class LifecycleBus {
  private handlers = new Map<string, AnyHandler[]>();

  // 观察型事件:并行执行,单个失败不阻断主流程
  async emit<T>(event: string, payload: T, ctx: RequestContext): Promise<void> {
    const handlers = this.handlers.get(event) ?? [];
    const results = await Promise.allSettled(
      handlers.map(h => h({ payload, ctx }))
    );
    // 失败仅打日志,不抛异常
  }

  // 拦截型事件:串行执行,支持修改 payload 或中断(AbortError)
  async intercept<T>(event: string, payload: T, ctx: RequestContext): Promise<T> {
    let current = { ...payload };
    for (const handler of handlers) {
      await handler({
        payload: current, ctx,
        intercept: (modified) => { current = { ...current, ...modified }; },
        abort: (reason) => { abortReason = reason; },
      });
      if (abortReason) throw new AbortError(abortReason);
    }
    return current;
  }
}

插件示例:审批流插件

// approval-plugin.ts(简化版)
export const approvalPlugin: PluginManifest = {
  id: "approval",
  name: "审批流",
  version: "1.0.0",
  tier: 1,
  dependencies: ["auth"],
  permissions: [
    { action: "approve", subject: "WorkOrder" },
    { action: "approve", subject: "PurchaseRequest" },
  ],
  routePrefix: "/api/approval",
  createRouter: () => {
    const router = Router();
    router.get("/pending", listPendingApprovals);
    router.post("/:id/approve", approveRequest);
    router.post("/:id/reject", rejectRequest);
    return router;
  },
  lifecycleHandlers: {
    "equipment:beforeCreate": async ({ payload, abort }) => {
      if (payload.value > 100000) {
        // 超过 10 万元需要审批
        abort("设备价值超过 10 万元,需要主管审批");
      }
    },
  },
};

分层与加载顺序

Tier 0: auth, tenant            (平台核心,不可卸载)
    ↑
Tier 1: equipment, spare-part, work-order, fault-report,
        pm-plan, approval, notification
    ↑
Tier 2: purchase, inventory, report
    ↑
Tier 3: mockup                (实验性,可热拔插)

上述五个片段覆盖了本文所引用的全部实现细节:契约字段、注册流程、事件分发的两种模式、一份完整 manifest,以及各层的加载方向。 其余实现(业务状态机、流程定义、界面配置)位于各插件内部,不在核心范围内。

02相关产品能力

本文所述的实现,对应哪些产品能力

03相关文章

同专题与跨专题的延伸

上一级专题:技术系列——该专题总纲给出十一篇文章的阅读顺序,本篇为第 08 篇。

本文所述的实现可以逐项核对

153 行核心实现、Tier 1 至 Tier 3 共 11 个模块、该账号可见工具数由端点实时返回、契约字段与事件名—— 上述各项均无需采信本文陈述,可由演示环境自行核实。

信息中心 / 技术评估
关心扩展点定义与接入方式
设备科 / 资产管理员
关心模块启用后现场如何使用