dnf/v1
实践与交付 / dnf/v1

调试与运行管理

检查插件状态,理解失败回退、计数器、管理接口和执行限额。

安装位置与配置边界

所有插件路径相对安装目录下顶层 runtime/,使用正斜线。自定义内容放在 runtime/plugins/,官方示例放在 runtime/assets/plugins/。官方框架、两个示例插件各自的清单和入口共 5 份资源会随更新包覆盖;需要长期保留的修改应复制到自定义目录后再加载。

text
安装目录/
└─ runtime/
   ├─ config/
   │  └─ instance.json
   ├─ plugins/
   │  ├─ framework.lua
   │  └─ community-welcome/
   │     ├─ plugin.json
   │     └─ main.lua
   └─ assets/plugins/       官方资源,更新可能覆盖

在现有 runtime/config/instance.json 中合并以下片段,保留其他字段:

json
{
  "plugins": {
    "enabled": true,
    "framework": "plugins/framework.lua"
  }
}

这不是完整实例配置,不能用它覆盖原文件。不要编辑生成目录 runtime/configs。完整安装文件见快速上手。总开关 plugins.enabled 或框架入口路径改变后,使用现有实例管理流程重启服务端;框架内容、插件启停、权限、配置和源码在启用后可热重载。

管理接口连接方式

插件管理 API 挂在服务端已有的本机管理监听,使用 X-Admin-Token,要求 platform 管理权限。端口和凭据取实际实例的管理配置;它不是游戏端口、网关玩家接口或公开 Pages 站点接口。浏览文档不需要提供服务器凭据。

标准实例配置中的 server.adminListen 是管理监听,server.adminToken 是管理令牌。只在服务端本机运行查询,将令牌留在内存中;不要把实例配置、令牌、浏览器请求导出或命令录屏放进公共文档项目。

以下 PowerShell 示例从安装目录运行,读取本机现有配置,不修改配置,也不打印令牌:

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 解析。

手动热重载

沿用上方变量,在本机执行:

powershell
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 是结构示意,数字不代表真实实例:

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 按设计处理,不能用这些值代替持久账本

一套可重复的调试流程

  1. 先运行快速上手的完整欢迎插件,确认目录、权限、启用与新角色绑定事件都有效。
  2. 用 catalog 检查目标能力存在;初始化使用 assert 验证配置类型,让错误尽早被发现。
  3. 一次添加一项订阅或 Hook,检查 reload 的 HTTP 状态、generation、插件 ID/version。
  4. 触发一个新的范围内事件;对业务 Hook 分别验证允许、拒绝和核心原生拒绝三种情况,资产变化以实际业务结果为准。
  5. 修改后重新触发新事件;经验测试使用新一轮副本,不能等待历史事件重放。
  6. 观察计数与宿主采样错误日志。减少诊断通配订阅,避免调试公告制造大量噪声。

发布自定义插件时一起交付清单、main.lua、框架配置片段、所需宿主 API 和已验证范围。不要把临时内存限制描述成持久防刷机制,也不要将 handler_failed=false、actions 或公告返回 true 写成客户端成功凭证。

DNF90 · 服务端 Lua 插件核对版本 cda08ef · 2026-10-26

输入关键词,查找指南和全部事件。

点击结果跳转 · Esc 关闭