Skip to content

fix: 修复更新检查失效与四处运行期异常,补充单元测试 - #4

Open
mincHR549 wants to merge 7 commits into
TeamVastsea:v6from
mincHR549:v6
Open

fix: 修复更新检查失效与四处运行期异常,补充单元测试#4
mincHR549 wants to merge 7 commits into
TeamVastsea:v6from
mincHR549:v6

Conversation

@mincHR549

Copy link
Copy Markdown
Member

变更摘要

本 PR 修复 5 处会在生产环境实际触发的缺陷,统一 Velocity API 版本,并为项目引入首批单元测试。共 7 个提交,按关注点拆分,可单独 review 或 revert。

修复的缺陷

1. 更新检查无法发现次版本号和主版本号升级

SemVersion.needUpgradeIgnoreSuffixes 的判断条件是:

return version.major >= major && version.minor >= minor && version.patch > patch;

patch 严格递增是必要条件,因此 0.6.11 -> 0.7.01.2.3 -> 2.0.0 全部被判定为不需要更新。只要下一个版本不是纯修订号递增,所有用户都收不到更新提示。

改为按 major、minor、patch、后缀等级、后缀序号逐级比较。

2. 更新检查会因单次失败永久停止

两个独立原因,都会让检查彻底失效:

  • SemVersion.of 对非法输入抛异常。它的输入来自远程仓库的 latest 文件,内容不可控,带一个行尾回车就抛 NumberFormatException
  • CheckUpdater.start 只捕获 IOException。底层是 scheduleAtFixedRate,任务抛出未捕获异常后不再被调度。

现在解析失败返回 null 并容忍首尾空白,周期任务捕获所有异常。

3. 用户名冲突改名提示空指针

AssignInGameFlows 延迟 2000ms 给玩家发送改名提示,此时玩家可能尚未进服或已断开,getPlayer 返回 null 后直接调用 sendMessagePL 抛 NPE。名称冲突场景下必然触发。已加判空,玩家不在线时记 debug 日志跳过。

4. 玩家缓存清理任务会因异常永久停止

PlayerHandler.register 的周期任务没有异常保护,同样受 scheduleAtFixedRate 语义影响,一次失败后 cache 永不清理,造成内存泄漏。原有的 InterruptedException 处理也不正确:catch 之后继续往下走,会在已被中断的线程上执行清理。

5. 低版本客户端退出时静默报错

injectPlayer 只对协议版本 761(1.19.3)及以上注入 ChatSessionHandlerremovePlayer 缺少同样的判断,对低版本玩家移除不存在的处理器会抛 NoSuchElementException。因为在 eventLoop().submit() 内被 Future 吞掉,表现为静默失败。裸的 761 已提取为 CHAT_SESSION_MIN_PROTOCOL 常量。

其他改动

统一 Velocity API 版本velocity3.5.0-SNAPSHOTvelocity:injector 却停留在 3.4.0-SNAPSHOT。injector 全靠反射操作 Velocity 内部类(StateRegistryInitialLoginSessionHandlerAuthSessionHandler),两模块编译基准不一致时反射目标容易错位,是 #1 那类启动崩溃的温床。

引入 JUnit 5,74 个用例覆盖 SemVersionAssignInGameFlows.incrementStringMapperConfigValueUtil。选这四处的原因是它们都是纯函数,能脱离服务器环境验证,且本次修复集中在此。上述缺陷 1、2 均有对应的回归测试。

CI 加入 test 步骤,位于构建之前。

文档CONTRIBUTING.md 原内容是占位符 Enter file contents hereSECURITY.md 是 GitHub 默认模板,且支持版本表格把所有版本都标为不支持。两份均已重写。README 修正了安装步骤的编号错乱与重复项,补充测试命令,中英文同步。

验证

./gradlew clean test shadowJar
BUILD SUCCESSFUL
29 actionable tasks: 29 executed
测试类 用例数 失败
SemVersionTest 38 0
AssignInGameFlowsTest 15 0
MapperConfigTest 6 0
ValueUtilTest 15 0
合计 74 0

已知限制

未在真实 Velocity 上验证登录流程,只做了编译与单元测试验证。缺陷 3、5 涉及运行期行为,建议合并前在测试服过一遍登录、名称冲突改名、低版本客户端进出这三个场景。

Velocity API 统一到 3.5.0-SNAPSHOT 后,如果需要继续兼容 3.4.x,这个改动需要另做讨论。

.digests 未纳入版本控制(.gitignore 中已忽略),本次没有改动 libraries,因此依赖摘要无需重新生成。

XiaoHaoo added 7 commits August 2, 2026 10:43
velocity 模块用的是 3.5.0-SNAPSHOT,而 velocity:injector 仍停留在
3.4.0-SNAPSHOT。injector 全靠反射操作 Velocity 内部类(StateRegistry、
InitialLoginSessionHandler、AuthSessionHandler),两个模块基于不同版本
编译时反射目标容易错位,是 issue TeamVastsea#1 那类启动崩溃的温床。

统一到 3.5.0-SNAPSHOT。
needUpgradeIgnoreSuffixes 的条件是

    version.major >= major && version.minor >= minor && version.patch > patch

patch 严格递增是必要条件,因此 0.6.11 -> 0.7.0、1.2.3 -> 2.0.0 全部被
判定为不需要更新。只要下一个版本不是纯修订号递增,所有用户都收不到更新提示。

改为按 major、minor、patch、后缀等级、后缀序号逐级比较。

同时修两个会让更新检查彻底停止工作的问题:

- SemVersion.of 对非法输入抛异常。它的输入来自远程仓库的 latest 文件,
  内容不可控,带一个行尾回车就会抛 NumberFormatException。现在解析失败
  返回 null,并且容忍首尾空白。
- CheckUpdater.start 只捕获 IOException。底层是 scheduleAtFixedRate,
  任务抛出未捕获异常后不会再被调度,一次失败就让更新检查永久停止。
  现在捕获所有异常,检查逻辑抽到独立方法里。
AssignInGameFlows 在检测到游戏内用户名被占用后,会延迟 2000ms 给玩家发送
改名提示。此时玩家可能还没有真正进入服务器,或者已经断开连接,
getPlayer 返回 null,直接调用 sendMessagePL 就会抛 NullPointerException。
名称冲突场景下必然触发。

加上判空,玩家不在线时记 debug 日志跳过。

顺带把 incrementString 改为包级可见以便单元测试。
PlayerHandler.register 注册的周期任务完全没有异常保护,底层
scheduleAtFixedRate 在任务抛出未捕获异常后不再调度,一次失败就会让
cache 永远不再清理,造成内存泄漏。原先的 InterruptedException 处理也不对:
catch 之后继续往下走,会在被中断的线程上执行清理。

把清理逻辑抽到 clearInvalidCache,外层捕获所有异常并记录日志。
injectPlayer 只对协议版本 761(1.19.3)及以上的玩家注入 ChatSessionHandler,
removePlayer 却没有同样的判断,对低版本玩家移除一个不存在的处理器会抛
NoSuchElementException。因为跑在 eventLoop().submit() 里被 Future 吞掉,
表现为静默失败。

removePlayer 补上同样的协议版本判断,并且在移除前确认处理器存在、
包一层异常捕获。裸的 761 提取为 CHAT_SESSION_MIN_PROTOCOL 常量。
项目此前没有任何测试。补上 74 个用例,覆盖四处改动风险最高、
又完全可以脱离服务器环境验证的纯函数:

- SemVersion:版本号解析与升级判断,包含本次修复的次版本号、
  主版本号升级判定,以及远端 latest 带行尾回车时的解析容错
- AssignInGameFlows.incrementString:用户名冲突时的候选名生成,
  含进位、连续递增单调性、结果字符合法性
- MapperConfig:ChatSession 包 ID 映射的去重规则、协议版本下限、
  保存与重载往返
- ValueUtil:消息变量替换、UUID 与字节数组互转、join、sha256

同时在 CI 的构建步骤前加入 test,避免回归。
CONTRIBUTING.md 原内容是占位符 "Enter file contents here",
SECURITY.md 是 GitHub 默认模板且支持版本表格把所有版本都标为不支持。

- CONTRIBUTING.md:补齐构建与测试命令、模块职责说明、injector 反射
  注意事项、运行期依赖的添加方式、周期任务必须捕获异常等代码约定、
  新增协议版本支持的做法
- SECURITY.md:明确受支持版本、走 GitHub 私密漏洞报告通道、
  报告需附带的信息(并提醒删除 accessToken 等敏感值)、处理时限,
  以及不属于安全漏洞的情形
- README:修正安装步骤的编号错乱与重复项,补充测试命令,
  加上贡献指南和安全策略的入口,中英文同步
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant