mirai/mirai-console/docs/PluginData.md

254 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Mirai Console Backend - PluginData
[`Plugin`]: ../backend/mirai-console/src/plugin/Plugin.kt
[`PluginDescription`]: ../backend/mirai-console/src/plugin/description/PluginDescription.kt
[`PluginLoader`]: ../backend/mirai-console/src/plugin/loader/PluginLoader.kt
[`PluginManager`]: ../backend/mirai-console/src/plugin/PluginManager.kt
[`JvmPluginLoader`]: ../backend/mirai-console/src/plugin/jvm/JvmPluginLoader.kt
[`JvmPlugin`]: ../backend/mirai-console/src/plugin/jvm/JvmPlugin.kt
[`JvmPluginDescription`]: ../backend/mirai-console/src/plugin/jvm/JvmPluginDescription.kt
[`AbstractJvmPlugin`]: ../backend/mirai-console/src/plugin/jvm/AbstractJvmPlugin.kt
[`KotlinPlugin`]: ../backend/mirai-console/src/plugin/jvm/KotlinPlugin.kt
[`JavaPlugin`]: ../backend/mirai-console/src/plugin/jvm/JavaPlugin.kt
[`Value`]: ../backend/mirai-console/src/data/Value.kt
[`PluginData`]: ../backend/mirai-console/src/data/PluginData.kt
[`AbstractPluginData`]: ../backend/mirai-console/src/data/AbstractPluginData.kt
[`AutoSavePluginData`]: ../backend/mirai-console/src/data/AutoSavePluginData.kt
[`AutoSavePluginConfig`]: ../backend/mirai-console/src/data/AutoSavePluginConfig.kt
[`PluginConfig`]: ../backend/mirai-console/src/data/PluginConfig.kt
[`PluginDataStorage`]: ../backend/mirai-console/src/data/PluginDataStorage.kt
[`MultiFilePluginDataStorage`]: ../backend/mirai-console/src/data/PluginDataStorage.kt#L116
[`MemoryPluginDataStorage`]: ../backend/mirai-console/src/data/PluginDataStorage.kt#L100
[`AutoSavePluginDataHolder`]: ../backend/mirai-console/src/data/PluginDataHolder.kt#L45
[`PluginDataHolder`]: ../backend/mirai-console/src/data/PluginDataHolder.kt
[`PluginDataExtensions`]: ../backend/mirai-console/src/data/PluginDataExtensions.kt
[`MiraiConsole`]: ../backend/mirai-console/src/MiraiConsole.kt
[`MiraiConsoleImplementation`]: ../backend/mirai-console/src/MiraiConsoleImplementation.kt
<!--[MiraiConsoleFrontEnd]: ../backend/mirai-console/src/MiraiConsoleFrontEnd.kt-->
[`Command`]: ../backend/mirai-console/src/command/Command.kt
[`CompositeCommand`]: ../backend/mirai-console/src/command/CompositeCommand.kt
[`SimpleCommand`]: ../backend/mirai-console/src/command/SimpleCommand.kt
[`RawCommand`]: ../backend/mirai-console/src/command/RawCommand.kt
[`CommandManager`]: ../backend/mirai-console/src/command/CommandManager.kt
[`Annotations`]: ../backend/mirai-console/src/util/Annotations.kt
[`ConsoleInput`]: ../backend/mirai-console/src/util/ConsoleInput.kt
[`JavaPluginScheduler`]: ../backend/mirai-console/src/plugin/jvm/JavaPluginScheduler.kt
[`ResourceContainer`]: ../backend/mirai-console/src/plugin/ResourceContainer.kt
[`PluginFileExtensions`]: ../backend/mirai-console/src/plugin/PluginFileExtensions.kt
[Kotlin]: https://www.kotlincn.net/
[Java]: https://www.java.com/zh_CN/
[JVM]: https://zh.wikipedia.org/zh-cn/Java%E8%99%9A%E6%8B%9F%E6%9C%BA
[JAR]: https://zh.wikipedia.org/zh-cn/JAR_(%E6%96%87%E4%BB%B6%E6%A0%BC%E5%BC%8F)
[为什么不支持热加载和卸载插件?]: QA.md#为什么不支持热加载和卸载插件
[使用 AutoService]: QA.md#使用-autoservice
Mirai Console 提供支持自动保存的,静态类型插件数据模型。
### 设计目标
- 源码级静态强类型:避免 `getString()`, `getList()`...
- 全自动加载保存:插件仅需在启动时通过一行代码链接自动保存
- 与前端同步修改:在 Android 等图形化前端实现中可以在内存动态同步修改
- 存储扩展性:可使用多种方式存储,无论是文件还是数据库,插件层都使用同一种实现方式
综上,**最小化插件作者在处理数据和配置做的付出**。
*暂无数据库保存支持,但这已经被提上日程。*
## [`Value`]
```kotlin
interface Value<T> : ReadWriteProperty<Any?, T> {
@get:JvmName("get")
@set:JvmName("set")
var value: T
}
```
表示一个值代理。在 [`PluginData`] 中,除简单数据类型外,值都经过 [`Value`] 包装。
## [`PluginData`]
一个插件内部的, 对用户隐藏的数据对象。类似于属性名作为键,对应 [`Value`] 作为值的 `Map`
[`PluginData`] 接口拥有一个基础实现类,[`AbstractPluginData`],默认不支持自动保存,仅存储键值关系及其序列化器。
插件可继承 [`AbstractPluginData`],拥有高自由的实现细节访问权限,并扩展数据结构。
但通常,插件使用 [`AutoSavePluginData`]。
[`AutoSavePluginData`] 监听保存在它之中的值的修改,并在合适的时机在提供的 [`AutoSavePluginDataHolder`] 协程作用域下启动协程保存数据。
### 使用 `PluginData`
示例在此时比理论更高效。
1. 定义一个单例,继承 `AutoSavePluginData`
```kotlin
object MyData : AutoSavePluginData("MyData")
```
2. 使用委托添加属性。所有类型都可以使用同样的‘语法’。
```kotlin
object MyData : AutoSavePluginData("MyData") { // 文件名为 MyData, 会被保存为 MyData.yml
val value1 by value<Int>() // 推断为 Int
val value2 by value(0) // 默认值为 0 推断为 Int
var value3 by value(0) // 支持 var修改会自动保存
val value4: Int by value() // 显式类型和推断类型,你喜欢哪种?
val value5: List<String> by value() // 支持 ListSet
val value6: MutableList<String> by value() // 可按需使用 Mutable 类型
val value7: List<List<String>> by value() // 支持嵌套
val value8: Map<String, List<List<String>>> by value() // 支持 Map
var value9: List<String> by value() // List、Set 或 Map 同样支持 var。但请注意这是非引用赋值详见下文
}
```
3. 建立自动保存链接
使用 `PluginDataStorage.load(PluginDataHolder, PluginData)` 即可完成自动保存链接,并读取数据。
对于 [JVM 插件][`JvmPlugin`],可简便地在 `onEnable()` 中使用 `MyData.reload()`(对于上例)。详见 [读取 `PluginData` 或 `PluginConfig`](plugin/JVMPlugin-Appendix.md#读取-plugindata-或-pluginconfig)
### 定义数据模型Java
*由于 Java 语法局限,为 Kotlin 而设计的 PluginData 在 Java 使用很复杂。*
*即使 Mirai Console 为 Java 提供适配器,也强烈推荐 Java 用户在项目中混用 Kotlin 代码来完成数据模型定义。*
参考 [JAutoSavePluginData](../backend/mirai-console/src/data/java/JAutoSavePluginData.kt#L69)
### 非引用赋值
由于实现特殊, 赋值时不会写其引用. 即:
```kotlin
val list = ArrayList<String>("A")
MyPluginData.list = list // 赋值给 PluginData 的委托属性是非引用的
println(MyPluginData.list) // "[A]"
list.add("B")
println(list) // "[A, B]"
println(MyPluginData.list) // "[A]" // !! 由于 `list` 的引用并未赋值给 `MyPluginData.list`.
```
另一个更容易出错的示例:
```kotlin
// MyPluginData.nestedMap: MutableMap<Long, List<Long>> by value()
val newList = MyPluginData.map.getOrPut(1, ::mutableListOf)
newList.add(1) // 不会添加到 MyPluginData.nestedMap 中, 因为 `mutableListOf` 创建的 MutableList 被非引用地添加进了 MyPluginData.nestedMap
```
要解决这种无法自动初始化空集合的问题,请查看 [实验性扩展方法](#实验性扩展方法) 方法
### 使用自定义可序列化数据类型
在 Kotlin支持使用 [kotlinx.serialization](https://github.com/kotlin/kotlinx.serialization) 序列化的自定义数据类型。
**Console 使用反射构造自定义数据类型示例**。当自定义数据类型拥有公开无参构造器,或者一个构造器的所有参数都可选时,在使用委托 `by value()` 时可无需提供默认值。
否则,需要提供默认值。(见如下示例)
自定义数据类型定义:
```kotlin
@Serializable // kotlinx.serialization.Serializable
class CustomA(val str: String)
@Serializable
class CustomB(val str: String = "") // 参数可选CustomB 就可以直接被反射构造。
```
使用时:
```kotlin
object MyData : AutoSavePluginData("MyData") {
val value1 by value(CustomA("")) // CustomA 不可以通过反射直接构造实例,因为必须提供参数 str。因此要在创建 value 时提供默认值。
val value2: CustomB by value() // CustomB 可以通过反射直接构造实例
}
```
### (实验性)[扩展方法][`PluginDataExtensions`]
由于非引用赋值特性,在 `PluginData` 中定义的 `Map` 无法使用 `map.getOrPut(..., ::mutableListOf)` 等方法。
为此Console 提供一些 *映射方法*
(下文示例省略 `Value` 所在的 `PluginData` 定义)
#### (实验性)`Map.withEmptyDefault`
```kotlin
fun <K, InnerE, InnerV> SerializerAwareValue<MutableMap<K, Map<InnerE, InnerV>>>.withEmptyDefault(): SerializerAwareValue<MutableMap<K, Map<InnerE, InnerV>>>
```
创建一个代理对象, 当 `Map.get` 返回 `null` 时先放入一个 `LinkedHashMap`, 再返回这个 `LinkedHashMap`
示例:
```kotlin
val value1 by value<MutableMap<Long, List<Int>>>().withEmptyDefault()
```
使用时
```kotlin
val v: MutableMap<Long, List<Int>> = MyData.value1[123456] // 此时 Map.get 返回非 null。因为若 MyData 中不存在 123456 对应的值,就先放入一个空 List。
```
**但是,这种方法不支持多层嵌套**:例如 `Map<Long, Map<Long, List<Int>>>` 内层的 Map 不会被这样处理。
因此此方法仍处于实验性状态。如果你有任何建议,请在 issues 中发起讨论。
#### (实验性)`Map.withDefault`
```kotlin
fun <K, V> SerializerAwareValue<MutableMap<K, V>>.withDefault(defaultValueComputer: (K) -> V): SerializerAwareValue<MutableMap<K, V>>
```
与上述 `Map.withEmptyDefault` 类似。只是把默认值从 `mutableListOf` 换成了 `defaultValueComputer()`
**但是,方法命名仍有待确认**`withDefault` 可能不是最好的命名,因为可能与标准库的 `map.withDefault` 产生歧义(他们行为不同)
#### (实验性)`Map.mapKeys`
映射 `Map` 的键。
```kotlin
fun <OldK, NewK, V> SerializerAwareValue<MutableMap<OldK, V>>.mapKeys(
oldToNew: (OldK) -> NewK,
newToOld: (NewK) -> OldK,
): SerializerAwareValue<MutableMap<NewK, V>>
```
可进一步简化配置的操作。
示例:
```kotlin
val value by value<MutableMap<Long, List<Int>>>().withEmptyDefault().mapKeys(Bot::id, Bot::getInstance)
```
使用时:
```kotlin
val bot: Bot = getBot()
val list: List<Int> = value[bot]
value[bot] = listOf()
```
## [`PluginConfig`]
### [`PluginData`] 与 [`PluginConfig`] 的区别
- [`PluginData`] 表示插件内部的数据,不应该被用户看到。
- [`PluginConfig`] 表示插件的配置,用户可以修改这些配置。
### 使用 [`PluginConfig`]
[`PluginConfig`] 与 [`PluginData`] 用法完全相同。
在上述 [使用 `PluginData`](#使用-plugindata) 的示例中,
将 [`AutoSavePluginData`] 换为 [`AutoSavePluginConfig`] 即可创建一个配置,而不是数据。
在加载时使用 `configInstance.reload()``JvmPlugin.reloadPluginConfig(configInstance)`
## [`PluginDataHolder`]
***注意:这是实验性 API。***
[`PluginData`] 的拥有者。一般用于区分不同插件的不同 [`PluginData`],避免命名冲突。
[`JvmPlugin`] 实现 [`PluginDataHolder`],使用插件名作为保存时的名称。
## [`PluginDataStorage`]
***注意:这是实验性 API。***
[`PluginData`] 的存储仓库,将 [`PluginData`] 从内存序列化到文件或到数据库,或反之。
内置的实现包含:[`MultiFilePluginDataStorage`], [`MemoryPluginDataStorage`]
----
> 下一步,[Permissions](Permissions.md#mirai-console-backend---permissions)
>
> 返回 [开发文档索引](README.md#mirai-console)