Files
sub2api-add/plugins/subscription-admin

Sub2API Subscription Admin Business Plugin V1

这是一个可独立运行后端的管理员只读订阅业务模块,不是现有 .s2plugin transport 插件,也不是插件管理控制面。它不导入 Sub2API internal 包,不连接 Core 数据库,也不修改 Core Go/Vue、迁移、路由或 .s2plugin ABI。浏览器前端由 plugin-admin 统一 TDesign 控制面承载,订阅模块不提供第二个登录页或 Cookie。

生产/集成环境由通用 plugins/plugin-admin 控制面安装、启用和升级本插件;本插件不会预装,也不会成为控制面首页。只有健康检查通过并由管理员执行菜单预览/应用后,Core 管理员菜单才会出现“订阅管理”入口。直接运行本目录仅用于本地开发和契约测试。

本地启动

CORE_BASE_URL=http://127.0.0.1:8080 \
PLUGIN_HOST=127.0.0.1 \
PLUGIN_PORT=8091 \
go run .

本地后端可打开 http://127.0.0.1:8091/healthz 进行服务契约调试;生产浏览器入口应从 Plugin Admin 的订阅模块路由进入。生产环境应通过 HTTPS 反向代理,并设置 PLUGIN_COOKIE_SECURE=true。默认情况下本服务只暴露健康检查、就绪检查和交接页,不暴露第二套登录、Cookie 或 Core 数据 API。PLUGIN_STANDALONE_AUTH=true 仅在 PLUGIN_ENV=development 且监听地址为 loopback 时生效,生产必须保持关闭。

V1 范围

  • 由 Plugin Admin 统一完成 Core 管理员账号登录和 Core 2FA;普通账号统一拒绝。
  • 订阅模块复用控制面的 HttpOnly、SameSite 会话和写请求 CSRF 校验,不创建模块级登录会话。
  • Core token 只保存在插件服务端内存会话中,不进入浏览器、URL、HTML、LocalStorage、响应或日志。
  • 只读套餐、订阅列表、订阅详情和插件操作记录。
  • Core access token 失效时最多刷新一次;刷新失败会销毁插件会话。
  • 页面刷新会从插件会话恢复,并重新取得短期 CSRF token;服务端不会把 Core token 返回浏览器。
  • 登录会读取 Core 公开验证码配置并透传 Turnstile、腾讯、阿里云或 GeeTest 的验证结果;验证码本身仍由 Core 校验。
  • 余额购买、续费、撤销、退款和外部支付不在 V1,页面不渲染提交按钮。

Core API allowlist

插件服务端在 PLUGIN_STANDALONE_AUTH=true、PLUGIN_ENV=development 且 loopback 的本地兼容模式下仅调用这些明确路径;生产数据访问由 Plugin Admin 同源 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/payment/plans
GET  /api/v1/admin/subscriptions
GET  /api/v1/admin/subscriptions/{id}
GET  /api/v1/admin/users/{id}
GET  /api/v1/admin/users/{id}/subscriptions

列表请求只接受 page、page_size、limit、user_id、group_id、status、platform、sort_by、sort_order 参数。插件不会代理任意 URL,也不会调用尚不存在的 /api/v1/plugin-host/*。

Core 菜单与反向代理

控制面会依据清单中的菜单声明生成下面的 custom_menu_items 项;部署时无需手工写入订阅菜单:

{
  "id": "qiu.subscription-admin",
  "label": "订阅管理",
  "url": "https://CORE_ORIGIN/extensions/qiu.plugin-admin/admin/#/modules/subscription/overview",
  "visibility": "admin",
  "sort_order": 200
}

Core 自定义页面的 sandbox iframe 不会继承 Core localStorage 登录态,因此菜单应指向 Plugin Admin 的统一控制面入口;订阅模块本身不显示登录页、不创建 Cookie。不要把 JWT 放进 URL。

测试

go test ./... -count=1
node --check ui/app.js

main_test.go 覆盖 Core 路径 allowlist、查询参数过滤、管理员角色拒绝、插件 Cookie、Core token 不泄露、会话过期、请求 ID、登录限流和 2FA pending 一次性消费。浏览器验收脚本位于 test/browser-check.mjs,可使用本地 Mock Core 验证直连、子路径反代和三种视口。

生成可安装包

./package.sh

脚本生成 dist/qiu.subscription-admin.s2plugin,包内根文件名为 manifest.json,并包含清单声明哈希的 UI 文件。该模块采用外部服务模式: 安装后先独立启动 subscription-admin,再在 plugin-admin 的配置中填写 service_url(插件 loopback 地址),并为 Plugin Admin 设置 PLUGIN_PUBLIC_URL(例如 https://CORE_ORIGIN/extensions/qiu.plugin-admin)。 然后执行启用、健康检查和菜单应用。浏览器前端由 plugin-admin 统一挂载并共享 控制面会话,菜单固定跳转到 #/modules/subscription/overview,不会跳转到本服务 的登录页。生产环境必须把签名文件通过 SIGNATURE_FILE=/path/to/signature.json ./package.sh 放入包内,并将对应公钥 加入控制面受信发布者配置;未签名包仅限 development + loopback。

清单和发布

business-plugin-manifest.v1.json 是部署层清单,不由 Core 读取。生产发布应由独立 CI 签名并校验清单、版本、健康路径和兼容的 Core 版本;不要把发布私钥放入仓库或插件包。插件版本独立于 backend/cmd/server/VERSION。

发布构建必须显式启用签名门禁:

RELEASE_BUILD=true SIGNATURE_FILE=/secure/signature.json ./package.sh

脚本会把 signature.json 放入归档并生成同名 .sha256 校验文件;未签名包 仅用于 development + loopback。

已知限制

  • 模块后端是常驻服务,不需要每次使用后重启。Core access token 过期时,后端会 按需 refresh 并继续当前请求;只有 refresh 失效、Core 撤销管理员、会话 空闲超过 30 分钟或达到 8 小时绝对上限时才需要重新登录。
  • V1 控制面使用内存会话,控制面服务重启会要求重新登录一次;订阅模块不会 单独要求登录。多实例或跨重启免登录需将控制面会话存储替换为插件自有的 加密 Redis/共享会话服务。
  • 现有 Core 自定义 iframe 没有 token handoff,V1 不提供无感 SSO;真正 SSO 需要单独的 V1.1 Core 交接接口。
  • Core 当前套餐响应中的 features 可能是 JSON 字符串,UI 会兼容字符串和数组。
  • Core 开启验证码时,管理员必须先完成对应提供商的挑战并将结果填入登录表单;插件不保存验证码票据。