安装位置与配置边界
所有插件路径相对安装目录下顶层 runtime/,使用正斜线。自定义内容放在 runtime/plugins/,官方示例放在 runtime/assets/plugins/。官方框架、两个示例插件各自的清单和入口共 5 份资源会随更新包覆盖;需要长期保留的修改应复制到自定义目录后再加载。
安装目录/
└─ runtime/
├─ config/
│ └─ instance.json
├─ plugins/
│ ├─ framework.lua
│ └─ community-welcome/
│ ├─ plugin.json
│ └─ main.lua
└─ assets/plugins/ 官方资源,更新可能覆盖
在现有 runtime/config/instance.json 中合并以下片段,保留其他字段:
{
"plugins": {
"enabled": true,
"framework": "plugins/framework.lua"
}
}
这不是完整实例配置,不能用它覆盖原文件。不要编辑生成目录 runtime/configs。完整安装文件见快速上手。总开关 plugins.enabled 或框架入口路径改变后,使用现有实例管理流程重启服务端;框架内容、插件启停、权限、配置和源码在启用后可热重载。
管理接口连接方式
插件管理 API 挂在服务端已有的本机管理监听,使用 X-Admin-Token,要求 platform 管理权限。端口和凭据取实际实例的管理配置;它不是游戏端口、网关玩家接口或公开 Pages 站点接口。浏览文档不需要提供服务器凭据。
标准实例配置中的 server.adminListen 是管理监听,server.adminToken 是管理令牌。只在服务端本机运行查询,将令牌留在内存中;不要把实例配置、令牌、浏览器请求导出或命令录屏放进公共文档项目。
以下 PowerShell 示例从安装目录运行,读取本机现有配置,不修改配置,也不打印令牌:
$instance = Get-Content -LiteralPath './runtime/config/instance.json' -Raw | ConvertFrom-Json
$adminBase = 'http://' + $instance.server.adminListen
$adminHeaders = @{ 'X-Admin-Token' = $instance.server.adminToken }
Invoke-RestMethod -Method Get -Uri ($adminBase + '/local/plugins') -Headers $adminHeaders
Invoke-RestMethod -Method Get -Uri ($adminBase + '/local/plugins/catalog') -Headers $adminHeaders
若管理员启用了额外 RBAC 或不同的管理代理,应使用其分配的 platform 凭据和访问方式。不要为方便插件调试把本机管理端口改成公网监听。
三个 HTTP 端点
只有插件总开关启用时才注册这些接口。
| 请求 | 成功返回 | 插件 handler 的错误 |
|---|---|---|
GET /local/plugins |
200,当前状态 JSON | 非 GET:405,Allow: GET |
GET /local/plugins/catalog |
200,{api,events,hooks} |
非 GET:405,Allow: GET |
POST /local/plugins/reload |
无需请求体;200,切换后的状态 JSON | 加载、校验、初始化、取消、旧版本尚未退出等失败:409 文本;非 POST:405,Allow: POST |
鉴权、RBAC 和管理限流会先于插件 handler 执行,可能返回 401、403、限流等错误。不要将 401 或 403 当成 Lua 加载错误;409 响应体是文本,不能强行按 JSON 解析。
手动热重载
沿用上方变量,在本机执行:
Invoke-RestMethod -Method Post -Uri ($adminBase + '/local/plugins/reload') -Headers $adminHeaders
HTTP 200 后检查 generation 已增加,并确认预期插件 ID、版本均在当前清单内。失败时保留旧集合;阅读 HTTP 状态和响应体,再修正文件后重试。last_reload_error 记录候选构建/启动失败,但并非每一种 HTTP 请求失败都会更新它,不能只看该字段。
热重载如何工作
宿主每 2 秒检查框架以及启用插件的 plugin.json、main.lua 的大小和修改时间。它不扫描或自动启用陌生目录。禁用插件不读取也不监测;新增目录应先在框架明确 load。
变化后先在独立候选集合初始化全部启用插件并校验权限、ID、provider 冲突等;全部成功启动才整体切换。任何插件失败都会保留原集合。第一次启动没有可保留旧版本,加载失败会阻止游戏监听,应先修正错误。
| 修改 | 生效方法 |
|---|---|
instance.json 的插件总开关或框架路径 |
重启服务端 |
| 框架内插件列表、权限、配置、播报策略 | 保存框架后等待自动检查,或手动 reload |
| 已启用插件的清单、源码 | 保存后自动检查,或手动 reload |
| 同大小且保留原修改时间的文件替换 | 自动检查可能看不到,必须手动 reload |
| 仅改插件 version | 不是必要条件;宿主按文件签名检查,不靠版本号判断代码变化 |
重载构建过程中若文件继续变化,候选可能被拒绝,保存完成后再重试即可。不要依赖批量拷贝时某个中间状态恰好可加载。
成功切换会重建所有启用插件的 VM 和计数,内存状态不迁移,没有卸载回调,不补发 server.ready 或历史事件。规则链固定在同一版本;已固定的本轮基础经验决策不会被新版本重算。旧通知/动作允许丢弃,旧公告实际发送前会再校验版本,已开始发送的内容不能撤回。
最多保留一个退休版本,前代尚未退出时再次 reload 可返回 409。不要无限高频重载;等前次退出并确认状态后再尝试。
读懂状态
以下 JSON 是结构示意,数字不代表真实实例:
{
"api": "dnf/v1",
"enabled": true,
"running": true,
"generation": 1,
"runtime": {
"api": "dnf/v1",
"enabled": true,
"running": true,
"clear_dropped": 0,
"plugins": [
{
"id": "community-welcome",
"version": "1.0.0",
"accepted": 0,
"dropped": 0,
"errors": 0,
"actions": 0,
"action_dropped": 0
}
]
}
}
| 字段 | 实际含义 |
|---|---|
api |
当前 API 版本 |
enabled / running |
配置是否启用 / 运行状态 |
generation |
成功切换后递增,失败不增加 |
last_reload_error |
非空才出现;候选构建或启动错误,成功切换后清空 |
runtime.plugins |
当前启用集合,按 ID 排序;不列禁用插件 |
accepted / dropped |
该插件通知事件入队数 / 队列满丢弃数,既不是回调数,也不是业务成功数 |
errors |
该插件回调或公告处理错误累计,不直接等于 HTTP reload 错误数 |
actions |
宿主公告动作调用成功数;无在线目标或目标 mailbox 丢弃时也不能当作送达数 |
action_dropped |
插件公告限流或动作队列满丢弃数,不包含全部下游丢失 |
clear_dropped |
当前版本通关播报协调队列满丢弃数 |
成功切换后计数归零。一次事件匹配多个回调仍只增加一次入队计数。回调错误日志按第 1、2、4、8…次采样,避免高频刷屏;后续事件仍会处理,没有永久自动禁用故障插件的策略。
按症状排查
| 现象 | 先检查 | 处理 |
|---|---|---|
| 接口 404 / 不存在 | 是否连接正确管理监听;实例插件总开关是否启用并已重启 | 核对当前进程的配置和监听,关闭状态不会注册端点 |
| 401 / 403 | 管理令牌与 platform RBAC 权限 |
使用实例管理员分配的当前凭据 |
| 405 | 请求方法和 Allow 响应头 |
状态与目录用 GET,reload 用 POST |
| 409 | 响应体、last_reload_error、是否仍有前代退出 |
修正候选,保存完后重试;旧版本通常仍在运行 |
| 插件不在清单中 | 框架是否显式加载,enabled 是否 boolean true |
调整框架,确认本次 reload 成功 |
| 修改后没有变化 | 当前 generation、实际 framework 路径、文件大小/mtime | 手动 reload;框架路径变化需要重启 |
unknown event |
拼写、宿主 catalog、通配位置 | 使用当前稳定事件名或合法领域通配 |
hook permission required |
清单申请和框架授权两边 | 同时补齐该 Hook 的权限;播报使用 notice.broadcast |
duplicate hook / provider 冲突 |
同插件重复阶段注册、集合内多个 provider | 同点同阶段每插件一个,provider 整组最多一个 |
only the dnf module is available |
是否 require 自定义模块 | 将辅助函数合并到 main.lua,当前不支持其他 require |
attempt to call ... print 或 nil 函数 |
是否使用未开放库、假想 API | 只使用本参考列出的接口;错误由宿主日志定位 |
before hook must return boolean |
是否返回 nil、0 或表 | 明确 return true / false |
provider must return ... table |
provider 是否漏 return 或返回 nil | 返回允许字段组成的补丁表,空表也可 |
unknown plan field |
是否返回 text、reason 等本点不支持字段 | 按该 Hook 的字段表返回,不能自定义拒绝文案 |
| 规则通过但业务仍拒绝 | 余额、PVF、门票、装备锁、限购、容量等原规则 | allowed=true 不绕过核心检查 |
| 经验设 0 后仍有经验 | amount 必须为正;失败保留原基础值 | 用合法正整数;取消该 Hook 不表示取消奖励 |
| 经验改动本局无效 | 本轮基础决策已固定 | 开始新的副本运行验证 |
| Hook 读不到 event.data | 除播报外,当前 Hook 都是裸上下文 | 按本点示例使用 event.character 等字段 |
| 公告调用报权限/阶段错误 | 是否在初始化或规则回调,权限是否双边授权 | 只在通知回调发公告;播报规则通过 return text |
| 公告返回 true 却没看到 | 后续回调错误、限流、离线目标、队列或版本切换 | 检查 errors/action_dropped,再用新事件和在线角色验证 |
| 队列丢弃或规则 busy | 是否订阅 *、诊断高频事件或做大量循环 | 缩小订阅范围、减少工作和公告次数;预算见运行环境页 |
| 重载后计数/变量变 0 | 成功切换重建所有 VM | 按设计处理,不能用这些值代替持久账本 |
一套可重复的调试流程
- 先运行快速上手的完整欢迎插件,确认目录、权限、启用与新角色绑定事件都有效。
- 用 catalog 检查目标能力存在;初始化使用
assert验证配置类型,让错误尽早被发现。 - 一次添加一项订阅或 Hook,检查 reload 的 HTTP 状态、generation、插件 ID/version。
- 触发一个新的范围内事件;对业务 Hook 分别验证允许、拒绝和核心原生拒绝三种情况,资产变化以实际业务结果为准。
- 修改后重新触发新事件;经验测试使用新一轮副本,不能等待历史事件重放。
- 观察计数与宿主采样错误日志。减少诊断通配订阅,避免调试公告制造大量噪声。
发布自定义插件时一起交付清单、main.lua、框架配置片段、所需宿主 API 和已验证范围。不要把临时内存限制描述成持久防刷机制,也不要将 handler_failed=false、actions 或公告返回 true 写成客户端成功凭证。