插件契约 v2
适用版本:OpenVideoAPI 26.10.0+。v2 完全向后兼容——v1 的字段、方法、事件、前端注入点原样保留,v2 全部为增量。 权威契约为主仓库根目录的
PLUGIN-CONTRACT.md;参考实现:lib/plugin.js(PluginManager)、src/services/plugins.js(装配)、src/routes/plugins.js(HTTP 端点)、plugins/openvideo-plugin-demo1.1.0(全能力演示)。
manifest 新增字段
{
"openvideoPlugin": {
"deps": { "openvideo": ">=26.0.0", "plugins": { "openvideo-plugin-otp": "^1.0.0" } },
"hooks": { "install": "onInstall", "enable": "onEnable", "disable": "onDisable", "uninstall": "onUninstall", "update": "onUpdate" }
}
}- deps(可选):
deps.openvideo为主程序版本范围(semver:>=、^、1.x、*、||多选);不满足时加载报主程序版本不满足: 需要 X,当前 Y。deps.plugins为插件依赖表,未安装报依赖插件未安装、版本不符报依赖插件版本不满足;依赖存在但未启用时自动递归启用。 - hooks(可选):声明生命周期钩子,值为主模块导出的函数名(也可直接挂
hooks子对象)。不写即无钩子,与 v1 完全一致。
生命周期与钩子
| 阶段 | 时机 | 说明 |
|---|---|---|
| load | 启用时 | 校验 deps → 注入服务 → apply(ctx, config);成功后广播 plugin:loaded {name,version} |
| unload | 停用时 | dispose 链(事件、cron、静态挂载、页面注销、i18n 回滚)→ 广播 plugin:unloaded |
| install | npm 安装完成后 | 运行 hooks.install |
| enable / disable | load 成功后 / unload 前 | 运行对应钩子 |
| uninstall | 停用后、npm 卸载前 | 运行钩子;随后清空该插件在 plugin_settings 的设置 |
| update | npm 更新后、重载前 | 运行 hooks.update |
钩子执行规则:每次重新 require 插件模块;收到一次性 ctx(注册的资源随钩子结束自动失效);同步/异步异常与 15 秒超时都只记日志,绝不中断管理操作。
ctx 新增能力
// HTTP 客户端扩展(默认超时 15s)
await ctx.http.get(url, opts); // 原始 Response
await ctx.http.text(url, opts); // 2xx→文本,否则 null
await ctx.http.request(url, { method, headers, body, timeout }); // 完整控制
// 静态资源目录(防目录穿越,dispose 自动卸载)
ctx.static('/plugins/xxx-assets', 'lib/client/assets');
// 自定义页面(auth:true 未鉴权时 302 到后台路径;返回注销函数)
ctx.pages.register({ route: '/plugin/xxx/hello', file: 'lib/client/page.html', title: 'Hello', auth: false });
// 定时任务(间隔下限 500ms;异常只记日志;dispose 自动清理)
const cancel = ctx.cron.every(5000, fn);
const cancel2 = ctx.cron.at(Date.now() + 60000, fn);
// 插件私有持久化设置(kv 隔离,单值 ≤100KB、≤200 键,卸载自动清空)
await ctx.settings.set('key', value);
// 结构化日志(环形缓冲,GET /api/admin/plugins/logs 可查)
ctx.logs.info('message', detail);
// 词条注入(公共聚合端点 GET /api/plugins/i18n?locale=zh[&plugin=xxx])
ctx.i18n.add('zh', { 'xxx.hello': '你好' });
ctx.i18n.t('xxx.hello', 'en');
// 事件总线增强
const off = ctx.bus.on('ev', fn); // 同 ctx.on
ctx.bus.once('ev', fn); ctx.bus.off('ev', fn);
ctx.bus.emitTo('openvideo-plugin-otp', 'ev', payload); // 定向投递
ctx.bus.events();
// 存储命名空间(实际表名 xxx__kv,≤48 字符)
const ns = ctx.model.namespace('xxx');
ns.define('kv', { primary: 'id', fields: { ... } });唯一行为差异:ctx.on 现在返回取消函数(v1 返回原 handler;官方插件未依赖该返回值)。
主程序广播的事件
| 事件 | 载荷 |
|---|---|
danmu:send | {vid,text,color,type,time,author} |
video:created | {vid,url,source}(source = `map |
video:saved / video:deleted | {vid,url,source:'admin'} / {vid} |
admin:login-ok / admin:login-fail | {username,ip} / {username,ip,reason} |
plugin:loaded / plugin:unloaded | {name,version} |
ready / before:restart | — |
任何 handler 异常都被捕获并记日志,不影响主流程。
三层错误隔离
- 路由层:
ctx.router所有方法自动包裹每个 handler——旧实例路由热重载后自动失效(next());异常记日志并返回{code:1,msg:'插件路由异常: …'},绝不抛回主进程。 - 钩子层:15s 超时 + 全捕获,只记日志。
- 事件层:
emit逐 handler try/catch;cron 异常只记日志。
前端注入点(v2 增补)
| 注入点 | 声明 / 用法 |
|---|---|
| 自定义页面 | ctx.pages.register(...) — 任意路由整页 HTML,可要求管理员鉴权 |
| 静态资源 | ctx.static(mount, dir) — 任意挂载路径的目录服务 |
| i18n 词条 | ctx.i18n.add(locale, dict) — GET /api/plugins/i18n 聚合拉取 |
| 后台 / 播放器 / 登录页 | client.admin / client.player / client.login(v1 保留) |
客户端脚本经 GET /api/plugins/client/:scope/:pkg/* 下发(admin scope 需鉴权)。
相关端点
- 管理端(v1 不变):
GET /api/admin/plugins及 install / toggle / config / uninstall / update / market / logs。 - 公共端 v2 新增:
GET /api/plugins/pages、GET /api/plugins/i18n?locale=&plugin=。
安装与热重载
npm 安装(POST /api/admin/plugins/install)或本地目录放入 plugins/ 自动发现(默认停用);状态存 data/plugins.json;OPENVIDEO_PLUGIN_DIR / OPENVIDEO_DATA_DIR 可覆盖目录。OPENVIDEO_DEV=1 时 watcher 监听插件目录(400ms 防抖,不可用时降级 1s mtime 轮询),已启用插件的本地源码变更自动 disable→enable。
兼容性
v1 插件(demo 1.0.x、otp 1.0.x、ip-ban 1.0.x、embed-subtitle 1.0.5)在 v2 下无需修改即可运行。deps 与 hooks 均为可选声明。
下一步
- ctx API 参考 — v1 + v2 完整 API
- 插件开发指南 — 从零写一个插件
- 主仓库
plugins/openvideo-plugin-demo1.1.0 — 自定义页面 / 静态资源 / cron / 事件 / 命名空间全演示