Skip to content

LyMySQLCore

LyMySQLCore 是离渊系列插件使用 MySQL 存储时的数据库前置,负责统一处理数据库连接、玩家数据锁、玩家数据加载、玩家数据保存和跨服数据安全。

凡是离渊系列插件中启用了 MySQL 存储的功能,都必须先安装 LyMySQLCore,并确认插件已经成功连接数据库。只有在 LyMySQLCore 正常加载且数据库连接成功后,相关数据读取、保存和跨服锁功能才会生效。

插件信息

项目内容
插件名LyMySQLCore
当前版本1.0.5
作者Liyuan
主类Ly.sqlcore.safer.MySQLSafer
Java 版本Java 8
编译平台Paper API 1.16.5-R0.1-SNAPSHOT
适用服务端Bukkit、Spigot、Paper 系列服务端;项目证据仅明确使用 Paper 1.16.5 API 编译
主要用途离渊系列插件的 MySQL 数据前置

警告

启用任何依赖 MySQL 的功能前,必须安装 LyMySQLCore,正确填写数据库配置,并确认控制台出现数据库连接成功提示。

如果 LyMySQLCore 未加载、数据库连接失败或插件启动失败,相关插件的数据读取、数据保存、跨服同步、冷却记录和次数记录等功能都不能正常工作。

玩法与作用

LyMySQLCore 不提供独立玩法内容,也不提供玩家操作指令。它作为数据库前置,为其它插件提供安全的数据处理流程:

  1. 玩家进入服务器后,先登记玩家状态并尝试获取数据库锁。
  2. 获取锁之前,限制玩家进行可能影响数据的操作。
  3. 获取锁后,异步执行已注册的数据加载器。
  4. 数据加载完成后,触发安全加载事件并解除限制。
  5. 玩家离开服务器时,执行已注册的数据保存器,然后释放玩家锁。
  6. 服务器关闭或执行重启时,等待当前服务器持有的玩家锁处理完成。

依赖与支持

必需条件

项目要求
前置插件LyMySQLCore 本身就是离渊系列插件的 MySQL 前置
数据库可通过 MySQL JDBC 地址连接的 MySQL 数据库
服务端 API项目使用 Paper API 1.16.5-R0.1-SNAPSHOT 编译
JavaJava 8

项目的 plugin.yml 未声明其它 Bukkit 插件依赖。项目构建文件中将 HikariCP 和 Guava 打包到插件中使用。

功能

功能说明
MySQL 连接plugins/LyMySQLCore/config.yml 读取数据库地址、端口、库名、账号、密码和连接参数
HikariCP 连接池使用 HikariCP 管理数据库连接,最小空闲连接数为 1,最大连接数根据处理器数量计算,最高为 20
玩家数据锁使用 lymysqlcore_lock 记录玩家当前持有数据锁的服务器
跨服安全玩家在其它服务器存在有效锁时,不会立即读取数据,避免多个服务器同时操作同一玩家数据
锁续期定时更新在线玩家锁的时间,维持当前服务器对玩家数据的占用状态
玩家加载流程玩家进服后先获得锁,再异步执行数据加载器
加载期间保护玩家数据加载期间限制移动、交互、丢弃物品、攻击、打开背包、点击背包和聊天
加载状态提示数据加载期间为玩家添加失明效果,加载完成后移除该效果
加载失败处理数据加载异常或长时间无法获得锁时,清理状态并将在线玩家踢出服务器
玩家离服保存玩家退出或被踢出时执行数据保存,并释放玩家锁
周期保存定时为在线且已完成加载的玩家执行周期保存
服务器关闭保护服务器关闭时等待当前服务器持有的玩家锁处理完成,最多循环等待十次
玩家日志使用 lymysqlcore_playerlog 记录玩家名称和 UUID
数据供应器允许其它插件注册数据加载、保存和周期保存处理器
优先级处理MySQLSupplierPriority 顺序执行已注册的数据供应器
Bukkit 事件提供玩家加载、保存和周期保存事件供其它插件监听

指令

项目的 plugin.yml 未声明任何命令。

指令说明
LyMySQLCore 不提供玩家或管理员指令

权限

项目的 plugin.yml 未声明任何权限节点。

权限说明
LyMySQLCore 不提供权限节点

配置文件

配置文件位置:

text
plugins/LyMySQLCore/config.yml

当前项目配置文件如下:

yaml
mysql:
  databasename: mc2
  username: mc2
  password: mc1234
  port: 3306
  ip: 127.0.0.1
  link: 'useSSL=false&serverTimezone=UTC&characterEncoding=UTF-8'

join-load-delay: 20

mysql

mysql 用于配置数据库连接信息。数据库配置正确并且连接成功后,后续的玩家数据锁、数据加载和数据保存流程才会正常运行。

配置键类型默认值说明
mysql.databasename字符串mc2MySQL 数据库名称
mysql.username字符串mc2MySQL 登录用户名
mysql.password字符串mc1234MySQL 登录密码
mysql.port整数3306MySQL 服务端口
mysql.ip字符串127.0.0.1MySQL 服务器地址
mysql.link字符串useSSL=false&serverTimezone=UTC&characterEncoding=UTF-8拼接到 JDBC 地址后的连接参数

插件最终使用的 JDBC 地址格式为:

text
jdbc:mysql://<ip>:<port>/<databasename>?<link>

其中 <ip><port><databasename><link> 对应配置文件中的实际值。

join-load-delay

配置键类型默认值单位说明
join-load-delay长整数20tick数据加载完成后,触发 MySQLSafePlayerLoadEvent 并解除玩家加载状态前的延迟

20 tick 通常对应约 1 秒,但实际时间取决于服务器 tick 执行情况。

启动流程

插件启用时会执行以下操作:

  1. 读取或生成 config.yml
  2. 根据 mysql 配置建立 HikariCP 数据库连接池。
  3. 创建 lymysqlcore_lock 数据表。
  4. 创建 lymysqlcore_playerlog 数据表。
  5. 注册玩家锁监听器。
  6. 启动锁续期、周期保存和玩家数据加载任务。
  7. 清理当前服务器名称对应的旧锁记录。
  8. 输出初始化成功信息。

如果数据库连接或数据表初始化失败,插件会输出初始化失败信息并禁用自身。

数据库连接成功时,控制台会输出包含以下内容的日志:

text
[LyMySQLCore] 数据库连接成功
[LyMySQLCore] 初始化成功,插件启动成功!

实际日志还会包含 JDBC 地址、连接池连接数量和耗时。

玩家数据流程

进入服务器

玩家加入服务器后,插件会:

  • 记录玩家名称和 UUID;
  • 记录玩家进入时的位置;
  • 将玩家标记为等待获得数据锁;
  • 为玩家添加失明效果;
  • 检查玩家是否在其它服务器持有有效锁;
  • 获取锁后,异步执行所有已注册供应器的 loadData
  • 加载完成后,等待 join-load-delay 配置的 tick 数;
  • 触发 MySQLSafePlayerLoadEvent
  • 标记玩家数据为已加载并移除失明效果。

如果玩家在其它服务器存在未过期锁,插件会持续尝试获取锁。尝试次数达到 100 次后,玩家会被踢出并显示“数据加载失败,请稍后再试”。

数据加载期间

玩家处于 CONNECTINGLOADINGFAILED 阶段时,插件会视为仍在加载数据,并限制以下操作:

操作处理
移动取消移动并尝试传送回进入时的位置
交互取消玩家交互
丢弃物品取消丢弃物品
攻击实体取消玩家作为攻击者造成的伤害
点击背包取消背包点击
打开背包取消打开背包
聊天取消聊天消息

离开服务器

玩家退出或被踢出时,插件会:

  1. 检查玩家是否持有当前服务器的数据锁。
  2. 防止重复执行离服流程。
  3. 执行所有已注册供应器的 saveData
  4. 触发 MySQLSafePlayerSaveEvent
  5. 释放当前服务器持有的玩家锁。
  6. 清理玩家运行状态。

周期保存

插件会定时处理在线且已完成数据加载的玩家,并调用供应器的 cycleSaveData

供应器的周期保存任务会尽量分散执行:当某个供应器距离上次周期保存超过 160 秒,或随机条件满足时,才会进入本轮周期保存。周期保存完成后,会为对应玩家触发 MySQLSafePlayerCycleSaveEvent

服务器关闭

服务器关闭时,插件会检查当前服务器是否仍有有效玩家锁:

  • 如果仍有锁,输出等待日志并每次等待 1 秒;
  • 最多尝试等待 10 次;
  • 检测到没有当前服务器锁后,输出数据保存完成日志并继续关闭服务器。

数据表

插件启动时会自动创建以下数据表。业务插件自己的数据表不由 LyMySQLCore 创建。

lymysqlcore_lock

用于记录玩家数据锁和服务器锁状态。

字段类型说明
idint(16) UNSIGNED自增主键
player_namevarchar(32)玩家名称,唯一
servervarchar(32)持有锁的服务器名称;释放后使用内部解锁标记
lock_timetimestamp锁的创建或更新时间

表使用 InnoDB 引擎和 utf8mb4 字符集,并创建玩家名称、玩家名称与服务器、服务器相关索引。

lymysqlcore_playerlog

用于记录玩家名称和 UUID。

字段类型说明
idint(16) UNSIGNED自增主键
player_namevarchar(128)玩家名称,唯一
uuidvarchar(36)玩家 UUID

表使用 utf8mb4 字符集,并创建玩家名称与 UUID 的联合唯一索引。

数据供应器 API

其它插件可以通过 MySQLSupplierManager 注册数据供应器。供应器负责实现具体业务数据的加载、保存和周期保存,LyMySQLCore 负责按照优先级调用这些方法。

MySQLSupplier

实现接口:

java
public interface MySQLSupplier {
    String getName();

    void loadData(OfflinePlayer player);

    void saveData(OfflinePlayer player);

    void cycleSaveData(OfflinePlayer player);
}
方法说明
getName()返回供应器名称。名称用于识别供应器,不能与已注册供应器重复
loadData(OfflinePlayer player)加载玩家业务数据
saveData(OfflinePlayer player)保存玩家业务数据
cycleSaveData(OfflinePlayer player)执行周期保存

注册供应器

不指定优先级时,供应器使用 NORMAL

java
MySQLSupplierManager.registerSupplier(supplier);

指定优先级时:

java
MySQLSupplierManager.registerSupplier(supplier, MySQLSupplierPriority.HIGH);

如果已有供应器使用相同名称,注册时会抛出异常。

删除供应器

按名称删除:

java
MySQLSupplierManager.deleteSupplier("供应器名称");

按供应器对象删除:

java
MySQLSupplierManager.deleteSupplier(supplier);

查询供应器

java
Set<MySQLSupplierWrapper> suppliers = MySQLSupplierManager.getSuppliers();

getSuppliers() 返回当前已注册供应器集合。MySQLSupplierWrapper 为内部包装类型,业务插件通常只需要使用注册和删除方法。

供应器优先级

供应器按照以下顺序执行,数值越小越先执行:

优先级数值
LOWEST0
LOW1
NORMAL2
HIGH3
HIGHEST4
MONITOR5

加载、保存和周期保存都会按照这个顺序遍历供应器。

Bukkit 事件

MySQLSafePlayerLoadEvent

玩家数据供应器加载完成,并且等待 join-load-delay 延迟后触发。监听此事件后,可以开始执行依赖玩家数据的相关操作。

java
@EventHandler
public void onPlayerDataLoad(MySQLSafePlayerLoadEvent event) {
    Player player = event.getPlayer();
}

MySQLSafePlayerSaveEvent

玩家离开服务器时,供应器完成数据保存后触发。

java
@EventHandler
public void onPlayerDataSave(MySQLSafePlayerSaveEvent event) {
    Player player = event.getPlayer();
}

MySQLSafePlayerCycleSaveEvent

玩家完成一次周期保存后触发。

java
@EventHandler
public void onPlayerCycleSave(MySQLSafePlayerCycleSaveEvent event) {
    Player player = event.getPlayer();
}

玩家状态机制

插件使用 PlayerPhase 标记玩家当前的数据处理阶段:

状态说明
CONNECTING玩家刚进入服务器,等待获得数据锁
LOADING已获得数据锁,正在异步加载数据
LOADED玩家数据已经加载完成,可以正常参与游戏
LEAVING玩家离服保存流程已经开始,用于避免重复保存
FAILED玩家数据加载失败,等待清理状态并踢出

运行任务

插件启动后会注册以下定时任务:

任务首次延迟执行间隔作用
玩家锁续期30 * 20 tick30 * 20 tick更新已加载玩家的锁时间
周期保存20 * 20 tick20 * 20 tick为在线玩家执行周期保存
玩家数据加载检查2 tick2 tick检查进服玩家并推进数据加载流程

这些任务由插件内部自动注册,不提供配置项调整执行间隔。

常见问题

数据库连接失败怎么办?

检查 plugins/LyMySQLCore/config.yml 中的以下配置:

  • mysql.databasename
  • mysql.username
  • mysql.password
  • mysql.port
  • mysql.ip
  • mysql.link

确认数据库服务正在运行,账号具有对应数据库的访问权限,并确认服务器能够访问数据库地址。连接失败时,插件会输出数据库连接失败日志并在初始化失败后禁用自身。

为什么依赖 MySQL 的功能没有效果?

确认以下条件全部满足:

  1. LyMySQLCore 已放入插件目录并成功加载。
  2. plugins/LyMySQLCore/config.yml 已填写正确。
  3. 控制台出现数据库连接成功日志。
  4. 控制台出现初始化成功日志。
  5. 相关业务插件确实使用了 MySQL 存储。

只安装业务插件而没有安装 LyMySQLCore,或数据库没有成功连接时,相关 MySQL 功能不会生效。

玩家为什么进服后不能移动或操作?

这是插件的数据加载保护机制。玩家处于 CONNECTINGLOADINGFAILED 状态时,插件会限制移动、交互、背包操作、聊天和攻击,避免玩家在业务数据加载完成前操作插件功能。

数据成功加载、触发 MySQLSafePlayerLoadEvent 并完成延迟放行后,限制会解除。

玩家为什么被提示“数据加载失败,请稍后再试”?

常见原因包括:

  • 数据库连接异常;
  • 其它服务器仍持有该玩家的有效数据锁;
  • 数据供应器加载数据时抛出异常;
  • 玩家长时间无法获得数据锁,尝试次数达到 100 次。

应先检查数据库连接日志、跨服服务器状态和业务插件的数据加载逻辑。

插件会自动创建数据表吗?

会。插件启动时会创建:

  • lymysqlcore_lock
  • lymysqlcore_playerlog

业务插件自己的数据表不由 LyMySQLCore 创建,具体由对应业务插件负责。

为什么服务器关闭时会等待?

插件会等待当前服务器持有的玩家锁处理完成,避免服务器关闭时仍有玩家数据没有完成保存。等待期间控制台会输出等待日志。

业务插件应该什么时候读取玩家数据?

业务插件应监听 MySQLSafePlayerLoadEvent,在该事件触发后再执行依赖玩家数据的操作。玩家数据成功读取前,不应允许玩家进行依赖该数据的业务操作。

业务插件应该如何保存玩家数据?

建议通过 MySQLSupplier 注册数据供应器,并实现 loadDatasaveDatacycleSaveData。这样可以接入 LyMySQLCore 的统一加载、离服保存和周期保存流程。