相关:API概览.md · 工具集.md · GUI编程API.md · ../05-参考/术语表.md
💾 数据存储与占位符(PdcService / DatabaseManager / PapiBridge)
本页讲 QCL 给模块开发者的三块持久化 / 取数能力:
- PdcService —— 给任意
PersistentDataContainer(物品、实体等)读写带命名空间的数据。 - DatabaseManager —— SQLite / MySQL 数据库门面,含连接获取与序列化工具。
- PapiBridge + QinhPlaceholderProvider —— PlaceholderAPI 桥,注册自定义
%qcl_xxx%、安全解析、未装降级。
一、PdcService —— 持久化数据容器服务
包:com.qinhuai.corelib.pdc
PDC(PersistentDataContainer)是 Bukkit 给物品 / 实体等挂自定义数据的标准容器。PdcService 在它之上加了命名空间隔离和类型化读写,让各模块互不踩键。
1.1 取得一个命名空间的服务
val pdc: PdcService = PdcServiceManager.get("mymodule") // 按命名空间取服务
PdcServiceManager.allNamespaces() // 列出所有已注册命名空间
PdcServiceManager.registerStatus("mymodule")
PdcServiceManager.diagnoseAll() // 诊断所有命名空间1.2 键规则
createKey(key) 生成的 NamespacedKey 形如:
qinhcorelib:命名空间_键名例如命名空间 mymodule、键 level,最终 key 为 qinhcorelib:mymodule_level。统一前缀 qinhcorelib 保证不与其他插件碰撞,命名空间段再做模块内隔离。
val key: NamespacedKey = pdc.createKey("level") // → qinhcorelib:mymodule_level1.3 类型化读写
PdcService 的读写方法作用于任意 PersistentDataContainer(物品 meta、实体等):
| 类型 | 写 | 读 |
|---|---|---|
| String | setString(container, key, value) | getString(...) |
| Int | setInt(...) | getInt(...) / getIntOrDefault(..., default) |
| Long | setLong(...) | getLong(...) |
| Double | setDouble(...) | getDouble(...) |
| Boolean | setBoolean(...) | getBoolean(...) / getBooleanOrDefault(..., default) |
| 通用 | set(container, key, type, value) | get(container, key, type) |
| —— | has(container, key) / remove(container, key) | —— |
🧷 Boolean 底层用 int 存(
1/0),读写都由PdcService自动转换,你看到的是Boolean。
1.4 给物品存数据
val pdc = PdcServiceManager.get("mymodule")
val item = ItemStack(Material.DIAMOND_SWORD)
val meta = item.itemMeta!!
val container = meta.persistentDataContainer
pdc.setInt(container, "level", 5)
pdc.setString(container, "owner", player.uniqueId.toString())
pdc.setBoolean(container, "bound", true)
item.itemMeta = meta // 别忘了写回 meta
// 读
val level = pdc.getIntOrDefault(item.itemMeta!!.persistentDataContainer, "level", 0)
val bound = pdc.getBooleanOrDefault(item.itemMeta!!.persistentDataContainer, "bound", false)1.5 给实体存数据
val pdc = PdcServiceManager.get("mymodule")
val container = entity.persistentDataContainer
pdc.setLong(container, "spawnedAt", System.currentTimeMillis())
if (pdc.has(container, "spawnedAt")) {
val t = pdc.getLong(container, "spawnedAt")
}
pdc.remove(container, "spawnedAt")1.6 诊断
PdcServiceManager.diagnoseAll() // 排查命名空间 / 键状态二、DatabaseManager —— 数据库门面
包:com.qinhuai.corelib.database
2.1 初始化与获取
DatabaseManager.init(databaseConfig) // 用 DatabaseConfig 初始化
val db: QclDatabase = DatabaseManager.get() // 取数据库实例
DatabaseManager.getType() // DatabaseType.SQLITE 或 .MYSQL
DatabaseManager.isMySQL() // Boolean
DatabaseManager.isSQLite() // Boolean
DatabaseManager.bridgeStatus() // 桥状态
DatabaseManager.diagnose() // 诊断DatabaseType 枚举:SQLITE / MYSQL。
2.2 config 的 database 段
数据库类型在配置里的 database 段决定:
database:
type: sqlite # sqlite 或 mysql
sqlite:
data-folder: data # 相对 plugins/QinhCoreLib/ 的数据目录
mysql:
host: localhost
port: 3306
database: qcl
user: root
password: ""- SQLite 时数据存在
plugins/QinhCoreLib/data/,分为global.db(全局)与每玩家一个{uuid}.db。
2.3 QclDatabase —— 连接与序列化工具
val db = DatabaseManager.get()
// 取连接:owner 传玩家 UUID → 对应 {uuid}.db;传 null → 全局库 global.db
val conn: Connection = db.getConnection(player.uniqueId)
val globalConn = db.getConnection(null)
// ... 用 JDBC 操作 ...
db.close()序列化工具(存进数据库时常用):
| 工具 | 作用 |
|---|---|
serializeLocation(location) | 位置 → 字符串 |
deserializeLocation(world, x, y, z, yaw, pitch) | 反序列化位置 |
serializeLocations(...) | 多个位置(分号分隔) |
serializeInventory(inventory) | 背包 → Base64 字符串 |
deserializeInventory(base64) | Base64 → 背包 |
val db = DatabaseManager.get()
// 存背包
val base64 = db.serializeInventory(player.inventory)
// ... 写入数据库字段 ...
// 取回
val inv = db.deserializeInventory(base64)
// 存位置
val locStr = db.serializeLocation(player.location)三、PapiBridge + 自定义占位符
包:com.qinhuai.corelib.placeholder
PapiBridge 封装 PlaceholderAPI(PAPI):未装 PAPI 时安全降级,不报错也不崩。
3.1 解析文本
PapiBridge.isEnabled() // PAPI 是否可用
val s1 = PapiBridge.apply(player, "你好 %player_name%!") // 在线玩家
val s2 = PapiBridge.apply(offlinePlayer, "积分:%qcl_points%") // 离线玩家🛟 降级与短路:
apply只在文本含%时才真正去解析;未装 PAPI 时直接原样返回,安全降级。
3.2 注册自定义占位符 %qcl_xxx%
实现 QinhPlaceholderProvider 接口并注册,就能让 %标识_参数% 走到你的代码:
interface QinhPlaceholderProvider {
val identifier: String // 如 "qcl" → %qcl_xxx%
fun onRequest(player: OfflinePlayer?, params: String): String? // 返回 null = 不解析
}identifier—— 占位符前缀,"qcl"对应%qcl_...%。onRequest——params是identifier_之后的那部分;返回null表示「这个我不认,交回 PAPI」。
class MyProvider : QinhPlaceholderProvider {
override val identifier = "qcl"
override fun onRequest(player: OfflinePlayer?, params: String): String? {
return when (params) {
"points" -> getPoints(player).toString() // %qcl_points%
"level" -> getLevel(player).toString() // %qcl_level%
else -> null // 不认的交回 PAPI
}
}
}
// 注册 / 注销
val ok: Boolean = PapiBridge.register(myPlugin, MyProvider())
PapiBridge.unregister(myPlugin)
PapiBridge.bridgeStatus()
PapiBridge.diagnose()注册后,任意配置 / GUI / 消息里的 %qcl_points%、%qcl_level% 都会被解析。
📖 继续阅读
- GUI编程API.md —— GUI 里如何用占位符与数据源
- 工具集.md —— 反射桥等底层工具
- API概览.md —— QCL 对外 API 全貌
- ../05-参考/术语表.md —— 术语定义