设计与配置
宠物生命周期、数据结构、行为系统和扩展设计。
文档版本:0.1
插件版本:0.11.0-SNAPSHOT
基准环境:Paper/Folia 1.21.11、26.1.2、Paper 26.2 / Bukkit 标准 API
当前阶段:框架设计,不实现具体宠物玩法
1. 项目定位
LiPet 是一个面向 Minecraft 群组服务器的宠物插件。
玩家可以在不同子服务器之间共享自己的宠物数据。宠物在切服、服务器重启或异常掉线后,必须保持等级、经验、属性、技能、背包及状态一致。
设计参考 MyPet 的以下思想:
- API 与实现分离。
- 使用 Repository 隔离存储实现。
- 使用 Manager 组织业务逻辑。
- 宠物能力和技能由配置驱动。
- 为 Vault、PlaceholderAPI 等插件提供独立 Hook。
LiPet 不复制 MyPet 源码,也不直接沿用其集中式主类结构。
2. 第一阶段目标
第一阶段只建设可持续开发的工程框架:
- 建立明确的代码分层。
- 定义宠物领域模型。
- 定义持久化接口。
- 定义群组同步接口。
- 建立配置与消息系统。
- 建立主指令和 Tab 补全结构。
- 预留 Vault、PlaceholderAPI Hook。
- 预留运行时依赖下载机制。
- 确定跨服数据一致性方案。
第一阶段不开发:
- 宠物捕捉。
- 宠物召唤与实体生成。
- 宠物战斗。
- 宠物技能。
- 宠物背包。
- 宠物 GUI。
- 宠物交易和商店。
3. 核心设计原则
3.1 单一职责
每个类只承担一种职责:
- 主类只转发生命周期。
- Bootstrap 只负责组件组装。
- Command 只负责解析输入。
- Listener 只负责接收 Bukkit 事件。
- Manager 只负责业务用例。
- Repository 只负责持久化。
- Sync Gateway 只负责跨服同步。
- Hook 只负责第三方插件适配。
3.2 依赖方向
上层可以依赖下层接口,但领域层不得反向依赖 Bukkit 实体或数据库实现。
Bukkit 入口层
↓
指令层 / 监听器层
↓
业务管理器层
↓
领域模型与 API
↓
Repository / Cluster Gateway 接口
↑
MySQL / Redis 具体实现
- 业务逻辑优先使用 Bukkit 标准 API。
- Paper/Folia 调度、骑乘输入等必须依赖高版本 API 的能力统一封装或集中在监听器中,避免散落到业务层。
- 不使用 NMS。
- Paper API 仅作为编译期依赖。
- 如果未来确实需要版本适配,必须放入独立兼容层。
4. 代码分层
建议基础包:
cn.lipet.bukkit
目录结构:
cn.lipet.bukkit
├─ LiPetPlugin 插件主类
├─ bootstrap 启动和关闭编排
├─ api 对外公开 API
│ ├─ model 稳定数据模型
│ ├─ repository 存储接口
│ └─ sync 群组同步接口
├─ command 主指令执行器与补全器
│ └─ subcommand 独立子指令
├─ config 配置读取、保存和重载
├─ dependency 运行时依赖管理
├─ hook 第三方插件适配
├─ listener Bukkit 事件监听器
├─ manager 业务管理器
├─ repository Repository 实现
├─ sync 群组同步实现
└─ util 无状态通用工具
当公开 API 稳定后,可拆分为:
lipet-parent
├─ lipet-api
├─ lipet-core
├─ lipet-bukkit
└─ lipet-runtime
当前阶段采用单模块,避免框架尚未稳定时增加构建复杂度。
5. 插件生命周期
5.1 启动过程
LiPetPlugin#onEnable
↓
加载 config.yml 和 messages.yml
↓
检查服务器 ID、群组 ID 和配置合法性
↓
检查并加载运行时依赖
↓
创建 Repository
↓
创建 ClusterSyncGateway
↓
创建业务 Manager
↓
注册 LiPetApi
↓
注册指令与 Tab 补全
↓
注册监听器
↓
注册 Vault / PlaceholderAPI Hook
↓
启动服务器心跳与同步订阅
任意关键组件启动失败时:
- 输出明确的中文错误。
- 不直接向用户打印无意义堆栈。
- 安全关闭已启动的组件。
- 禁用 LiPet,防止带病运行。
5.2 关闭过程
停止接受新的宠物激活请求
↓
冻结当前服务器的活动宠物
↓
保存所有待保存状态
↓
释放宠物租约
↓
停止 Redis 订阅和服务器心跳
↓
关闭数据库连接池
↓
注销 Hook 和公开 API
6. 宠物领域模型
宠物数据模型不得直接保存 Bukkit Entity 对象。
基础模型建议包含:
PetProfile
├─ petId 宠物全局 UUID
├─ ownerId 主人 UUID
├─ petType 宠物类型标识
├─ displayName 宠物名称
├─ level 等级
├─ experience 经验
├─ health 当前生命值
├─ attributes 属性数据
├─ skills 技能数据
├─ inventory 背包数据
├─ state 持久状态
├─ revision 数据修订号
├─ createdAt 创建时间
└─ updatedAt 更新时间
宠物状态建议定义为:
STORED 已存放,不生成实体
ACTIVE 已在某台服务器激活
TRANSFERRING 正在切服
DEAD 已死亡,等待复活
DISABLED 被管理员禁用
运行时实体与持久数据分离:
PetProfile 可存储、可跨服
PetSession 当前服务器内的活动会话
Bukkit Entity 游戏世界中的实际实体
7. Repository 设计
核心层只依赖 PetRepository 接口。
主要职责:
- 按宠物 UUID 查询。
- 按主人 UUID 查询宠物列表。
- 新增宠物。
- 更新宠物。
- 删除宠物。
- 使用 revision 进行乐观锁校验。
所有数据库操作必须异步执行,禁止阻塞 Bukkit 主线程。
建议实现:
PetRepository
├─ InMemoryPetRepository 测试和框架验证
└─ MySqlPetRepository 生产环境
SQLite 仅适合单服开发和测试,不用于群组服共享数据。
8. 群组互通设计
8.1 组件职责
群组互通由 MySQL 和 Redis 共同完成:
| 组件 | 职责 |
|---|---|
| MySQL | 保存宠物权威数据 |
| Redis | 消息通知、缓存失效、服务器心跳、宠物租约 |
| Bukkit 子服 | 维护当前服务器的宠物实体和会话 |
Redis 不作为宠物永久数据源。
8.2 宠物租约
同一只宠物不能同时在两台服务器生成。
Redis 中为活动宠物建立带 TTL 的租约:
lipet:{group}:lease:{petId} = serverId
激活宠物前:
- 子服尝试原子获取租约。
- 获取成功后读取 MySQL 最新数据。
- 在 Bukkit 主线程生成实体。
- 定期续租。
获取失败时,说明宠物已在其他服务器活动。
8.3 玩家切服流程
源服务器
↓
将宠物状态改为 TRANSFERRING
↓
移除宠物实体
↓
保存数据并增加 revision
↓
发布 PET_DEACTIVATED
↓
释放租约
目标服务器
↓
等待玩家加入
↓
获取宠物租约
↓
从 MySQL 读取最新 revision
↓
创建 PetSession
↓
在主线程生成宠物实体
↓
发布 PET_ACTIVATED
8.4 异常宕机恢复
租约必须带 TTL。
如果源服务器异常关闭:
- Redis 租约在 TTL 到期后自动失效。
- 目标服务器不能立即强制抢占,避免短暂网络抖动造成双实例。
- 租约到期后重新读取 MySQL 最新数据。
- 管理员可以使用恢复指令清理异常状态。
8.5 数据冲突
每条宠物记录包含 revision。
保存时执行:
UPDATE lipet_pet
SET data = ?, revision = revision + 1
WHERE pet_id = ? AND revision = ?
更新行数为零时表示版本冲突。
发生冲突后不得直接覆盖,应:
- 丢弃当前旧快照。
- 重新读取数据库数据。
- 根据业务类型决定重试或终止。
- 记录中文警告日志。
9. Redis 消息模型
建议统一消息结构:
{
"messageId": "UUID",
"groupId": "main",
"sourceServerId": "survival-1",
"petId": "UUID",
"eventType": "PET_UPDATED",
"revision": 12,
"createdAt": "2026-06-18T10:00:00Z"
}
事件类型:
PET_UPDATEDPET_ACTIVATEDPET_DEACTIVATEDPET_DELETEDCACHE_INVALIDATEDSERVER_SHUTTING_DOWN
消费者必须根据 messageId 去重,并忽略来自自己的重复消息。
10. 数据库设计草案
10.1 宠物主表
CREATE TABLE lipet_pet (
pet_id CHAR(36) PRIMARY KEY,
owner_id CHAR(36) NOT NULL,
pet_type VARCHAR(64) NOT NULL,
display_name VARCHAR(128) NOT NULL,
level INT NOT NULL,
experience BIGINT NOT NULL,
state VARCHAR(32) NOT NULL,
data_json JSON NOT NULL,
revision BIGINT NOT NULL DEFAULT 0,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL,
INDEX idx_lipet_pet_owner (owner_id)
);
10.2 服务器状态表
Redis 是服务器心跳的首选实现。MySQL 可额外保存服务器最后一次正常关闭记录,用于审计,不依赖该表进行实时在线判断。
10.3 数据迁移
数据库结构必须有版本:
lipet_schema_history
├─ version
├─ description
├─ checksum
└─ installed_at
插件升级时只允许向前迁移,不在启动时执行破坏性降级。
11. 指令设计
主指令:
/lipet
框架阶段:
/lipet status
/lipet reload
后续规划:
/lipet call [宠物]
/lipet store
/lipet list
/lipet info [宠物]
/lipet rename <名称>
/lipet skill
/lipet admin recover <玩家>
/lipet admin unlock <宠物UUID>
/lipet admin migrate
每个子指令必须:
- 使用独立执行类。
- 提供独立参数补全。
- 声明明确权限。
- 通过统一工具检查玩家或控制台身份。
- 不在代码中硬编码用户消息。
12. 配置文件设计
基础配置:
config.yml 服务器、存储、Redis、依赖配置
messages.yml 所有用户消息
pet-types.yml 宠物类型
attributes.yml 属性规则
skills.yml 技能定义
levels.yml 等级和经验
要求:
- 每个配置项带完整中文注释。
- 配置类封装读取、保存、验证和重载。
- 配置加载后转换成不可变配置对象。
- 业务代码不直接调用
getConfig()。 - 非法配置必须指出具体路径和合法范围。
13. 文本系统
所有消息使用 MiniMessage。
支持:
- RGB 颜色。
- 多色渐变。
- 粗体、斜体和下划线。
- 悬浮提示。
- 点击事件。
- 统一前缀。
- 安全占位符替换。
固定快捷渐变示例:
紫蓝渐变:#7C4DFF → #40C4FF
生命渐变:#FF5252 → #FFAB40
自然渐变:#69F0AE → #00BFA5
玩家名称、宠物名称等外部文本必须转义,不能直接拼接为 MiniMessage 标签。
14. 运行时依赖
外部公共依赖不打入 LiPet Jar。
运行时依赖统一存放:
plugins/LiPet/lib/
依赖管理器负责:
- 检测类是否已由服务器提供。
- 根据固定 URL 下载依赖。
- 校验 SHA-256。
- 使用隔离类加载器加载。
- 输出中文缺失提示。
- 下载失败时安全停止对应功能。
规划依赖:
- MySQL Connector/J。
- HikariCP 或自研轻量连接管理适配。
- Redis 客户端。
- MiniMessage 仅在服务器未提供时处理。
依赖版本和哈希必须固定,不能每次启动自动选择最新版。
15. 第三方插件适配
Vault
用途:
- 宠物购买费用。
- 复活费用。
- 技能升级费用。
Vault 缺失时,经济功能关闭,但基础宠物功能可以继续运行。
PlaceholderAPI
预留变量:
%lipet_active_name%
%lipet_active_level%
%lipet_active_health%
%lipet_pet_count%
%lipet_server_id%
核心业务不得直接依赖 PlaceholderAPI 类型。
16. 线程模型
Bukkit 主线程执行:
- 创建和移除实体。
- 修改实体属性。
- 操作背包界面。
- 发送 Bukkit 事件。
异步线程执行:
- MySQL 查询与保存。
- Redis 发布和订阅。
- 依赖下载。
- 数据序列化。
异步结果返回后,涉及 Bukkit API 的操作必须切回主线程。
禁止:
- 在主线程等待
CompletableFuture#get()。 - 在 Redis 回调线程直接操作实体。
- 多线程同时修改同一个
PetSession。
17. 错误处理与日志
日志格式建议:
[LiPet] [模块] 中文说明 | context=value
示例:
[LiPet] [Storage] 宠物数据保存失败 | petId=... ownerId=...
[LiPet] [Cluster] 宠物租约获取失败 | petId=... holder=survival-2
普通配置错误和依赖缺失不直接输出完整堆栈。
调试模式开启时,可以把详细异常写入独立日志文件。
18. 安全与可靠性
- 所有 SQL 使用预编译参数。
- Redis Key 必须带群组命名空间。
- 下载依赖必须校验 SHA-256。
- 宠物名称和配置文本必须进行 MiniMessage 转义。
- 管理指令必须有独立权限。
- 删除和强制解锁操作必须记录审计日志。
- 服务器 ID 在同一群组内必须唯一。
19. 测试策略
单元测试
- 宠物状态转换。
- revision 冲突。
- 配置验证。
- 消息占位符转义。
- 租约状态判断。
集成测试
- MySQL Repository 增删改查。
- Redis 租约原子性。
- Redis 消息去重。
- 数据库迁移。
群组场景测试
- 玩家正常切服。
- 源服务器切服过程中崩溃。
- Redis 短暂断线。
- MySQL 短暂断线。
- 两台服务器同时召唤同一宠物。
- 旧 revision 尝试覆盖新数据。
20. 开发里程碑
M0:工程框架
- 分层目录。
- Bukkit 主类。
- Bootstrap。
- 配置和消息系统。
- Repository 和 Cluster Gateway 接口。
- 指令框架。
- Hook 模板。
M1:基础设施
- 运行时依赖下载与类加载。
- MySQL Repository。
- 数据库迁移器。
- Redis Gateway。
- 心跳与租约。
M2:宠物生命周期
- 宠物创建。
- 宠物存放。
- 宠物召唤。
- 实体移除。
- 玩家上线和离线处理。
- 群组切服状态机。
M3:宠物成长
- 属性系统。
- 等级和经验。
- 技能框架。
- 配置驱动宠物类型。
M4:扩展功能
- 宠物背包。
- GUI。
- Vault。
- PlaceholderAPI。
- 管理工具。
M5:稳定性
- 压力测试。
- 故障恢复测试。
- 数据迁移测试。
- API 文档。
- 正式版发布。
21. 暂定技术决策
| 项目 | 决策 |
|---|---|
| 构建工具 | Maven |
| 插件形态 | 第一阶段单 Jar |
| 编译目标 | Java 21 字节码;Paper 26.1.2 / 26.2 运行端推荐 Java 25 |
| 服务端基准 | Paper/Folia 1.21.11、26.1.2、Paper 26.2 |
| Bukkit API | 只使用标准兼容接口 |
| 权威存储 | MySQL |
| 群组同步 | Redis |
| 并发控制 | Redis 租约 + MySQL revision |
| 文本格式 | MiniMessage |
| 默认测试存储 | 内存 Repository |
| 第三方依赖 | 运行时下载,不打入插件 Jar |
22. 当前结论
LiPet 的核心不是先实现宠物实体,而是先保证:
- 数据模型不依赖具体服务器。
- 同一宠物不会跨服重复激活。
- 旧服务器数据不会覆盖新服务器数据。
- 数据库和 Redis 故障不会阻塞 Bukkit 主线程。
- 后续技能、背包、经济和 GUI 可以通过独立模块扩展。
在 MySQL Repository、Redis 租约和玩家切服状态机完成前,不应开始大规模开发宠物玩法。