---
title: "插件开发"
description: "为指定应用或宿主公共数据域添加受信的接口、页面与生命周期能力。"
canonical_url: "https://docs.mcent.cn/qicore/plugin-development"
---
# 插件开发

> 为指定应用或宿主公共数据域添加受信的接口、页面与生命周期能力。

插件通过 `qicore-plugin-v1` 接入已有装配和生命周期流程。插件有自己的身份、版本和命名空间，不占用租户当前应用坑位，也不替换所属应用的处理器。

扩展代码是受信服务端代码。描述校验、权限检查和文件清单不会把任意第三方脚本变成安全沙箱；安装时仍须核实来源及对应发行材料。

## 目录结构

### SDK 最小模板

公开 SDK 的 `@qicore/application-sdk/templates/plugin-package/*` 提供以下文件：

```text
plugin-package/
├─ application.json     必需：插件身份、所属应用、命名空间和路由声明
├─ entry.mjs            必需：示例服务端入口与生命周期实现
└─ README.md            模板说明，不是运行时必需文件
```

这是最小服务端包样例，不是完整开发工程生成器。模板默认示范框架插件；应用插件应显式填写所属应用的业务 key，并按实际业务修改权限和处理器。

### 带后台的推荐开发结构

下面是独立插件工程的组织建议，源码位置由插件自己的脚本决定：

```text
notes-export/
├─ application.json                 必需：稳定的插件描述
├─ package.json                     必需：开发依赖、SDK 版本及开发/构建命令
├─ pnpm-lock.yaml                   必需：固定插件与 SDK 的实际依赖
├─ apps/
│  ├─ api/
│  │  ├─ src/entry.mjs               推荐：插件服务端入口源码
│  │  └─ prisma/                    可选：插件模型和不可变增量迁移源码
│  └─ admin/                        可选：独立 Nuxt 后台页面与组件源码
├─ scripts/
│  ├─ development.mjs               联合开发时提供，按 qicore.development 指定
│  └─ build.mjs                     推荐：生成服务端、迁移及后台运行产物
└─ tests/                           推荐：权限、作用域、撤权与生命周期测试
```

使用开发工作区时，由 `package.json` 的 `qicore.development.entry` 显式指向自己的开发脚本，并声明实际监听目录。SDK 不会自动生成这些业务源码、页面或脚本。开发入口和模式见[开发工作区](https://docs.mcent.cn/qicore/development-workspace)。

推荐树中的 Prisma 目录只是模型与迁移源码位置。有模型时，插件入口仍需返回经校验的 `database` 片段；开发脚本负责生成运行模型和包内迁移，宿主不会扫描这个源码目录自动注册模型。模型与物理表名保持全局唯一，具体格式见[生命周期与打包](https://docs.mcent.cn/qicore/lifecycle-and-packaging)。

### 与所属应用一起联调

框架插件 `applicationKey: "*"` 可以在零应用开发宿主装配。本文的 `notes-app` 插件则要求工作区同时装配其所属应用；仅从插件目录启动、没有加载所属应用时，会被 `PLUGIN_OWNER_MISMATCH` 拒绝。

先为所属应用和插件分别安装匹配 SDK 并实现各自的开发入口，然后在宿主工程显式加载两者：

```bash
pnpm dev --app /path/to/notes-application --plugin /path/to/notes-export --sdk-mode installed --state /path/to/private-dev-state --port 4300
```

宿主的 `--plugin` 与组件路径选择参数一起读取。也可以在插件工程使用已安装 SDK 的 CLI；下面假定其 `scripts.dev` 已配置为 `qicore dev`：

```bash
pnpm dev --host /path/to/official-qicore --app /path/to/notes-application --state /path/to/private-dev-state --port 4300
pnpm dev:stop --host /path/to/official-qicore --state /path/to/private-dev-state
```

SDK CLI 自动传入当前插件目录并选择 `installed` 模式，额外的 `--app` 会继续传给宿主，所以所属应用也能进入同一工作区。停止命令只传相同宿主与状态目录，不追加组件参数；开发装配不从生产安装目录自动补入所属应用。

### 发行包固定布局

开发源码经插件自己的构建流程转换为运行包；不要直接把整个开发工程压缩上传：

```text
插件包根/
├─ application.json                 必需：qicore-plugin-v1 描述
├─ release-manifest.json            必需：真实发行清单及对应发行材料
├─ entry.mjs                        服务端入口示例，实际由清单 entrypoint 指定
├─ migrations/                      可选：有迁移时提供
│  └─ YYYYMMDDHHmmss_create_notes.sql
└─ web/                             可选：未提供后台时省略
   └─ admin/                        声明 web.admin 时提供
      ├─ server/index.mjs           固定的后台 SSR 入口
      └─ public/                    固定的后台公开资源目录
```

`application.json` 和清单指定的服务端入口必须存在。若声明 `web.admin`，其 `entrypoint` 固定为 `web/admin/server/index.mjs`，`publicDir` 固定为 `web/admin/public`；SSR 入口必须进入完整文件清单，公开资产按实际产物交付。未提供后台时不声明该端，也不创建占位产物冒充已完成构建。

插件描述当前只允许 `web.admin`，不单独声明 `web.h5` 或 `web.miniProgram`。H5、小程序页面和组件由所属应用在构建时组合。完整包校验与发行流程见[生命周期与打包](https://docs.mcent.cn/qicore/lifecycle-and-packaging)。

## 描述文件

插件描述也使用 `application.json`。以下是仅提供后台状态接口的示例：

```json
{
  "format": "qicore-plugin-v1",
  "key": "notes-export",
  "packageKey": "plugin_notes_demo",
  "version": "1.0.0",
  "name": "笔记导出扩展",
  "applicationKey": "notes-app",
  "namespace": "notesExport",
  "pendingTasks": [],
  "routes": {
    "admin": [
      {
        "path": "status",
        "method": "GET",
        "permission": "status.read"
      }
    ],
    "consumer": []
  }
}
```

<table>
<thead>
  <tr>
    <th>
      字段
    </th>
    
    <th>
      约束
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        key / packageKey
      </code>
    </td>
    
    <td>
      插件业务身份与发行组件身份；更新、卸载、重装继续保留对应关系
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        applicationKey
      </code>
    </td>
    
    <td>
      所属应用的业务 key；<code>
        *
      </code>
      
       表示框架插件
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        namespace
      </code>
    </td>
    
    <td>
      2–32 位、首字母小写的字母数字名称；在目标应用内唯一
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        routes.admin
      </code>
    </td>
    
    <td>
      相对路径、HTTP 方法和权限声明
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        routes.consumer
      </code>
    </td>
    
    <td>
      相对路径与 HTTP 方法；业务资源和权益仍由插件核验
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        pendingTasks
      </code>
    </td>
    
    <td>
      自有未完成持久任务；没有时明确为空数组
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        web.admin
      </code>
    </td>
    
    <td>
      可选后台端；遵循应用的固定 SSR/public 布局和路由约束
    </td>
  </tr>
</tbody>
</table>

命名空间及所属应用是持久身份，不能用换路径、移出开发目录或卸载来释放后让另一包接管。

应用插件的可信发行元数据还必须绑定正确父应用及依赖版本。仅在描述中填写 `applicationKey` 不构成发行或授权依据。

## 路由与权限

后台入口为：

```text
/api/v1/admin/tenants/:tenantId/apps/:applicationKey/plugins/:pluginKey/<path>
```

消费者入口为：

```text
/api/v1/apps/:applicationKey/plugins/:pluginKey/<path>
```

例如示例插件后台的 `GET status` 对应 `/plugins/notes-export/status`。完整后台权限自动组合为：

```text
notes-app:plugins.notesExport.status.read
```

`definition.permissions` 声明插件内的 `status.read`，无需手工重复整个前缀。`permissionLabels` 可为这些权限提供角色编辑器中的显示名称；名称不会增加访问权。

相同 HTTP 方法下，相互重叠的固定/参数路径会被拒绝，例如 `items/:id` 与 `items/status`。后台页面不能覆盖宿主、所属应用或其他已装配页面。

描述符允许的 HTTP 方法包含 `PATCH`；当前控制台和消费者 SDK 的 `api()` 仅提供 `GET / POST / PUT / DELETE`，宿主当前跨域方法集合也未包含 `PATCH`。使用 `PATCH` 前须核对目标宿主和调用端支持，不能仅凭描述校验通过认定浏览器调用链已支持。

## 入口与处理器上下文

插件同样导出 `createApplication(serverSdk)`，返回 `format/version/definition/createRuntime`。`createRuntime(resources)` 收到的 `resources` 包含当前目标的 `applicationKey/pluginKey`；这些不是 `serverSdk` 的顶层字段。返回的运行时以自身插件 key 标识身份。

下面只展示 `createRuntime` 返回值中 `admin` 处理器的参数，不是完整插件入口：

```js
async function admin(context, request) {
  return {
    applicationKey: context.applicationKey,
    pluginKey: context.pluginKey,
    ready: true
  }
}
```

插件处理器是 `admin(context, request)` 或 `consumer(context, request)`，与普通应用的路径数组签名不同。

<table>
<thead>
  <tr>
    <th>
      <code>
        context
      </code>
      
       字段
    </th>
    
    <th>
      来源
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        session
      </code>
    </td>
    
    <td>
      宿主核验的当前后台或消费者会话
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        tenantId
      </code>
    </td>
    
    <td>
      当前请求的目标租户，经过范围检查
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        applicationKey
      </code>
    </td>
    
    <td>
      当前目标数据域
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        pluginKey
      </code>
    </td>
    
    <td>
      当前插件 key
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        params
      </code>
    </td>
    
    <td>
      已声明路由中 <code>
        :id
      </code>
      
       等动态段匹配结果
    </td>
  </tr>
</tbody>
</table>

请求体、查询和参数仍需由插件校验。所有私有数据操作继续使用完整的租户、应用和用户作用域。

宿主在调用处理器前后重新核验应用可用性、后台权限或消费者范围，并再次核验插件权益。敏感写入还应在共享事务内校验关键状态；处理器外的二次检查不能撤销已经发生的外部操作。

## 框架插件与应用插件

`applicationKey: "*"` 的框架插件可装配到各应用数据域；零应用时使用 `system` 公共数据域。不同目标数据域有相应运行实例，请求继续受当前租户和应用范围约束。

`system` 不进入已安装业务应用目录，也不占应用坑位。它的后台权限使用宿主 `tenant:` 公共权限前缀，不能用于绕过某个业务应用的授权。

插件可使用宿主提供的数据库和已绑定作用域的存储。不能关闭宿主数据库，也不能覆盖所属应用的身份、支付适配器和基础处理器。

## 生命周期与撤权

运行时必须提供 `connect / initialize / tick / admin / consumer / health / close`，并以自身插件 key 返回 `key`。

- `connect` 接收公共支付和订阅等服务，`initialize` 准备插件自身运行资源。
- `tick(enabled)` 接收宿主是否允许新业务与插件当前权益的组合结果。
- `tick(false)` 时停止发起新业务，同时安全处理已有回执、补偿和恢复义务。
- `health()` 必须明确返回 `true`；真实插件应检查自身必要依赖。
- `close()` 只释放插件拥有的资源；组合关闭时插件先于基础应用退出。

撤销插件权益会阻止新请求和菜单访问，但不会自动删除未完成任务或已有数据。卸载还会检查依赖、任务与恢复引用。

## 前端与交付

插件后台页面通过现有 `web.admin` 契约打包；H5、小程序页面和自定义组件由所属应用在构建时组合，不在运行中从任意地址导入插件脚本。

插件完整包使用 `plugin_update` 类型，并执行对应发行策略、完整文件清单、兼容范围、备份、迁移与健康提交校验。某个产品的普通发行例外不会自动适用于其他插件。

继续阅读[应用描述契约](https://docs.mcent.cn/qicore/application-manifest)、[服务端 SDK](https://docs.mcent.cn/qicore/server-sdk)和[生命周期与打包](https://docs.mcent.cn/qicore/lifecycle-and-packaging)。


## Sitemap

See the full [sitemap](https://docs.mcent.cn/sitemap.md) for all pages.
