Skip to content

相关:API概览.md · 工具集.md · GUI编程API.md · ../05-参考/术语表.md

💾 数据存储与占位符(PdcService / DatabaseManager / PapiBridge)

本页讲 QCL 给模块开发者的三块持久化 / 取数能力:

  1. PdcService —— 给任意 PersistentDataContainer(物品、实体等)读写带命名空间的数据。
  2. DatabaseManager —— SQLite / MySQL 数据库门面,含连接获取与序列化工具。
  3. PapiBridge + QinhPlaceholderProvider —— PlaceholderAPI 桥,注册自定义 %qcl_xxx%、安全解析、未装降级。

一、PdcService —— 持久化数据容器服务

包:com.qinhuai.corelib.pdc

PDC(PersistentDataContainer)是 Bukkit 给物品 / 实体等挂自定义数据的标准容器。PdcService 在它之上加了命名空间隔离类型化读写,让各模块互不踩键。

1.1 取得一个命名空间的服务

kotlin
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 保证不与其他插件碰撞,命名空间段再做模块内隔离。

kotlin
val key: NamespacedKey = pdc.createKey("level")   // → qinhcorelib:mymodule_level

1.3 类型化读写

PdcService 的读写方法作用于任意 PersistentDataContainer(物品 meta、实体等):

类型
StringsetString(container, key, value)getString(...)
IntsetInt(...)getInt(...) / getIntOrDefault(..., default)
LongsetLong(...)getLong(...)
DoublesetDouble(...)getDouble(...)
BooleansetBoolean(...)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 给物品存数据

kotlin
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 给实体存数据

kotlin
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 诊断

kotlin
PdcServiceManager.diagnoseAll()   // 排查命名空间 / 键状态

二、DatabaseManager —— 数据库门面

包:com.qinhuai.corelib.database

2.1 初始化与获取

kotlin
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 段决定:

yaml
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 —— 连接与序列化工具

kotlin
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 → 背包
kotlin
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 解析文本

kotlin
PapiBridge.isEnabled()                       // PAPI 是否可用

val s1 = PapiBridge.apply(player, "你好 %player_name%!")     // 在线玩家
val s2 = PapiBridge.apply(offlinePlayer, "积分:%qcl_points%") // 离线玩家

🛟 降级与短路apply 只在文本% 时才真正去解析;未装 PAPI 时直接原样返回,安全降级。

3.2 注册自定义占位符 %qcl_xxx%

实现 QinhPlaceholderProvider 接口并注册,就能让 %标识_参数% 走到你的代码:

kotlin
interface QinhPlaceholderProvider {
    val identifier: String                                  // 如 "qcl" → %qcl_xxx%
    fun onRequest(player: OfflinePlayer?, params: String): String?  // 返回 null = 不解析
}
  • identifier —— 占位符前缀,"qcl" 对应 %qcl_...%
  • onRequest —— paramsidentifier_ 之后的那部分;返回 null 表示「这个我不认,交回 PAPI」。
kotlin
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% 都会被解析。


📖 继续阅读