Files
sub2api-add/docs/BUSINESS_PLUGIN_FRAMEWORK_V1.md
T
Qiufeng 5feae3ad41
Business Plugins CI / check (plugin-admin) (push) Successful in 3m13s
Business Plugins CI / check (subscription-admin) (push) Successful in 1m41s
chore: initialize standalone business plugin repository
2026-08-27 23:36:08 +08:00

11 KiB
Raw Blame History

Sub2API 独立业务插件框架 V1

状态:Accepted Contract / V1 参考实现已完成本地验收

本文定义与 Sub2API Core 解耦的通用业务插件框架。业务插件是独立服务、独立端口、独立版本和独立 UI;它可以通过现有管理员自定义菜单嵌入 Core,也可以在新窗口运行。订阅管理只是一个可选业务插件,不能成为框架后台、Core 热路径或插件生命周期的固定组成部分。

1. 目标

V1 需要提供一个独立的插件控制面,负责:

  • 展示已登记、已安装和可升级的业务插件;
  • 校验包清单、发布者签名、文件哈希和 Core 兼容范围;
  • 安装、启用、停用、健康检查、升级、回滚、卸载和配置插件;
  • 保存插件版本、服务地址、运行状态、菜单声明和操作审计;
  • 通过 Core 现有管理员鉴权复用操作者身份;
  • 将已启用插件的管理员菜单注入 custom_menu_items;
  • 让每个业务插件仅通过自己的 BFF 调用 Core 明确允许的 API。

V1 不改变 Core Go/Vue、数据库迁移、现有鉴权、前端路由或 .s2plugin transport ABI。控制面自己的注册表、安装目录、进程和配置存储属于独立服务;它不连接 Core PostgreSQL、Redis 或宿主业务表。

2. 与现有插件的关系

类型 现有 .s2plugin transport Business Plugin V1
运行方式 Core 子进程 + gRPC 独立服务 + HTTP/BFF
能力 openai.oauth.outbound_transport.v1 由清单声明的业务能力
生命周期 Core PluginManager 独立 Plugin Control Plane
UI 配置 iframe + UI Bridge 业务后台/用户工具页面
数据边界 Core 负责账号转发与计费 Core 负责权威业务数据,插件只读/投影
菜单 Core 固定插件管理页 custom_menu_items 管理员入口

现有 .s2plugin 的 TransportPlugin、清单 schema 和 capability 校验保持不变。业务插件不能仅通过声明一个新 capability 假装获得 HTTP 路由、数据库、支付或订阅权限。

3. V1 拓扑

管理员浏览器
    │ Core 登录后的管理员菜单
    ▼
Core /custom/<menu-id>
    │ sandbox iframe 或新窗口
    ▼
反向代理 /extensions/<plugin-id>/
    ▼
Business Plugin Control Plane
    ├─ 插件目录、签名、版本和状态
    ├─ 独立进程 supervisor
    ├─ 管理员会话和审计
    └─ 插件 BFF / Core API Adapter
            │ 仅发送服务端 Bearer Core JWT + X-Request-ID
            ▼
Sub2API Core 现有鉴权、Admin API 和领域账本

控制面可以托管插件进程,也可以把进程交给 systemd、容器或 Kubernetes;无论采用哪种 supervisor,控制面都必须能读取健康状态并保留旧版本回滚点。停用时先撤销自有菜单,再将托管进程置于有界终止流程(SIGTERM,最多等待 10 秒后强制结束);反向代理负责停止新请求,外部服务只做健康探测并由其部署者负责停机。

4. 角色和权限

  • plugin_admin:安装、启停、升级、回滚、卸载、配置和菜单注入。
  • plugin_operator:查看状态、日志摘要和健康诊断,不改变包或凭据。
  • plugin_readonly:只读查看已登记插件。

V1 的控制面只允许 Core role=admin 登录。插件不创建第二套 Core 用户表;插件会话只保存 plugin_id、admin_user_id、角色、会话版本和过期时间。UI 隐藏按钮不等于授权,控制面和 Core API 均须重新校验权限。

5. 管理面契约

控制面内部 API 的最小集合:

GET  /healthz
POST /login
POST /login/2fa
POST /logout
GET  /api/me
GET  /api/plugins
GET  /api/plugins/{id}
POST /api/plugins/{id}/install
POST /api/plugins/{id}/enable
POST /api/plugins/{id}/disable
POST /api/plugins/{id}/upgrade
POST /api/plugins/{id}/rollback
POST /api/plugins/{id}/uninstall
GET  /api/plugins/{id}/config
PUT  /api/plugins/{id}/config
GET  /api/audit
POST /api/menu-items/preview
POST /api/menu-items/apply

所有写请求要求 CSRF、操作者会话和幂等键。幂等指纹由服务端根据方法、路径、查询和请求体计算,失败操作也保留终态,重复请求不会重新执行。安装、启用、升级、回滚和卸载必须返回 operation ID,并可通过插件详情查询最终状态。控制面不把上述路径注册到 Core,也不声称 Core 已存在 /api/v1/plugin-host/*。

6. 插件生命周期

discovered -> verified -> installed -> disabled -> starting -> healthy
                                      │             │
                                      │             └── error
                                      └── incompatible

healthy -> draining -> disabled
healthy -> upgrading -> healthy
healthy -> rollback_pending -> healthy
  • discovered:目录或清单被发现,尚未验签。
  • verified:清单、签名、哈希和兼容性通过。
  • installed:版本包已安全写入 staging 并原子切换。
  • disabled:已安装但不接收业务请求,菜单默认隐藏。
  • starting:进程启动、端口和健康检查进行中。
  • healthy:健康端点、就绪端点和(若响应提供)版本检查通过。
  • draining:菜单已撤销,托管进程正在执行有界终止;在途请求由插件进程或反向代理按部署约定处理。
  • upgrading / rollback_pending:新旧版本并存检查,只有健康版本成为 active。
  • error:进程、健康、配置或 Core 合约失败,默认 fail-closed。
  • incompatible:当前 Core baseline 或协议不满足清单要求。

已启用版本不能被原地覆盖。升级先安装新 revision、执行健康和契约检查,再原子更新 active revision;至少保留一个可回滚 revision。卸载只能作用于停用插件,不删除 Core 数据。控制面重启时重新校验 healthy/enabled 插件:托管 command 重新启动 active revision,外部服务重新探测 service_url;探测失败统一标记 error 并清空不可用端点。backend.command 与外部 service_url 是互斥运行模式,升级不会继承不属于新模式的旧端点。

7. 安装和包安全

安装包必须包含清单和 UI;若插件由控制面托管进程,再额外包含清单声明的服务文件:

manifest.json
signature.json
ui/index.html
ui/assets/...

外部服务模式允许省略 service/ 文件,但清单不得声明 backend.command,且启用前必须在控制面配置经过校验的 service_url。托管进程模式 必须声明 backend.command,并将该路径及二进制哈希放入包内。

控制面必须拒绝绝对路径、父目录跳转、重复条目、符号链接、未声明文件、超大文件和不匹配哈希。签名使用 Ed25519,签名覆盖 manifest.json 原始字节;清单中的 SHA-256 覆盖服务文件和 UI。发布者私钥不进入仓库、包、服务器或日志。

安装使用临时目录和原子 rename;失败不得破坏 active revision。包来源默认是管理员上传或受控本地目录,V1 不自动从互联网下载任意包。

8. Core API Adapter

每个插件清单声明精确的 method + path allowlist。控制面或插件 BFF 只能调用这些路径:

POST /api/v1/auth/login
POST /api/v1/auth/login/2fa
POST /api/v1/auth/refresh
POST /api/v1/auth/logout
GET  /api/v1/auth/me
GET  /api/v1/settings/public
GET  /api/v1/admin/<declared-read-endpoint>

<declared-read-endpoint> 只是文档占位符,实际清单必须列出具体路径、查询参数、分页上限和响应字段。浏览器永远不接触 Core JWT、refresh token 或 Admin Key;所有出站请求由服务端添加 Authorization: Bearer ... 和统一 X-Request-ID。

Core 返回 401 时,一次用户请求最多 refresh 一次;并发 refresh 必须按插件会话串行化。403 不重试,429 按 Retry-After 有上限退避,5xx 只重试明确幂等操作。响应按 DTO 或敏感键规则脱敏,不能把 token、密码、Cookie、secret 或完整凭据透传给 UI。

9. 会话和嵌入

插件登录调用 Core 现有 /auth/login、按需 /auth/login/2fa,再调用 /auth/me 校验管理员角色。Core token 只存插件服务端会话,浏览器只持有 HttpOnly、Secure、SameSite Cookie 和插件 CSRF token。

Core 的自定义页面当前使用 sandbox iframe,且不会自动继承 Core localStorage 登录态。因此 V1 必须同时提供新窗口入口;iframe 首屏显示插件登录页是已知行为。真正无感 SSO 需要 V1.1 的一次性 code/state 或受控 postMessage 交接,不得把 JWT 放在 URL。

菜单注入使用 Core 现有 custom_menu_items:

{
  "id": "DOMAIN_PLUGIN_ID",
  "label": "DOMAIN_PLUGIN_LABEL",
  "url": "https://CORE_ORIGIN/extensions/DOMAIN_PLUGIN_ID/",
  "visibility": "admin",
  "sort_order": 200
}

控制面只能创建和更新自己声明的 ID,保留其他管理员菜单;应用前展示 diff 并记录审计。停用或卸载时先隐藏/移除自己拥有的菜单项,再停止进程。

10. 数据归属

Core 始终是用户身份、余额、订阅、订单、用量、权限、计费和审计的权威来源。控制面只保存插件包、revision、状态、服务配置、菜单声明、会话和操作索引。业务插件可以保存可删除、可重建的 projection,但 projection 不得作为 Core 网关放行、扣费或权限判断依据。

11. 安全和可运维性

  • 监听地址默认 loopback,生产通过 HTTPS 反向代理暴露。
  • 插件进程使用独立低权限账号/容器、最小文件权限和出站网络 allowlist。
  • 配置 secret 使用服务端加密存储或 secret manager,UI 只显示 configured/rotatable,不回显原值。
  • 日志只记录插件 ID、revision、操作者、operation ID、资源 ID、request ID、状态和耗时。
  • 进程崩溃、健康失败、Core 不兼容或签名错误均 fail-closed,不静默切换到未验证版本。
  • 停用、升级、回滚和卸载必须可重复执行;Core 数据不随插件卸载删除。

12. 分阶段实施

Phase 0:契约冻结

冻结 manifest、签名、revision、控制面 API、状态机、反代路径、菜单字段、会话属性和 Core API allowlist。

Phase 1:通用控制面

实现管理员登录、插件目录、包校验、安装/启停、健康检查、配置加密、审计、菜单 preview/apply、systemd/container 适配和回滚骨架。

Phase 2:业务插件适配

提供 DOMAIN_PLUGIN_ID 级别的 SDK/模板和契约测试。订阅管理作为首个独立业务插件接入,只实现自身领域页面和 Core 只读 API,不改变控制面。

Phase 3:生产增强

评审多实例共享状态、短时 Plugin Access Token、无感 SSO、Core Host Adapter、远程 registry、灰度升级和跨节点 drain;这些能力需要单独版本和安全评审。

13. 非目标

  • 不把业务插件注册为现有 OpenAI OAuth transport。
  • 不把任意业务路由、数据库、支付、余额扣款或每请求计费放进控制面。
  • 不创建 Core 用户表、插件版账本或绕过 Core 鉴权。
  • 不通过 URL、iframe、LocalStorage、HTML、日志或下载文件传递凭据。
  • 不承诺独立进程是操作系统级沙箱。