未发布版本 v6.3.0-SNAPSHOT。 本页内容来自 alpha 分支,随时可能变更,不属于任何已发布版本。

commit ced9a7d · 注入于 2026-10-11 23:07 UTC

Skip to content

面板集成 ​

自 v6.3.0 起 —— 尚未发布

本页描述的全部行为都在 UltiTools-API v6.3.0 中落地,目前只存在于 alpha 分支。 如果你运行的是 v6.2.5 或更早版本,下面的大部分配置键与类都还不存在;已经存在的,所在小节会说明发生了哪些变化。

UltiTools 通过 WebSocket 连接到 UltiPanel 远程管理平台。自 v6.3.0 起,这条连接有五处变化: 每一项面板可用能力都变成运营者可见的开关;远程命令过滤变成运营者可编辑的黑名单;远程文件 API 被限定在一组显式的可编辑根目录内,并附带一层不可配置打开的凭据排除; 面板无法再启动、停止、暂停或恢复实时日志流,日志批量发送的间隔与 debug 级别配置开始生效,日志级别过滤也不再拦截由日志记录产生的错误上报; 模块现在可以观察或应答面板消息,而无需框架再造出第二套分发机制。

能力开关 ​

每一项面板可用能力都由 plugins/UltiTools/config.yml 中 ultipanel.capabilities 下的一个 开关控制:

yaml
ultipanel:
  capabilities:
    monitoring: true          # TPS、内存、世界/玩家快照
    logs: true                # 实时控制台日志流
    player-events: true       # 上线/下线/聊天事件
    file-read: true           # 在可编辑根目录内读取与列出文件
    file-write: false         # 在可编辑根目录内写入/上传文件
    file-delete: false        # 在可编辑根目录内删除文件/目录
    commands: false           # 远程命令执行,仍受黑名单约束
    server-properties: false  # 读取/编辑 server.properties 的安全白名单键

list 操作由 file-read 控制——列出即是读取,因此没有单独的第九项能力。

默认值按读写拆分,而非统一。 monitoring、logs、player-events、file-read 默认开启; commands、file-write、file-delete、server-properties 默认关闭。这是刻意为之: monitoring 的状态数据是面板唯一的"服务器在线"信号,若默认关闭会让升级后的服务器显示为 离线,而不仅仅是"未配置"——这是最糟糕的失败方式,因为症状会把运营者指向错误的方向。 error-reporting(位于 ultipanel.logging.error-reporting 下)不属于这八项能力之 一——它保留自己原有的配置键路径与默认值,本次发布不做改动。

关闭某项能力会产生明确拒绝,绝不会静默失败。 响应会指明具体的配置键与文件,例如:

Blocked by capability policy: 'file-write' is disabled — edit
ultipanel.capabilities.file-write in plugins/UltiTools/config.yml to change this.

远程命令黑名单 ​

远程命令执行(受上方 commands 能力开关控制)另外还受一份运营者可编辑黑名单的过滤:

yaml
ultipanel:
  commands:
    blocklist:
      - "op"
      - "deop"
      - "stop"
      - "restart"
      - "reload"
      - "ban-ip"
      - "pardon-ip"
      - "whitelist"
      - "save-off"
      - "save-all"

v6.3.0 之前,这份名单是一份硬编码、不可配置的十条条目集合。自 v6.3.0 起,它迁移到 ultipanel.commands.blocklist,并且可以双向自由编辑——运营者可以新增条目,也可以移除任意 条目,包括清空整份列表。

这里刻意没有设置不可覆盖的强制底线。 面板是运营者自己的远程控制台,不是自主代理;一条 硬编码底线约束的对象是运营者本人,而非攻击者——已经攻陷面板凭据的人已经拿到了运营者本人的 身份,能通过其他游戏内手段达到同样的效果,因此底线只会限制运营者自己合法的配置选择。配套的 补偿控制是下方的远程操作日志,无论这份名单内容如何,它都会记录面板执行的 每一条命令。本页不列举清空该名单后具体哪些命令会变得可达。

黑名单拒绝会指明原因与解决办法,例如:

Blocked by the blocklist — edit ultipanel.commands.blocklist in
plugins/UltiTools/config.yml to change this.

命令命名空间归一化不受本次变化影响:bukkit:op 与 op 在检查前会解析为同一个黑名单条 目,检查点仍是原来唯一执行该归一化的位置。

远程命令的结果 ​

自 v6.3.0 起,面板的远程命令以服务器控制台的身份运行,等同于在控制台中输入该命令,模块看到的仍是通常的控制台发送者。框架不捕获命令的回复:command_result 消息只说明命令已分派,文本为 Command dispatched to the server console. Its output appears in the server log stream.;分派返回 false 时为 The server console did not accept the command. Any message it printed appears in the server log stream.。回复本身与其他所有控制台输出一起,通过实时日志流到达面板,因此需要开启 logs 能力才能看到。黑名单拒绝、空命令与分派错误的报告方式不变。v6.3.0 之前,结果里携带的是编造的 Command executed successfully,而不是回复。把 output 当作命令回复来显示的面板或工具,现在显示的是上面这句话。

自 v6.3.0 起,从 UltiTools 开始禁用的那一刻起,面板请求不再得到回复(#621):入站消息只记一条 FINE 日志后丢弃,而不是为已禁用的插件调度任务,因此服务器停止期间发出的面板操作要等到面板自身的超时。恰在停服开始那一刻到达的远程命令,可能已在控制台和远程操作日志中记为允许,却不会执行,也没有结果回复。

远程文件 API 边界 ​

远程文件 API(file-read/file-write/file-delete)被限定在一组显式的可编辑根目录内, 默认覆盖插件配置与历史日志:

yaml
ultipanel:
  files:
    editable-roots:
      - "plugins"
      - "logs"

可编辑根目录回答"在哪";上方的能力开关回答"能做什么"。这是两条正交的轴——一个请求必须 同时通过两者。

在这些根目录内部,还有一层不可配置打开的、基于模式的凭据排除。 一小组通配模式与精确 文件名——覆盖密钥/证书类文件扩展名与若干已知的凭据文件名——会在可编辑根目录检查之前生效, 适用于每一次远程文件操作,无论运营者授予了哪些根目录。即使把某个包含凭据文件的目录重新加 为根目录,也不会重新打开对那个具体文件的访问。

拒绝始终指明两种可区分原因之一,绝不会合并成一句笼统的话:

  • 可配置 —— 目标在可编辑根目录集合之外。消息会指向 ultipanel.files.editable-roots。
  • 不可配置 —— 目标命中了无条件的凭据排除。消息会明确说明这一点,而不是指向一个并不存 在的开关。

list 会标记被拒绝的条目,而不是直接省略它们。 v6.3.0 之前,调用方无权查看的条目会被 静默从列表中剔除,面板无法区分"这个文件不存在"与"这个文件被策略隐藏"。自 v6.3.0 起,每个 条目都携带一个 accessible 布尔值;被拒绝的条目额外携带 reason,取值为 PROTECTED_CREDENTIAL 或 OUTSIDE_ROOTS,且不再携带 size/lastModified/readable/writable。这是一次向后兼容的模式新增——不了解这些新字 段的旧版面板会照旧渲染之前渲染的内容,外加此前被隐藏的那些行。

递归删除目录现在要求请求显式携带 recursive: true。 v6.3.0 之前,一个指向目录的 delete 请求会递归删除该目录及其全部内容,没有任何标志,也没有任何确认。自 v6.3.0 起,缺 少该字段、该字段为 false,或该字段不是真正的 JSON 布尔值的目录删除请求,都会在触碰文件 系统之前被拒绝,并指明缺失的字段。尚未发送该字段的旧版面板,其每一次目录删除请求都会被明确 拒绝。

面板修改配置 ​

自 v6.3.0 起,面板编辑只写它指定的内容。编辑模块配置时经过配置写入闸门:在指定的键处替换值,文件的其余每个字节保持不变;闸门无法这样写入的文件会收到注明原因的错误回复(见保存配置文件)。

编辑 server.properties 时,只替换定义该键的那一行的值文字。注释、键的顺序、分隔符和其它值保持原有字节,包括 UTF-8 的 motd;文件按服务器读取它的方式解码和编码,并原子写入。同一个键定义在多行、或定义续到下一行时会被拒绝,原因注明行号,不含值:set 回复以 reason 字段给出,set_all 回复把该键列在 failed 中,并在 failureReasons 中给出原因。服务器日志对每个被拒绝的键只记一次。服务器自己下次启动时仍会整份重写该文件,所有 Paper 版本都是如此。

凭据文件位置 ​

框架自身的 UltiCloud 凭据文件——面板连接令牌,不是任何模块拥有的东西——自 v6.3.0 起搬了 位置。这是运营者可见的变化:它改变了备份或迁移服务器时需要复制凭据的位置。

位置
v6.3.0 之前plugins/UltiTools/data.json
v6.3.0 及以后<服务器根目录>/.ultikits/credentials.json

迁移是自动的,只在升级后首次读写凭据时发生一次——没有单独的迁移命令要运行。这个流程 在结构上就是失效安全的:先读取旧文件,再写入新文件,随后读回新文件确认写入无误,只有到这一 步才会删除旧文件。如果写入在任何环节失败,旧文件会原封不动地留在原处,框架也会回退使用它—— 一次失败的迁移永远不会丢失凭据,只会让运营者停留在旧位置,直到下一次迁移成功。

新位置位于上方所有默认可编辑根目录之外,并且——和此前的 data.json 一样——同样受那道无条 件的、基于文件名的凭据排除保护,无论运营者配置了哪些根目录。远程文件 API 会同时拒绝访问新 位置,以及升级重启窗口期间的旧位置。

CloudAuthManager 上三个内部凭据协调静态方法已宣布将在 v6.4.0 移除。currentCredentialGeneration()、invalidateCredentialOperations() 与 commitTokenIfCurrent(TokenEntity, long) 从来都不是受支持的外部 API——它们协调的是框架自身 的异步凭据生产者与其拆除路径,标注了 @Deprecated(since = "6.3.0", forRemoval = true) 与 {@removeIn 6.4.0} javadoc 标签。三者的签名与行为在 v6.3.0 中保持不变;它们原本隐含的凭据 文件 I/O 现在都经由上文所述的内部存储完成。如果你的模块调用了其中任何一个,请在 v6.4.0 之前 停止使用——公开的面板集成或外部插件 API 表面都不依赖它们。

远程操作日志 ​

每一次通过能力开关与黑名单检查的远程操作——无论被允许还是被拒绝——都会作为一行结构化记录写 入 plugins/UltiTools/security/action.log,包含时间戳、能力、动作、目标、判定结果 (allowed/denied)与原因。该日志本身落在上文所述的同一层无条件凭据排除范围内,因此无法 通过远程文件 API 删除或关闭。它自己的轮转是可配置的——但"面板做过什么"这份记录不是:

yaml
ultipanel:
  logging:
    action-log:
      max-size-bytes: 1048576  # plugins/UltiTools/security/action.log 达到该大小后轮转
      max-files: 5             # 保留的轮转文件数量

这里刻意没有可以完全关闭该日志的配置键。

实时日志流 ​

logs 能力开启时,框架会在每次面板连接建立时挂上自己的日志处理器,并在连接保持期间持续把控制台日志记录发送给面板。自 v6.3.0 起,日志流完整镜像服务器控制台,见日志流镜像服务器控制台。自 v6.3.0 起,框架自己在这里写出或推送的行(玩家加入、退出与聊天,插件操作,在线玩家数,服务器状态与文件操作消息)遵循主插件配置中的 language 键,见 I18n 多语言;v6.3.0 之前它们始终是中文。

自 v6.3.0 起,动作为 start、stop、pause 或 resume 的 log_stream 或 log_stream_control 请求会收到一条说明该动作不受支持的错误响应,日志发送照常进行。v6.3.0 之前框架接受这四个动作,但在任何已发布版本上,它们都没有改变面板实际收到的内容。框架与面板中继之间只有一条连接,也无法识别连接背后的各个查看者,因此无法针对单个查看者暂停或停止日志流。暂停或隐藏实时日志视图由面板视图自己完成,例如不再渲染新的日志行。

status 动作仍会得到应答。自 v6.3.0 起,它的 data 对象除 action 与 clientId 外,还携带以下字段:

字段含义
connected本服务器与面板的连接当前是否在线。v6.3.0 新增
logTransmitterEnabled日志传输器是否在接收日志记录
queueSize已排队、尚未发送的日志条目数

响应中不再包含 subscriberCount 与 streaming 字段。

被拒绝的这些动作背后的 LogStreamManager 公开方法已在 v6.3.0 中移除:startLogStream(String, String)、startLogStream(String)、stopLogStream(String)、pauseLogStream(String)、resumeLogStream(String)、isStreaming() 与 getSubscriberCount()。

批量发送配置 ​

发往面板的日志记录会先排队,再分批发送,相关配置位于 ultipanel.logging.batch 下:

yaml
ultipanel:
  logging:
    batch:
      enabled: true
      size: 10
      interval: 5000
键默认值作用
enabledtrue设为 false 时,每条日志记录都作为独立消息立即发送
size10单次发送的最大条目数。排队条目达到这个数量时,不等时间间隔到达就立即发送。最小为 1
interval5000两次定时发送之间的毫秒数,无论 monitoring 能力是否开启都按它执行。最小为 1000

队列中最多等待 1000 条日志。队列已满时,最早的条目会被丢弃。

自 v6.3.0 起,随框架发布的 config.yml 声明了这一段,修改后的间隔也会生效。v6.3.0 之前,发布的文件里没有这一段,新的间隔值也不会让已经在运行的发送任务重新调度。已有的 config.yml 不会被改写来补上这一段;在你手动加上之前,使用上表的默认值。

框架在每次面板连接建立时,从内存中已加载的配置读取这些键。低于最小值的配置会被拒绝,框架记录一条指明该键的警告,并保留默认值。修改 config.yml 不会影响已经建立的连接,ul reload 会重新读取文件但不会重新建立连接,因此请重启服务器来应用修改后的值。

服务器运行期间,动作为 config 的 log_stream 请求可以携带 batchConfig 对象,包含 enabled、size、interval 中的任意几项。框架会先按同样的限制检查请求中的每一个值,再统一应用,任一值无效时整个请求都会被拒绝。检查通过后这些值立即生效,新的间隔会让正在运行的发送任务重新调度。以这种方式设置的值会保持到下一次面板连接建立,届时框架会重新应用 config.yml 中的值。

日志级别与排除的记录器 ​

ultipanel.logging 下还有两个键,决定日志处理器把哪些日志记录发送给面板。随框架发布的 config.yml 中没有这两个键。某个键不存在时,使用它的默认值。

yaml
ultipanel:
  logging:
    levels:
      - "info"
      - "warning"
      - "error"
    excluded-loggers:
      - "Minecraft"
键默认值作用
levelsinfo、warning、error发送给面板的日志级别。能匹配到日志记录的名称是 error(SEVERE)、warning(WARNING)、info(INFO)与 debug(CONFIG、FINE、FINER、FINEST)。其他名称会被接受,不产生警告,也不匹配任何记录
excluded-loggers空(自 v6.3.0 起)记录器名称前缀,来自这些记录器的日志记录会被丢弃。上面的示例会丢弃通过 Bukkit.getLogger() 输出的一切

与批量发送的键一样,框架在每次面板连接建立时读取这两个键,因此修改后的值同样需要重启服务器来应用。框架直接发送给面板的少数条目,例如玩家进入、离开服务器和聊天的日志行,不经过日志处理器,这两个键对它们不起作用。聊天日志行在 player-events 能力开启时发送,即使 levels 中没有 debug,它们也以 debug 级别到达面板。

每个 excluded-loggers 条目都会与发出该记录的 java.util.logging 记录器名称的开头比较,区分大小写,因此 org.apache 这样的条目也会排除 org.apache.http 以及其他所有以它开头的名称。发送给面板的条目中不包含这个名称。条目的 logger 字段是框架根据自己对记录的分类得出的标签:com.ultikits.ultitools 下的记录器为 UltiTools,名称中含有 plugin 的其他记录器为从记录器名称中取出的一个名称,服务器记录器为 MinecraftServer,其余为 database、network 或 system。把这个标签抄进 excluded-loggers,只会匹配到名称恰好以它开头的记录器。例如,MinecraftServer 这个条目不会匹配任何标签为 MinecraftServer 的记录,因为框架把这个标签给了 Minecraft、net.minecraft.* 这类服务器记录器,而不是名为 MinecraftServer 的记录器。

java.util.logging 记录的记录器名称有三种:Minecraft(通过 Bukkit.getLogger() 输出的一切,包括框架自己的 [UltiTools-API] ... 行)、插件自己的名称,以及 com.ultikits.ultitools.* 类记录器。Paper 通过 Log4j 输出的行,以及通过 Log4j 或 SLF4J 输出日志的库(例如 authlib、Netty、HikariCP 与 Jetty),经由下文的控制台镜像到达日志流,使用它们在 Log4j 中的记录器名称,excluded-loggers 条目对它们同样适用。v6.3.0 之前日志流只接收 java.util.logging 记录,默认列表写的是五个 Log4j 或 SLF4J 库,什么也没有过滤;自 v6.3.0 起默认列表为空,日志流因此显示整个控制台(#485)。

自 v6.3.0 起,在 levels 中加入 debug 会把调试记录发送给面板。v6.3.0 之前,日志处理器自身的级别阈值固定为 INFO,CONFIG、FINE、FINER 与 FINEST 记录在检查 levels 之前就被丢弃,debug 因此不起作用。现在只要 debug 在列表中,框架就会降低这个阈值。框架不会修改任何记录器的级别,所以只有当发出调试记录的记录器本身的级别允许该记录通过时,它才会到达面板。

自 v6.3.0 起,levels 不再影响错误自动上报。日志处理器会把每一条携带异常的 SEVERE 记录交给错误上报(ultipanel.logging.error-reporting),无论 error 是否在 levels 中。v6.3.0 之前,级别不在 levels 中的记录会在这一步之前被丢弃,因此从 levels 中移除 error 也会停止这部分上报。logs 能力关闭时日志处理器不会挂上,因此这部分上报也依赖该能力。排除在这两步之前进行:来自被排除记录器的记录既不会发送给面板,也不会作为错误上报。在其他位置产生的错误上报,例如命令执行时抛出的异常,不经过日志处理器,这两个键对它们不起作用。

日志流镜像服务器控制台 ​

自 v6.3.0 起,logs 能力开启时,日志流显示的就是服务器控制台显示的内容。Paper 的大部分输出通过 Log4j 打印:命令反馈、模块对控制台发送者的回复、玩家加入与退出、聊天、原版的警告与错误,以及玩家的命令行。v6.3.0 之前这些都不会到达面板。框架现在在加载时于 Log4j 的根记录器上安装一个 appender,在禁用时移除它。每一行都与插件日志行一样经过相同的过滤、批量发送与启动回放,并去掉 ANSI 颜色码。

这个镜像是完整且未脱敏的,包含带参数的玩家命令行,例如 <player> issued server command: /login <password>。面板与控制台处于同一信任级别,控制台显示什么,面板就可以显示什么。不希望如此的运营者应保持 logs 能力关闭。

插件日志行只会到达一次,尽管 Paper 也会把它复制进 Log4j。与面板连接有关的行、日志发送器自己的行以及 WebSocket 库(org.java_websocket.*)的行永远不会被发送。服务器的 Log4j 配置使用异步记录器时,镜像不会安装,控制台会输出一条警告说明这一点,日志流只包含插件日志行。自 v6.3.0 起,java.util.logging 根记录器上找不到 Paper 的日志转发器(org.bukkit.craftbukkit.util.ForwardLogHandler,例如分支服务端或重定位了 CraftBukkit 的构建)时同样如此:没有它,镜像无法区分转发的插件日志行与控制台行,每条插件日志都会被推送两次,因此不安装镜像,每次服务器运行只输出一条控制台警告(#583)。自 v6.3.0 起,Paper 1.19.2 至 1.20.4 的所有构建都重定位了 CraftBukkit,因此在这些版本上镜像始终不会安装,启动时输出一条警告说明这一点,日志流只包含插件日志行;Paper 1.20.5 及以上不受影响。携带异常的 Log4j ERROR 行还会向面板的错误收集上报一次。org.apache.logging.log4j:log4j-core(2.24.1)是框架的 provided 依赖,由 Paper 在运行时提供,不会被打包,模块无需做任何改动。

启动阶段的日志与发送失败 ​

自 v6.3.0 起,从框架加载到面板连接建立之间输出的日志记录(例如模块加载与依赖解析),也会进入日志流。框架把它们保存在启动缓冲区中,在日志流开始时最先发送、从最早的一条开始(#487)。缓冲区只保存 INFO 及以上、且未被 excluded-loggers 排除的记录,最多 2000 条(估计约 512 KiB);服务器没有云端登录,或日志流在五分钟内没有开始时,缓冲区会被释放,不发送任何内容。回放按每条不超过 64 KiB 的消息发送,第一条立即发出,之后大约每秒一条,与上文的批量发送键无关,因此装满的缓冲区几秒内即可发完。回放期间新产生的实时记录可能先于最后几条回放消息到达。logs 能力关闭时不会创建缓冲区,也不保存任何记录。

同样自 v6.3.0 起,一批日志记录如果因为连接恰在发送时关闭而发送失败,会被保留下来,在下一次发送时先于任何更新的记录发出,因此记录仍按顺序到达(#486)。投递仍是尽力而为:连接恰在一批记录写出之后断开时,这批记录可能到达两次。到达的记录多于日志流能发送的数量时,队列保留最新的 1000 条,框架最多每分钟一次在服务器日志中用一条警告报告丢弃了多少条。

自 v6.3.0 起,日志流是控制台尽力而为的便利副本,以服务器的 logs/latest.log 为准(#583、#571)。已知两处缺口:启动回放尚未发完时面板连接断开,未发送的回放记录会转入新连接的实时队列,该队列保留最新的 1000 条、其余计入上文的警告,之后这些记录按实时记录发送,不再按回放的节奏;以及在日志流首次接管启动缓冲区的那几微秒内,其他线程记下的一条记录可能漏发,且不计数。凡是不能遗漏的内容(例如审计记录或事故排查),请从 logs/latest.log 读取。

服务器运行期间,上文所述动作为 config 的 log_stream 请求也可以携带 levels 数组。自 v6.3.0 起框架会应用它;v6.3.0 之前该字段会被忽略。只要有一个名称不属于上述四个,整个请求都会被拒绝;空数组会让所有级别都不启用。以这种方式设置的级别会保持到下一次面板连接建立,届时框架会按 config.yml 重新创建日志处理器,config.yml 中没有 levels 时则使用默认值。这个请求没有用于排除记录器的字段。

模块扩展点 ​

现在,模块无需框架再造出与既有 24 种消息类型的分发表并列的第二套分发机制,就能对面板消息 做出反应。

观察每一条消息:PanelMessageEvent ​

com.ultikits.ultitools.events.PanelMessageEvent 会在既有的模块事件总线 上发布,作为处理每一条入站面板消息——包括框架自身并不拥有的消息类型——的最后一步。订阅方式 与订阅其他任何 ModuleEvent 完全一致:

java
@ModuleEventHandler
public void onPanelMessage(PanelMessageEvent event) {
    String type = event.getType();
    JsonObject data = event.getData();
    // 处理器运行在主线程——在这里调用 Bukkit API 是安全的
}

关于这个事件,有两点是刻意设计的,使用前值得了解:

  • 处理器运行在主线程上。 框架在桥接处通过 Bukkit.getScheduler().runTask(...) 把发布 调度到主线程,而不是 publishAsync——publishAsync 提交到异步线程池,根本不会到达主线 程,因此无法让处理器安全地调用 Bukkit API。这里一个慢处理器造成的服务器 tick 开销,与任 何其他运行在 Bukkit 主线程上的事件完全一样;当一次发布耗时超过一个较小的固定阈值时,框 架会记录一条指明消息类型的告警日志,但没有任何机制阻止处理器变慢。
  • 该事件不是 Cancellable。 它在分发结束、框架已经对消息采取行动之后才发布——此时的 取消标志不会产生任何效果。这与 EventBus.publishAsync 本身已经无条件拒绝 Cancellable 事件的做法出于同一个理由:一个不产生任何效果的可调用方法,正是 UltiTools-API v6.3.0 要 清除的"声明了却不可用"这类缺陷。

事件上的数据在传入时以及每次访问器调用时都会被防御性拷贝——处理器对收到内容的修改,既不 会影响下一个处理器看到的内容,也不会影响框架已经采取的行动。

拥有一个请求/响应类型:PanelResponderRegistry ​

对于框架尚未处理的面板消息类型,模块可以通过 com.ultikits.ultitools.websocket.PanelResponderRegistry 注册唯一一个 responder,直接应 答请求:

java
UltiTools.getInstance().getPanelResponderRegistry()
    .registerResponder("my_module:status", data -> {
        JsonObject reply = new JsonObject();
        reply.addProperty("state", "ok");
        return CompletableFuture.completedFuture(reply);
    }, "MyModule");
  • 每个类型只能有一个所有者。 注册一个框架已经服务的类型(框架内置的 24 种消息类型之 一)会立即抛出异常,并指明框架是既有所有者;注册一个另一个模块已经拥有的类型也会立即抛 出异常,并指明那个模块。这是硬性检查,不是建议——不存在"后注册者静默生效"的兜底行为。
  • responder 返回 CompletableFuture<JsonObject>。 框架会把解析结果(或失败)包装上请 求的 requestId 并发回给面板——你无需直接接触 WebSocket 连接。
  • 一个有界超时,只在一处生效。 如果 responder 的 future 在几秒内未完成,框架会代替 responder 完成回复,附带一条明确的超时错误,而不会让面板的请求无限期挂起。
  • 模块卸载时,responder 会自动注销,与框架自动注销该模块 EventBus 订阅的同一时机 一致。
  • responder 属于某一个模块实例(自 v6.3.0 起)。 框架加载模块期间(容器刷新或 registerSelf() 中)注册的 responder 属于该实例。加载之后用三参数 registerResponder(type, responder, ownerModule) 注册的,属于框架以 ownerModule 列为已加载的那个实例;新实例正在替换旧实例时,属于旧实例。实例卸载 或被替换时,只注销它自己的 responder。在模块构造器中,或在框架把主类注册为 Bean 时运行的 @PostConstruct 代码中(主类自身及其注入的 Bean 的) 按名称注册的 responder,属于此时以该名称列出的实例:首次加载时没有这样的实例,新实例加载期间则是旧实例。请在 registerSelf() 中注册 responder;如果你手上有模块实例, 也可以使用四参数的 registerResponder(type, responder, ownerModule, ownerInstance),直接记下实例。
  • 新实例接管旧实例的类型(自 v6.3.0 起)。 代码注册一个已加载模块的新实例时,旧实例的 responder 在新实例的 registerSelf() 运行之前被释放,所以在其中注册同一类型会成功。新实例加载失败时,它的 responder 被注销,旧实例的 responder 原样恢复。

框架不强制要求 <模块>:<类型> 这样的命名空间前缀——那将是面板一侧也需要遵守的跨仓库协议 约定,而不是框架单方面能强制的东西。不过采用带命名空间的类型字符串(如上例)仍是值得遵循 的约定,可以避免与其他模块自己的消息类型发生冲突。

参见 ​

贡献者

暂无相关贡献者

基于 MIT 许可发布