Simple Robot v4.6.0 Help

2. 实现 BotManager

实现 BotManager 的基本步骤

  1. 实现 Component

  2. 实现 Bot

  3. 实现 BotManager

  4. 可选地支持 SPI

Component

因为 Bot 需要明确属于一个 Component ,因此在这里, Component 是必须的了。

我们使用在 实现组件标识 中介绍的 FooComponent ,后续将不再赘述。

    实现 BotManager

    实现完 FooBot, 接下来我们要实现创建、保存它们的 FooBotManager 了。

    定义 FooBotManager

    依照官方组件的习惯,我们会定义一个 FooBotManager 抽象类,其中默认实现对配置类的处理。

    那么首先,让我们定义一个 FooBotManager 抽象类:

    abstract class FooBotManager : BotManager, JobBasedBotManager() { // 1️⃣ abstract override val job: Job // abstract override suspend fun join() // abstract override fun onCompletion(handle: OnCompletion) // abstract override fun cancel(cause: Throwable?) // abstract override val isActive: Boolean // abstract override val isCompleted: Boolean // 2️⃣ override fun configurable(configuration: SerializableBotConfiguration): Boolean { TODO("Not yet implemented") } override fun register(configuration: SerializableBotConfiguration): FooBot { TODO("Not yet implemented") } abstract fun register(id: String, password: String): FooBot abstract override fun all(): Sequence<FooBot> abstract override fun get(id: ID): FooBot override fun find(id: ID): FooBot? = try { get(id) } catch (nb: NoSuchBotException) { null } }

    FooBot 类似, 1️⃣ 中的部分与协程、任务、任务状态与挂起相关。由于我们继承了 JobBasedBotManager, 因此后续可以直接靠实现 val job: Job 来快捷地完成对它们地实现。 2️⃣ 以下的部分则是需要我们手动实现的。

    首先可以看到,我们添加了一个额外的抽象方法:

    abstract fun register(id: String, password: String): FooBot

    它便是通过明确的参数来构建 bot 的一种 "专属方法" 。几乎每个 BotManager 都会有属于自己的 "专属方法"

    接下来看看 configurableregister ,它们需要校验并使用一个 SerializableBotConfiguration 的类型。 让我们为 FooBot 创建一个。

    FooBotConfiguration

    创建一个 FooBotConfiguration, 使其实现 SerializableBotConfiguration, 标记为可序列化,并且要求它的序列化名称与 FooComponent 的 ID 一致。

    @Serializable @SerialName(FooComponent.ID_VALUE) class FooBotConfiguration( val id: String, val password: String ) : SerializableBotConfiguration()

    由于登录bot需要用到 id 和密码,因此我们将这两个的参数作为必要的序列化属性。

    接下来,为我们的 FooBotConfiguration 配置它的多态序列化信息。这需要在 FooComponent 中配置。

    class FooComponent : Component { override val id: String get() = ID_VALUE override val serializersModule: SerializersModule get() = SerializersModule companion object Factory : ComponentFactory<FooComponent, FooComponentConfiguration> { const val ID_VALUE: String = "com.example.foo" @JvmField val SerializersModule: SerializersModule = SerializersModule { // 👇此处添加 FooBot 配置类的序列化信息 // 这样可以支持一些自动扫描的环境下(比如 Spring Boot) // 可以自动反序列化 bot 的配置文件。 serializableBotConfigurationPolymorphic { subclass(FooBotConfiguration.serializer()) } } override val key: ComponentFactory.Key = object : ComponentFactory.Key {} override fun create(context: ComponentConfigureContext, configurer: ConfigurerFunction<FooComponentConfiguration>): FooComponent { 省略... } } }

    实现 configurable 和 register

    配置类创建好了,让我们实现 configurableregister 中的校验和实现的逻辑。

    abstract class FooBotManager : BotManager { // 其他省略.. /** * 判断类型是否为 [FooBotConfiguration] */ override fun configurable(configuration: SerializableBotConfiguration): Boolean = configuration is FooBotConfiguration /** * 如果 [configuration] 类型不是 [FooBotConfiguration] 则抛出 [UnsupportedBotConfigurationException]. * 否则构建 [FooBot]。 */ override fun register(configuration: SerializableBotConfiguration): FooBot { val fooBotConfiguration = configuration as? FooBotConfiguration ?: throw UnsupportedBotConfigurationException("`configuration` !is FooBotConfiguration") return register(fooBotConfiguration.id, fooBotConfiguration.password) } // 其他省略.. }

    很简单不是吗,判断、校验配置类的类型,然后在符合条件的情况下将注册 bot 的逻辑委托给 register(id, password) 即可。

    实现 FooBotManager

    接下来,我们来实现 FooBotManager 。我们提到过 BotManager 是一个特殊的 Plugin 类型, 因此实现它的大致流程与 Plugin 是类似的。

    实现类

    先来定义一个 FooBotManager 的实现类 FooBotManagerImpl

    class FooBotManagerImpl( /** * 用于控制 [FooBotManagerImpl] 任务周期的 Job */ override val job: Job, /** * 为每个 bot 准备的 [CoroutineContext], 其中不包含 Job, * 在构建 bot 的时候再添加。 */ private val coroutineContext: CoroutineContext, /** * 构建时获取到的 component,用于创建 bot */ private val component: FooComponent ) : FooBotManager() { /** * 记录创建过的 bot 和它对应的唯一id。 * * 此处的类型应确保并发安全,例如在 JVM 中使用 `ConcurrentHashMap`。 */ private val bots = concurrentMutableMap<String, FooBot>() override fun register(id: String, password: String): FooBot { // 如果 job 已经被终止,则抛出异常 job.ensureActive() val bot = bots.computeValue(id) { key, old -> // 如果已经存在同 id 的 bot,抛出异常 if (old != null && old.isActive) throw ConflictBotException("id: $key") val supervisorJob = SupervisorJob(job) FooBotImpl(key.ID, password, component, coroutineContext + supervisorJob) }!! // 当 bot 被终止后, 尝试移除自身 bot.onCompletion { // 在 JVM 中等同于 bots.remove(id, bot) bots.removeValue(id) { bot } } return bot } /** * 得到当前所有的 bot */ override fun all(): Sequence<FooBot> = bots.values.asSequence() /** * 根据 id 寻找 bot */ override fun get(id: ID): FooBot { return bots[id.literal] ?: throw NoSuchBotException("id: $id") } }

    实现工厂

    接下来,像 Plugin 那样,为它实现一个工厂。按照习惯,我们会将它通过伴生对象实现。

    需要注意的是,工厂实现是在 FooBotManager伴生对象中,而不是 FooBotManagerImpl 哦!按照习惯, FooBotManagerImpl 应当是被隐藏的。

    abstract class FooBotManager : BotManager, JobBasedBotManager() { // 其他内容省略.. companion object Factory : BotManagerFactory<FooBotManager, FooBotManagerConfiguration> { override val key: PluginFactory.Key = object : PluginFactory.Key {} override fun create( context: PluginConfigureContext, configurer: ConfigurerFunction<FooBotManagerConfiguration> ): FooBotManager { val config = FooBotManagerConfiguration().invokeBy(configurer) // 寻找对应的 component val component = context.components.find<FooComponent>() ?: throw NoSuchComponentException("FooComponent") // 合并 application 中的 coroutineContext 和配置类中的 coroutineContext val mergedCoroutineContext = config.coroutineContext.mergeWith(context.applicationConfiguration.coroutineContext) // 拿到里面的 job val job = mergedCoroutineContext[Job]!! // 清理 job val coroutineContext = mergedCoroutineContext.minusKey(Job) return FooBotManagerImpl(job, coroutineContext, component) } } } /** * [FooBotManager] 构建时的配置类。 * 可以提供一些配置,例如 coroutineContext */ class FooBotManagerConfiguration { var coroutineContext: CoroutineContext = EmptyCoroutineContext }

    支持 SPI

    BotManager 继承 Plugin, 因此支持 SPI 的方式与 Plugin 一致。 你可以前往参考之前的 实现插件 章节中的 支持 SPI 部分,只不过把其中描述的 FooPlugin 换成 FooBotManager 就好了。

    Last modified: 19 September 2024