MiraiForum

    • 注册
    • 登录
    • 搜索
    • 热门
    • 最新
    • 未解决
    • 标签
    • 群组
    • 友情链接

    使用 Spring Websocket 托管 Overflow 默认的WebSocket实现

    摸鱼区
    1
    2
    510
    正在加载更多帖子
    • 从旧到新
    • 从新到旧
    • 最多赞同
    回复
    • 在新帖中回复
    登录后回复
    此主题已被删除。只有拥有主题管理权限的用户可以查看。
    • K
      kagg886 最后由 kagg886 编辑

      适用于只使用overflow-core开发bot,并且使用spring自带的IoC能力的一套ws解决方案。可以避免早期overflow版本中反向websocket存在的各种问题。

      1. 定义handler
      class WSHandler : WebSocketHandler, IAdapter {
          override val scope = CoroutineScope(Dispatchers.IO) + SupervisorJob()
      
          override val logger: Logger = LoggerFactory.getLogger(ActionHandler::class.java)
          override val actionHandler: ActionHandler = ActionHandler(scope.coroutineContext[Job], logger)
      
          init {
              top.mrxiaom.overflow.internal.Overflow.setup()
              log.info("Youmu WebSocket 已接管默认Overflow Bot Handler,WSBufferSize: ${System.getProperty("org.apache.tomcat.websocket.DEFAULT_BUFFER_SIZE")}")
          }
      
          override fun afterConnectionEstablished(session: WebSocketSession) {
              //在这里进行鉴权
              scope.launch {
                  val botImpl = Bot(
                      SpringDelegatedWebSocket(session),
                      BotConfig(),
                      actionHandler
                  )
      
                  val versionInfo = botImpl.getVersionInfo()
                  if (botImpl.onebotVersion == 12) {
                      session.close(CloseStatus.PROTOCOL_ERROR.withReason("不支持的onebot版本:12"))
                      return@launch
                  }
      
                  net.mamoe.mirai.Bot.getInstanceOrNull(botImpl.id)?.getOriginChannel()
                      ?.close(CloseStatus.NORMAL.withReason("当前连接下线"))
      
                  val bot = with(BotWrapper) {
                      val result = runCatching {
                          botImpl.wrap(
                              configuration = BotConfiguration {
                                  botLoggerSupplier = {
                                      LoggerFactory.getLogger("Bot#${botImpl.id}").asMiraiLogger()
                                  }
                                  networkLoggerSupplier = {
                                      LoggerFactory.getLogger("Net#${botImpl.id}").asMiraiLogger()
                                  }
                              }
                          )
                      }
                      if (result.isFailure) {
                          session.close(CloseStatus.PROTOCOL_ERROR.withReason("无法实例化bot"))
                          return@launch
                      }
                      result.getOrThrow()
                  }
                  log.info("Bot ${bot.id} 已连接,协议版本信息:$versionInfo")
                  net.mamoe.mirai.Bot._instances[botImpl.id] = bot
              }
          }
      
          override fun handleMessage(session: WebSocketSession, message: WebSocketMessage<*>) {
              if (message is TextMessage) {
                  val text = message.payload
                  onReceiveMessage(text)
              }
          }
      
          override fun handleTransportError(session: WebSocketSession, exception: Throwable) {
          }
      
          @OptIn(MiraiInternalApi::class)
          override fun afterConnectionClosed(session: WebSocketSession, closeStatus: CloseStatus) {
              val bot = net.mamoe.mirai.Bot.instances.find {
                  it.getOriginChannel() == session
              }
              if (bot !== null) {
                  bot as BotWrapper
                  bot.eventDispatcher.broadcastAsync(BotOfflineEvent.Dropped(bot, cause = RuntimeException("连接断开")))
                  net.mamoe.mirai.Bot._instances.remove(bot.id)
                  log.info("${bot.id}断开连接")
              }
          }
      
          override fun supportsPartialMessages(): Boolean = false
      }
      
      1. 定义ws session的wrapper。overflow中未使用的方法均可以使用TODO替代。
      class SpringDelegatedWebSocket(val delegated: WebSocketSession) : WebSocket {
          override fun close(p0: Int, p1: String?) {
              delegated.close(CloseStatus(p0, p1))
          }
      
          override fun close(p0: Int) {
              delegated.close(CloseStatus(p0))
          }
      
          override fun close() {
              delegated.close()
          }
      
          override fun closeConnection(p0: Int, p1: String?) {
              delegated.close(CloseStatus(p0, p1))
          }
      
          override fun send(p0: String?) {
              delegated.sendMessage(TextMessage(p0!!))
          }
      
          override fun send(p0: ByteBuffer?) {
              delegated.sendMessage(BinaryMessage(p0!!))
          }
      
          override fun send(p0: ByteArray?) {
              delegated.sendMessage(BinaryMessage(p0!!))
          }
      
          override fun sendFrame(p0: Framedata?) {
              TODO("Not yet implemented")
          }
      
          override fun sendFrame(p0: MutableCollection<Framedata>?) {
              TODO("Not yet implemented")
          }
      
          override fun sendPing() {
              delegated.sendMessage(PingMessage())
          }
      
          override fun sendFragmentedFrame(p0: Opcode?, p1: ByteBuffer?, p2: Boolean) {
              TODO("Not yet implemented")
          }
      
          override fun hasBufferedData(): Boolean {
              TODO("Not yet implemented")
          }
      
          override fun getRemoteSocketAddress(): InetSocketAddress = delegated.remoteAddress!!
      
          override fun getLocalSocketAddress(): InetSocketAddress = delegated.localAddress!!
      
          override fun isOpen(): Boolean = delegated.isOpen
      
          override fun isClosing(): Boolean = !isOpen
      
          override fun isFlushAndClose(): Boolean {
              TODO("Not yet implemented")
          }
      
          override fun isClosed(): Boolean = !isOpen
      
          override fun getDraft(): Draft {
              TODO("Not yet implemented")
          }
      
          override fun getReadyState(): ReadyState {
              TODO("Not yet implemented")
          }
      
          override fun getResourceDescriptor(): String {
              TODO("Not yet implemented")
          }
      
          override fun <T : Any?> setAttachment(p0: T) {
              TODO("Not yet implemented")
          }
      
          override fun <T : Any?> getAttachment(): T {
              TODO("Not yet implemented")
          }
      
          override fun hasSSLSupport(): Boolean {
              TODO("Not yet implemented")
          }
      
          override fun getSSLSession(): SSLSession {
              TODO("Not yet implemented")
          }
      
          override fun getProtocol(): IProtocol {
              TODO("Not yet implemented")
          }
      }
      
      1. 最后自行注册即可
      @Configuration
      @EnableWebSocket
      class WSConfig:WebSocketConfigurer {
          override fun registerWebSocketHandlers(registry: WebSocketHandlerRegistry) {
              registry.addHandler(
                  WSHandler(),
                  "bot"
              )
          }
      }
      
      1. 补一个上文中出现的工具类
      @file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
      package top.kagg886.youmu.backend.socket
      
      import net.mamoe.mirai.Bot
      import org.springframework.web.socket.WebSocketSession
      import top.kagg886.youmu.bot.internal.spring.SpringDelegatedWebSocket
      import top.mrxiaom.overflow.internal.contact.BotWrapper
      import kotlin.reflect.KProperty1
      import kotlin.reflect.full.memberProperties
      import kotlin.reflect.jvm.isAccessible
      
      fun Bot.getOriginChannel(): WebSocketSession {
          val prop = BotWrapper::class.memberProperties.first { it.name == "implBot" } as KProperty1<BotWrapper, cn.evolvefield.onebot.client.core.Bot>
          prop.isAccessible = true
          val wrapper = prop.get(this as BotWrapper).channel
          return (wrapper as SpringDelegatedWebSocket).delegated
      }
      

      最后附赠一套使用此方案部署在公网上的反向websocket bot:

      wss://youmu.kagg886.top/bot

      接入教程见此:部署bot

      1 条回复 最后回复 回复 引用 0
      • K
        kagg886 最后由 编辑

        补充:

        1. 由于网络延迟原因,可能需要增大overflow对onebot api响应的等待时间,在启动类的main方法添加属性以增大等待时间:
        System.getProperties().setProperty("overflow.timeout",1.minutes.inWholeMilliseconds.toString())
        
        1. 由于Spring Websocket分包问题,需要增大Tomcat默认处理ws的缓冲区,推荐将发送图片大小压缩到2M以内,同时在启动类添加属性:
        System.getProperties().setProperty("org.apache.tomcat.websocket.DEFAULT_BUFFER_SIZE","5242880")
        
        1 条回复 最后回复 回复 引用 0
        • 1 / 1
        • First post
          Last post
        Powered by Mamoe Technologies & NodeBB | 友情链接 | 服务监控 | Contact