فهرست منبع

feat: 重构模块

ageerle 1 ماه پیش
والد
کامیت
ac4c037634
100فایلهای تغییر یافته به همراه9944 افزوده شده و 0 حذف شده
  1. 3 0
      ruoyi-common/pom.xml
  2. 49 0
      ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-base/pom.xml
  3. 32 0
      ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-base/src/main/java/tech/ordinaryroad/live/chat/client/commons/base/constant/Constants.java
  4. 51 0
      ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-base/src/main/java/tech/ordinaryroad/live/chat/client/commons/base/exception/BaseException.java
  5. 60 0
      ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-base/src/main/java/tech/ordinaryroad/live/chat/client/commons/base/listener/IBaseConnectionListener.java
  6. 149 0
      ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-base/src/main/java/tech/ordinaryroad/live/chat/client/commons/base/listener/IBaseMsgListener.java
  7. 46 0
      ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-base/src/main/java/tech/ordinaryroad/live/chat/client/commons/base/listener/IDanmuMsgListener.java
  8. 47 0
      ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-base/src/main/java/tech/ordinaryroad/live/chat/client/commons/base/listener/IEnterRoomMsgListener.java
  9. 47 0
      ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-base/src/main/java/tech/ordinaryroad/live/chat/client/commons/base/listener/IGiftMsgListener.java
  10. 46 0
      ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-base/src/main/java/tech/ordinaryroad/live/chat/client/commons/base/listener/ILikeMsgListener.java
  11. 47 0
      ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-base/src/main/java/tech/ordinaryroad/live/chat/client/commons/base/listener/ISuperChatMsgListener.java
  12. 33 0
      ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-base/src/main/java/tech/ordinaryroad/live/chat/client/commons/base/msg/BaseCmdMsg.java
  13. 74 0
      ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-base/src/main/java/tech/ordinaryroad/live/chat/client/commons/base/msg/BaseMsg.java
  14. 38 0
      ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-base/src/main/java/tech/ordinaryroad/live/chat/client/commons/base/msg/ICmdMsg.java
  15. 67 0
      ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-base/src/main/java/tech/ordinaryroad/live/chat/client/commons/base/msg/IDanmuMsg.java
  16. 62 0
      ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-base/src/main/java/tech/ordinaryroad/live/chat/client/commons/base/msg/IEnterRoomMsg.java
  17. 100 0
      ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-base/src/main/java/tech/ordinaryroad/live/chat/client/commons/base/msg/IGiftMsg.java
  18. 71 0
      ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-base/src/main/java/tech/ordinaryroad/live/chat/client/commons/base/msg/ILikeMsg.java
  19. 34 0
      ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-base/src/main/java/tech/ordinaryroad/live/chat/client/commons/base/msg/IMsg.java
  20. 49 0
      ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-base/src/main/java/tech/ordinaryroad/live/chat/client/commons/base/msg/ISuperChatMsg.java
  21. 55 0
      ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-client/pom.xml
  22. 200 0
      ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-client/src/main/java/tech/ordinaryroad/live/chat/client/commons/client/BaseLiveChatClient.java
  23. 135 0
      ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-client/src/main/java/tech/ordinaryroad/live/chat/client/commons/client/IBaseLiveChatClient.java
  24. 139 0
      ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-client/src/main/java/tech/ordinaryroad/live/chat/client/commons/client/config/BaseLiveChatClientConfig.java
  25. 83 0
      ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-client/src/main/java/tech/ordinaryroad/live/chat/client/commons/client/enums/ClientStatusEnums.java
  26. 25 0
      ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-util/pom.xml
  27. 80 0
      ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-util/src/main/java/tech/ordinaryroad/live/chat/client/commons/util/OrLiveChatCookieUtil.java
  28. 40 0
      ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-util/src/main/java/tech/ordinaryroad/live/chat/client/commons/util/OrLiveChatNumberUtil.java
  29. 50 0
      ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-util/src/main/java/tech/ordinaryroad/live/chat/client/commons/util/OrLiveChatReflectUtil.java
  30. 54 0
      ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-util/src/main/java/tech/ordinaryroad/live/chat/client/commons/util/OrLocalDateTimeUtil.java
  31. 44 0
      ruoyi-common/ruoyi-common-live/live-chat-client-commons/pom.xml
  32. 55 0
      ruoyi-common/ruoyi-common-live/live-chat-client-servers/live-chat-client-servers-netty-client/pom.xml
  33. 349 0
      ruoyi-common/ruoyi-common-live/live-chat-client-servers/live-chat-client-servers-netty-client/src/main/java/tech/ordinaryroad/live/chat/client/servers/netty/client/base/BaseNettyClient.java
  34. 65 0
      ruoyi-common/ruoyi-common-live/live-chat-client-servers/live-chat-client-servers-netty-client/src/main/java/tech/ordinaryroad/live/chat/client/servers/netty/client/config/BaseNettyClientConfig.java
  35. 66 0
      ruoyi-common/ruoyi-common-live/live-chat-client-servers/live-chat-client-servers-netty-client/src/main/java/tech/ordinaryroad/live/chat/client/servers/netty/client/handler/BaseNettyClientBinaryFrameHandler.java
  36. 65 0
      ruoyi-common/ruoyi-common-live/live-chat-client-servers/live-chat-client-servers-netty-client/src/main/java/tech/ordinaryroad/live/chat/client/servers/netty/client/handler/BaseNettyClientConnectionHandler.java
  37. 59 0
      ruoyi-common/ruoyi-common-live/live-chat-client-servers/live-chat-client-servers-netty/pom.xml
  38. 39 0
      ruoyi-common/ruoyi-common-live/live-chat-client-servers/live-chat-client-servers-netty/src/main/java/tech/ordinaryroad/live/chat/client/servers/netty/frame/base/BaseBinaryWebSocketFrame.java
  39. 184 0
      ruoyi-common/ruoyi-common-live/live-chat-client-servers/live-chat-client-servers-netty/src/main/java/tech/ordinaryroad/live/chat/client/servers/netty/handler/base/BaseBinaryFrameHandler.java
  40. 168 0
      ruoyi-common/ruoyi-common-live/live-chat-client-servers/live-chat-client-servers-netty/src/main/java/tech/ordinaryroad/live/chat/client/servers/netty/handler/base/BaseConnectionHandler.java
  41. 47 0
      ruoyi-common/ruoyi-common-live/live-chat-client-servers/pom.xml
  42. 71 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/pom.xml
  43. 247 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/api/BilibiliApis.java
  44. 74 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/api/request/BilibiliLikeReportV3Request.java
  45. 77 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/api/request/BilibiliSendMsgRequest.java
  46. 184 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/client/BilibiliLiveChatClient.java
  47. 67 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/config/BilibiliLiveChatClientConfig.java
  48. 143 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/constant/BilibiliCmdEnum.java
  49. 86 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/constant/OperationEnum.java
  50. 67 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/constant/ProtoverEnum.java
  51. 38 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/listener/IBilibiliConnectionListener.java
  52. 138 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/listener/IBilibiliMsgListener.java
  53. 92 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/msg/AuthMsg.java
  54. 63 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/msg/AuthReplyMsg.java
  55. 102 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/msg/DanmuMsgMsg.java
  56. 58 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/msg/HeartbeatMsg.java
  57. 60 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/msg/HeartbeatReplyMsg.java
  58. 396 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/msg/InteractWordMsg.java
  59. 158 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/msg/LikeInfoV3ClickMsg.java
  60. 351 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/msg/SendGiftMsg.java
  61. 81 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/msg/SendSmsReplyMsg.java
  62. 202 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/msg/SuperChatMessageMsg.java
  63. 68 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/msg/base/BaseBilibiliCmdMsg.java
  64. 50 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/msg/base/BaseBilibiliMsg.java
  65. 44 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/msg/base/IBilibiliMsg.java
  66. 66 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/msg/dto/MedalInfo.java
  67. 40 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/netty/frame/AuthWebSocketFrame.java
  68. 40 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/netty/frame/HeartbeatWebSocketFrame.java
  69. 54 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/netty/frame/base/BaseBilibiliWebSocketFrame.java
  70. 113 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/netty/frame/factory/BilibiliWebSocketFrameFactory.java
  71. 153 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/netty/handler/BilibiliBinaryFrameHandler.java
  72. 154 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/netty/handler/BilibiliConnectionHandler.java
  73. 78 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/protobuf/Dm_v2Proto.java
  74. 74 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/protobuf/Dm_v2_20Proto.java
  75. 610 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/protobuf/dm_v2.java
  76. 48 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/protobuf/dm_v2OrBuilder.java
  77. 565 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/protobuf/dm_v2_20.java
  78. 45 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/protobuf/dm_v2_20OrBuilder.java
  79. 259 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/util/BilibiliCodecUtil.java
  80. 14 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/resources/proto/dm_v2.proto
  81. 12 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/resources/proto/dm_v2_20.proto
  82. 16 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/test/java/tech/ordinaryroad/live/chat/client/bilibili/api/BilibiliApisTest.java
  83. 176 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/test/java/tech/ordinaryroad/live/chat/client/bilibili/client/BilibiliLiveChatClientTest.java
  84. 42 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-douyin/pom.xml
  85. 81 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-douyin/src/main/java/tech/ordinaryroad/live/chat/client/douyin/ClientModeExample.java
  86. 108 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-douyin/src/main/java/tech/ordinaryroad/live/chat/client/douyin/api/DouyinApis.java
  87. 174 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-douyin/src/main/java/tech/ordinaryroad/live/chat/client/douyin/client/DouyinLiveChatClient.java
  88. 93 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-douyin/src/main/java/tech/ordinaryroad/live/chat/client/douyin/config/DouyinLiveChatClientConfig.java
  89. 73 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-douyin/src/main/java/tech/ordinaryroad/live/chat/client/douyin/constant/DouyinCmdEnum.java
  90. 35 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-douyin/src/main/java/tech/ordinaryroad/live/chat/client/douyin/listener/IDouyinConnectionListener.java
  91. 44 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-douyin/src/main/java/tech/ordinaryroad/live/chat/client/douyin/listener/IDouyinMsgListener.java
  92. 77 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-douyin/src/main/java/tech/ordinaryroad/live/chat/client/douyin/msg/DouyinDanmuMsg.java
  93. 72 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-douyin/src/main/java/tech/ordinaryroad/live/chat/client/douyin/msg/DouyinEnterRoomMsg.java
  94. 107 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-douyin/src/main/java/tech/ordinaryroad/live/chat/client/douyin/msg/DouyinGiftMsg.java
  95. 77 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-douyin/src/main/java/tech/ordinaryroad/live/chat/client/douyin/msg/DouyinLikeMsg.java
  96. 35 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-douyin/src/main/java/tech/ordinaryroad/live/chat/client/douyin/msg/base/IDouyinCmdMsg.java
  97. 34 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-douyin/src/main/java/tech/ordinaryroad/live/chat/client/douyin/msg/base/IDouyinMsg.java
  98. 156 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-douyin/src/main/java/tech/ordinaryroad/live/chat/client/douyin/netty/handler/DouyinBinaryFrameHandler.java
  99. 117 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-douyin/src/main/java/tech/ordinaryroad/live/chat/client/douyin/netty/handler/DouyinConnectionHandler.java
  100. 78 0
      ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-douyin/src/main/java/tech/ordinaryroad/live/chat/client/douyin/protobuf/Douyin_cmd_msgProto.java

+ 3 - 0
ruoyi-common/pom.xml

@@ -39,6 +39,9 @@
         <module>ruoyi-common-encrypt</module>
         <module>ruoyi-common-tenant</module>
         <module>ruoyi-common-chat</module>
+        <module>ruoyi-common-wechat</module>
+        <module>ruoyi-common-pay</module>
+        <module>ruoyi-common-live</module>
     </modules>
 
 </project>

+ 49 - 0
ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-base/pom.xml

@@ -0,0 +1,49 @@
+<!--
+  ~ MIT License
+  ~
+  ~ Copyright (c) 2023 OrdinaryRoad
+  ~
+  ~ Permission is hereby granted, free of charge, to any person obtaining a copy
+  ~ of this software and associated documentation files (the "Software"), to deal
+  ~ in the Software without restriction, including without limitation the rights
+  ~ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+  ~ copies of the Software, and to permit persons to whom the Software is
+  ~ furnished to do so, subject to the following conditions:
+  ~
+  ~ The above copyright notice and this permission notice shall be included in all
+  ~ copies or substantial portions of the Software.
+  ~
+  ~ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+  ~ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+  ~ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+  ~ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+  ~ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+  ~ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+  ~ SOFTWARE.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.ruoyi</groupId>
+        <artifactId>live-chat-client-commons</artifactId>
+        <version>${revision}</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>live-chat-client-commons-base</artifactId>
+    <name>ordinaryroad-live-chat-client-commons-base</name>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 32 - 0
ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-base/src/main/java/tech/ordinaryroad/live/chat/client/commons/base/constant/Constants.java

@@ -0,0 +1,32 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.commons.base.constant;
+
+/**
+ * @author mjz
+ * @date 2023/8/26
+ */
+public class Constants {
+}

+ 51 - 0
ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-base/src/main/java/tech/ordinaryroad/live/chat/client/commons/base/exception/BaseException.java

@@ -0,0 +1,51 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.commons.base.exception;
+
+/**
+ * @author mjz
+ * @date 2023/9/5
+ */
+public class BaseException extends RuntimeException {
+
+    public BaseException() {
+    }
+
+    public BaseException(String message) {
+        super(message);
+    }
+
+    public BaseException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public BaseException(Throwable cause) {
+        super(cause);
+    }
+
+    public BaseException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+        super(message, cause, enableSuppression, writableStackTrace);
+    }
+}

+ 60 - 0
ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-base/src/main/java/tech/ordinaryroad/live/chat/client/commons/base/listener/IBaseConnectionListener.java

@@ -0,0 +1,60 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.commons.base.listener;
+
+
+/**
+ * 连接回调
+ *
+ * @author mjz
+ * @date 2023/8/26
+ */
+public interface IBaseConnectionListener<T> {
+
+    /**
+     * 连接建立成功
+     */
+    default void onConnected(T t) {
+        // ignore
+    }
+
+    /**
+     * 连接建立失败
+     *
+     * @param t
+     */
+    default void onConnectFailed(T t) {
+        // ignore
+    }
+
+    /**
+     * 连接断开
+     *
+     * @param t
+     */
+    default void onDisconnected(T t) {
+        // ignore
+    }
+}

+ 149 - 0
ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-base/src/main/java/tech/ordinaryroad/live/chat/client/commons/base/listener/IBaseMsgListener.java

@@ -0,0 +1,149 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.commons.base.listener;
+
+
+import tech.ordinaryroad.live.chat.client.commons.base.msg.BaseCmdMsg;
+import tech.ordinaryroad.live.chat.client.commons.base.msg.BaseMsg;
+import tech.ordinaryroad.live.chat.client.commons.base.msg.ICmdMsg;
+import tech.ordinaryroad.live.chat.client.commons.base.msg.IMsg;
+
+/**
+ * Base消息回调
+ *
+ * @author mjz
+ * @date 2023/8/26
+ */
+public interface IBaseMsgListener<T, CmdEnum extends Enum<CmdEnum>> {
+
+    /**
+     * 收到消息(所有消息)
+     *
+     * @param msg IMsg
+     */
+    default void onMsg(T t, IMsg msg) {
+        this.onMsg(msg);
+    }
+
+    default void onMsg(IMsg msg) {
+        // ignore
+    }
+
+    /**
+     * 收到cmd消息(所有cmd)
+     *
+     * @param cmd    CmdEnum
+     * @param cmdMsg BaseCmdMsg
+     */
+    default void onCmdMsg(T t, CmdEnum cmd, ICmdMsg<CmdEnum> cmdMsg) {
+        this.onCmdMsg(cmd, cmdMsg);
+    }
+
+    default void onCmdMsg(CmdEnum cmd, ICmdMsg<CmdEnum> cmdMsg) {
+        // ignore
+    }
+
+    /**
+     * 收到其他cmd消息(存在Enum,但Listener没有对应的回调)
+     *
+     * @param cmd    CmdEnum
+     * @param cmdMsg BaseCmdMsg
+     */
+    default void onOtherCmdMsg(T t, CmdEnum cmd, ICmdMsg<CmdEnum> cmdMsg) {
+        this.onOtherCmdMsg(cmd, cmdMsg);
+    }
+
+    default void onOtherCmdMsg(CmdEnum cmd, ICmdMsg<CmdEnum> cmdMsg) {
+        // ignore
+    }
+
+    /**
+     * 收到未知cmd消息
+     *
+     * @param cmdString 实际收到的cmd字符串
+     * @param msg       BaseMsg
+     */
+    default void onUnknownCmd(T t, String cmdString, IMsg msg) {
+        this.onUnknownCmd(cmdString, msg);
+    }
+
+    default void onUnknownCmd(String cmdString, IMsg msg) {
+        // ignore
+    }
+
+    /**
+     * 收到cmd消息(所有cmd)
+     *
+     * @param cmd    CmdEnum
+     * @param cmdMsg BaseCmdMsg
+     * @deprecated use {@link #onCmdMsg(T, Enum, ICmdMsg)}
+     */
+    default void onCmdMsg(T t, CmdEnum cmd, BaseCmdMsg<CmdEnum> cmdMsg) {
+        this.onCmdMsg(cmd, cmdMsg);
+    }
+
+    /**
+     * @deprecated use {@link #onCmdMsg(Enum, ICmdMsg)}
+     */
+    default void onCmdMsg(CmdEnum cmd, BaseCmdMsg<CmdEnum> cmdMsg) {
+        // ignore
+    }
+
+    /**
+     * 收到其他cmd消息(存在Enum,但Listener没有对应的回调)
+     *
+     * @param cmd    CmdEnum
+     * @param cmdMsg BaseCmdMsg
+     * @deprecated use {@link #onOtherCmdMsg(T, Enum, ICmdMsg)}
+     */
+    default void onOtherCmdMsg(T t, CmdEnum cmd, BaseCmdMsg<CmdEnum> cmdMsg) {
+        this.onOtherCmdMsg(cmd, cmdMsg);
+    }
+
+    /**
+     * @deprecated use {@link #onOtherCmdMsg(Enum, ICmdMsg)}
+     */
+    default void onOtherCmdMsg(CmdEnum cmd, BaseCmdMsg<CmdEnum> cmdMsg) {
+        // ignore
+    }
+
+    /**
+     * 收到未知cmd消息
+     *
+     * @param cmdString 实际收到的cmd字符串
+     * @param msg       BaseMsg
+     * @deprecated use {@link #onUnknownCmd(T, String, IMsg)}
+     */
+    default void onUnknownCmd(T t, String cmdString, BaseMsg msg) {
+        this.onUnknownCmd(cmdString, msg);
+    }
+
+    /**
+     * @deprecated use {@link #onUnknownCmd(String, IMsg)}
+     */
+    default void onUnknownCmd(String cmdString, BaseMsg msg) {
+        // ignore
+    }
+}

+ 46 - 0
ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-base/src/main/java/tech/ordinaryroad/live/chat/client/commons/base/listener/IDanmuMsgListener.java

@@ -0,0 +1,46 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.commons.base.listener;
+
+
+/**
+ * 弹幕消息回调
+ *
+ * @author mjz
+ * @since 0.0.6
+ */
+public interface IDanmuMsgListener<T, DanmuMsg> {
+
+    /**
+     * 收到弹幕
+     */
+    default void onDanmuMsg(T t, DanmuMsg msg) {
+        this.onDanmuMsg(msg);
+    }
+
+    default void onDanmuMsg(DanmuMsg msg) {
+        // ignore
+    }
+}

+ 47 - 0
ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-base/src/main/java/tech/ordinaryroad/live/chat/client/commons/base/listener/IEnterRoomMsgListener.java

@@ -0,0 +1,47 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.commons.base.listener;
+
+
+/**
+ * 进入房间消息回调
+ *
+ * @author mjz
+ * @date 2023/12/14
+ * @since 0.0.16
+ */
+public interface IEnterRoomMsgListener<T, EnterRoomMsg> {
+
+    /**
+     * 用户进入房间
+     */
+    default void onEnterRoomMsg(T t, EnterRoomMsg msg) {
+        this.onEnterRoomMsg(msg);
+    }
+
+    default void onEnterRoomMsg(EnterRoomMsg msg) {
+        // ignore
+    }
+}

+ 47 - 0
ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-base/src/main/java/tech/ordinaryroad/live/chat/client/commons/base/listener/IGiftMsgListener.java

@@ -0,0 +1,47 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.commons.base.listener;
+
+
+/**
+ * 礼物消息回调
+ *
+ * @author mjz
+ * @since 0.0.8
+ */
+public interface IGiftMsgListener<T, GiftMsg> {
+
+
+    /**
+     * 收到礼物
+     */
+    default void onGiftMsg(T t, GiftMsg msg) {
+        this.onGiftMsg(msg);
+    }
+
+    default void onGiftMsg(GiftMsg msg) {
+        // ignore
+    }
+}

+ 46 - 0
ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-base/src/main/java/tech/ordinaryroad/live/chat/client/commons/base/listener/ILikeMsgListener.java

@@ -0,0 +1,46 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.commons.base.listener;
+
+
+/**
+ * 点赞消息回调
+ *
+ * @author mjz
+ * @since 0.2.0
+ */
+public interface ILikeMsgListener<T, LikeMsg> {
+
+    /**
+     * 收到点赞
+     */
+    default void onLikeMsg(T t, LikeMsg msg) {
+        this.onLikeMsg(msg);
+    }
+
+    default void onLikeMsg(LikeMsg msg) {
+        // ignore
+    }
+}

+ 47 - 0
ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-base/src/main/java/tech/ordinaryroad/live/chat/client/commons/base/listener/ISuperChatMsgListener.java

@@ -0,0 +1,47 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.commons.base.listener;
+
+
+/**
+ * 醒目留言消息回调
+ *
+ * @author mjz
+ * @date 2023/9/24
+ * @since 0.0.11
+ */
+public interface ISuperChatMsgListener<T, SuperChatMsg> {
+
+    /**
+     * 收到醒目留言
+     */
+    default void onSuperChatMsg(T t, SuperChatMsg msg) {
+        this.onSuperChatMsg(msg);
+    }
+
+    default void onSuperChatMsg(SuperChatMsg msg) {
+        // ignore
+    }
+}

+ 33 - 0
ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-base/src/main/java/tech/ordinaryroad/live/chat/client/commons/base/msg/BaseCmdMsg.java

@@ -0,0 +1,33 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.commons.base.msg;
+
+/**
+ * @author mjz
+ * @date 2023/8/26
+ */
+public abstract class BaseCmdMsg<CmdEnum extends Enum<CmdEnum>> extends BaseMsg
+        implements ICmdMsg<CmdEnum> {
+}

+ 74 - 0
ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-base/src/main/java/tech/ordinaryroad/live/chat/client/commons/base/msg/BaseMsg.java

@@ -0,0 +1,74 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.commons.base.msg;
+
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import tech.ordinaryroad.live.chat.client.commons.base.exception.BaseException;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author mjz
+ * @date 2023/8/26
+ */
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public abstract class BaseMsg implements IMsg {
+
+    public static final ObjectMapper OBJECT_MAPPER = new ObjectMapper()
+            .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
+            .configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
+
+    /**
+     * 未知属性都放在这
+     */
+    private final Map<String, JsonNode> unknownProperties = new HashMap<>();
+
+    @JsonAnyGetter
+    public Map<String, JsonNode> getUnknownProperties() {
+        return unknownProperties;
+    }
+
+    @JsonAnySetter
+    public void setOther(String key, JsonNode value) {
+        this.unknownProperties.put(key, value);
+    }
+
+    @Override
+    public String toString() {
+        try {
+            return OBJECT_MAPPER.writeValueAsString(this);
+        } catch (JsonProcessingException e) {
+            throw new BaseException(e);
+        }
+    }
+}

+ 38 - 0
ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-base/src/main/java/tech/ordinaryroad/live/chat/client/commons/base/msg/ICmdMsg.java

@@ -0,0 +1,38 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.commons.base.msg;
+
+/**
+ * @author mjz
+ * @date 2023/10/2
+ */
+public interface ICmdMsg<CmdEnum extends Enum<CmdEnum>> extends IMsg {
+
+    String getCmd();
+
+    void setCmd(String cmd);
+
+    CmdEnum getCmdEnum();
+}

+ 67 - 0
ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-base/src/main/java/tech/ordinaryroad/live/chat/client/commons/base/msg/IDanmuMsg.java

@@ -0,0 +1,67 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.commons.base.msg;
+
+/**
+ * @author mjz
+ * @date 2023/9/8
+ */
+public interface IDanmuMsg extends IMsg {
+
+    /**
+     * 粉丝牌名称
+     */
+    String getBadgeName();
+
+    /**
+     * 粉丝牌等级
+     */
+    byte getBadgeLevel();
+
+    /**
+     * 弹幕发送者id
+     */
+    String getUid();
+
+    /**
+     * 弹幕发送者用户名
+     */
+    String getUsername();
+
+    /**
+     * 弹幕发送者头像地址
+     *
+     * @since 0.0.11
+     */
+    default String getUserAvatar() {
+        return null;
+    }
+
+    /**
+     * 弹幕内容
+     */
+    String getContent();
+
+}

+ 62 - 0
ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-base/src/main/java/tech/ordinaryroad/live/chat/client/commons/base/msg/IEnterRoomMsg.java

@@ -0,0 +1,62 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.commons.base.msg;
+
+/**
+ * 入房消息
+ *
+ * @author mjz
+ * @date 2023/12/26
+ * @since 0.0.16
+ */
+public interface IEnterRoomMsg extends IMsg {
+
+    /**
+     * 粉丝牌名称
+     */
+    String getBadgeName();
+
+    /**
+     * 粉丝牌等级
+     */
+    byte getBadgeLevel();
+
+    /**
+     * 用户id
+     */
+    String getUid();
+
+    /**
+     * 用户名
+     */
+    String getUsername();
+
+    /**
+     * 头像地址
+     */
+    default String getUserAvatar() {
+        return null;
+    }
+}

+ 100 - 0
ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-base/src/main/java/tech/ordinaryroad/live/chat/client/commons/base/msg/IGiftMsg.java

@@ -0,0 +1,100 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.commons.base.msg;
+
+/**
+ * @author mjz
+ * @date 2023/9/8
+ */
+public interface IGiftMsg extends IMsg {
+
+    /**
+     * 粉丝牌名称
+     */
+    default String getBadgeName() {
+        return "";
+    }
+
+    /**
+     * 粉丝牌等级
+     */
+    default byte getBadgeLevel() {
+        return 0;
+    }
+
+    /**
+     * 发送方id
+     */
+    String getUid();
+
+    /**
+     * 发送方用户名
+     */
+    String getUsername();
+
+    /**
+     * 发送方头像地址
+     *
+     * @since 0.0.11
+     */
+    default String getUserAvatar() {
+        return null;
+    }
+
+    /**
+     * 礼物名称
+     */
+    String getGiftName();
+
+    /**
+     * 礼物图像地址
+     */
+    String getGiftImg();
+
+    /**
+     * 礼物id
+     */
+    String getGiftId();
+
+    /**
+     * 礼物数量
+     */
+    int getGiftCount();
+
+    /**
+     * 单个礼物价格
+     */
+    int getGiftPrice();
+
+    /**
+     * 接收方id
+     */
+    String getReceiveUid();
+
+    /**
+     * 接收方用户名
+     */
+    String getReceiveUsername();
+}

+ 71 - 0
ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-base/src/main/java/tech/ordinaryroad/live/chat/client/commons/base/msg/ILikeMsg.java

@@ -0,0 +1,71 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.commons.base.msg;
+
+/**
+ * @author mjz
+ * @date 2024/1/31
+ * @since 0.2.0
+ */
+public interface ILikeMsg extends IMsg {
+
+    /**
+     * 粉丝牌名称
+     */
+    default String getBadgeName(){
+        return "";
+    }
+
+    /**
+     * 粉丝牌等级
+     */
+    default byte getBadgeLevel(){
+        return 0;
+    }
+
+    /**
+     * 点赞者id
+     */
+    String getUid();
+
+    /**
+     * 点赞者用户名
+     */
+    String getUsername();
+
+    /**
+     * 点赞者头像地址
+     */
+    default String getUserAvatar() {
+        return null;
+    }
+
+    /**
+     * 点赞数
+     */
+    default int getClickCount() {
+        return 1;
+    }
+}

+ 34 - 0
ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-base/src/main/java/tech/ordinaryroad/live/chat/client/commons/base/msg/IMsg.java

@@ -0,0 +1,34 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.commons.base.msg;
+
+import java.io.Serializable;
+
+/**
+ * @author mjz
+ * @date 2023/8/26
+ */
+public interface IMsg extends Serializable {
+}

+ 49 - 0
ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-base/src/main/java/tech/ordinaryroad/live/chat/client/commons/base/msg/ISuperChatMsg.java

@@ -0,0 +1,49 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.commons.base.msg;
+
+/**
+ * 醒目留言
+ *
+ * @author mjz
+ * @date 2023/9/22
+ */
+public interface ISuperChatMsg extends IDanmuMsg {
+
+    /**
+     * 醒目留言持续时间,单位秒
+     */
+    int getDuration();
+
+    @Override
+    default String getBadgeName() {
+        return "";
+    }
+
+    @Override
+    default byte getBadgeLevel() {
+        return 0;
+    }
+}

+ 55 - 0
ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-client/pom.xml

@@ -0,0 +1,55 @@
+<!--
+  ~ MIT License
+  ~
+  ~ Copyright (c) 2023 OrdinaryRoad
+  ~
+  ~ Permission is hereby granted, free of charge, to any person obtaining a copy
+  ~ of this software and associated documentation files (the "Software"), to deal
+  ~ in the Software without restriction, including without limitation the rights
+  ~ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+  ~ copies of the Software, and to permit persons to whom the Software is
+  ~ furnished to do so, subject to the following conditions:
+  ~
+  ~ The above copyright notice and this permission notice shall be included in all
+  ~ copies or substantial portions of the Software.
+  ~
+  ~ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+  ~ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+  ~ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+  ~ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+  ~ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+  ~ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+  ~ SOFTWARE.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.ruoyi</groupId>
+        <artifactId>live-chat-client-commons</artifactId>
+        <version>${revision}</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+    <packaging>jar</packaging>
+
+    <artifactId>live-chat-client-commons-client</artifactId>
+    <name>live-chat-client-commons-client</name>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.ruoyi</groupId>
+            <artifactId>live-chat-client-commons-base</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.ruoyi</groupId>
+            <artifactId>live-chat-client-commons-util</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 200 - 0
ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-client/src/main/java/tech/ordinaryroad/live/chat/client/commons/client/BaseLiveChatClient.java

@@ -0,0 +1,200 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.commons.client;
+
+import lombok.Getter;
+import tech.ordinaryroad.live.chat.client.commons.base.listener.IBaseMsgListener;
+import tech.ordinaryroad.live.chat.client.commons.client.config.BaseLiveChatClientConfig;
+import tech.ordinaryroad.live.chat.client.commons.client.enums.ClientStatusEnums;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Consumer;
+
+/**
+ * @author mjz
+ * @date 2023/8/26
+ */
+public abstract class BaseLiveChatClient<
+        Config extends BaseLiveChatClientConfig,
+        MsgListener extends IBaseMsgListener<?, ?>
+        > implements IBaseLiveChatClient<MsgListener> {
+
+    private final Config config;
+    @Getter
+    private volatile ClientStatusEnums status = ClientStatusEnums.NEW;
+    protected PropertyChangeSupport statusChangeSupport = new PropertyChangeSupport(status);
+    protected volatile boolean cancelReconnect = false;
+    protected final List<MsgListener> msgListeners = Collections.synchronizedList(new ArrayList<>());
+
+    protected BaseLiveChatClient(Config config) {
+        this.config = config;
+    }
+
+    public Config getConfig() {
+        return config;
+    }
+
+    @Override
+    public void connect(Runnable success) {
+        this.connect(success, null);
+    }
+
+    @Override
+    public void connect() {
+        this.connect(null, null);
+    }
+
+    @Override
+    public void disconnect(boolean cancelReconnect) {
+        this.cancelReconnect = cancelReconnect;
+        this.disconnect();
+    }
+
+    @Override
+    public void send(Object msg) {
+        this.send(msg, null, null);
+    }
+
+    @Override
+    public void send(Object msg, Runnable success) {
+        this.send(msg, success, null);
+    }
+
+    @Override
+    public void send(Object msg, Consumer<Throwable> failed) {
+        this.send(msg, null, failed);
+    }
+
+    @Override
+    public void sendDanmu(Object danmu) {
+        this.sendDanmu(danmu, null, null);
+    }
+
+    @Override
+    public void sendDanmu(Object danmu, Runnable success) {
+        this.sendDanmu(danmu, success, null);
+    }
+
+    @Override
+    public void sendDanmu(Object danmu, Consumer<Throwable> failed) {
+        this.sendDanmu(danmu, null, failed);
+    }
+
+    @Override
+    public void clickLike(int count) {
+        this.clickLike(count, null, null);
+    }
+
+    @Override
+    public void clickLike(int count, Runnable success) {
+        this.clickLike(count, success, null);
+    }
+
+    @Override
+    public void clickLike(int count, Consumer<Throwable> failed) {
+        this.clickLike(count, null, failed);
+    }
+
+    protected abstract void tryReconnect();
+
+    protected abstract String getWebSocketUriString();
+
+    /**
+     * 判断是否处于某个状态,或者处于后续状态
+     *
+     * @param status {@link ClientStatusEnums}
+     * @return false: 还没有到达该状态
+     */
+    protected boolean checkStatus(ClientStatusEnums status) {
+        return this.status.getCode() >= Objects.requireNonNull(status).getCode();
+    }
+
+    protected void setStatus(ClientStatusEnums status) {
+        ClientStatusEnums oldStatus = this.status;
+        if (oldStatus != status) {
+            this.status = status;
+            this.statusChangeSupport.firePropertyChange("status", oldStatus, status);
+        }
+    }
+
+    public void addStatusChangeListener(PropertyChangeListener listener) {
+        this.statusChangeSupport.addPropertyChangeListener(listener);
+    }
+
+    public void removeStatusChangeListener(PropertyChangeListener listener) {
+        this.statusChangeSupport.removePropertyChangeListener(listener);
+    }
+
+    @Override
+    public void destroy() {
+        for (PropertyChangeListener propertyChangeListener : this.statusChangeSupport.getPropertyChangeListeners()) {
+            this.statusChangeSupport.removePropertyChangeListener(propertyChangeListener);
+        }
+        this.msgListeners.clear();
+    }
+
+    @Override
+    public boolean addMsgListener(MsgListener msgListener) {
+        if (msgListener == null) {
+            return false;
+        }
+        return this.msgListeners.add(msgListener);
+    }
+
+    @Override
+    public boolean addMsgListeners(List<MsgListener> msgListeners) {
+        if (msgListeners == null || msgListeners.isEmpty()) {
+            return false;
+        }
+        return this.msgListeners.addAll(msgListeners);
+    }
+
+    @Override
+    public boolean removeMsgListener(MsgListener msgListener) {
+        if (msgListener == null) {
+            return false;
+        }
+        return this.msgListeners.remove(msgListener);
+    }
+
+    @Override
+    public boolean removeMsgListeners(List<MsgListener> msgListeners) {
+        if (msgListeners == null || msgListeners.isEmpty()) {
+            return false;
+        }
+        return this.msgListeners.removeAll(msgListeners);
+    }
+
+    @Override
+    public void removeAllMsgListeners() {
+        this.msgListeners.clear();
+    }
+
+}

+ 135 - 0
ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-client/src/main/java/tech/ordinaryroad/live/chat/client/commons/client/IBaseLiveChatClient.java

@@ -0,0 +1,135 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.commons.client;
+
+import tech.ordinaryroad.live.chat.client.commons.base.listener.IBaseMsgListener;
+
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * @author mjz
+ * @date 2023/9/5
+ */
+public interface IBaseLiveChatClient<MsgListener extends IBaseMsgListener<?, ?>> {
+
+    void init();
+
+    boolean addMsgListener(MsgListener msgListener);
+
+    boolean addMsgListeners(List<MsgListener> msgListeners);
+
+    boolean removeMsgListener(MsgListener msgListener);
+
+    boolean removeMsgListeners(List<MsgListener> msgListeners);
+
+    void removeAllMsgListeners();
+
+    void connect(Runnable success, Consumer<Throwable> failed);
+
+    void connect(Runnable success);
+
+    void connect();
+
+    /**
+     * 手动断开连接
+     *
+     * @param cancelReconnect 取消本次的自动重连(如果启用自动重连)
+     */
+    void disconnect(boolean cancelReconnect);
+
+    void disconnect();
+
+    void destroy();
+
+    void send(Object msg);
+
+    void send(Object msg, Runnable success, Consumer<Throwable> failed);
+
+    void send(Object msg, Runnable success);
+
+    void send(Object msg, Consumer<Throwable> failed);
+
+    /**
+     * 发送弹幕
+     *
+     * @param danmu 弹幕内容
+     * @since 0.0.6
+     */
+    void sendDanmu(Object danmu);
+
+    /**
+     * 发送弹幕
+     *
+     * @param danmu 弹幕内容
+     * @since 0.0.6
+     */
+    void sendDanmu(Object danmu, Runnable success, Consumer<Throwable> failed);
+
+    /**
+     * 发送弹幕
+     *
+     * @param danmu 弹幕内容
+     * @since 0.0.6
+     */
+    void sendDanmu(Object danmu, Runnable success);
+
+    /**
+     * 发送弹幕
+     *
+     * @param danmu 弹幕内容
+     * @since 0.0.6
+     */
+    void sendDanmu(Object danmu, Consumer<Throwable> failed);
+
+    /**
+     * 为直播间点赞
+     *
+     * @since 0.2.0
+     */
+    void clickLike(int count);
+
+    /**
+     * 为直播间点赞
+     *
+     * @since 0.2.0
+     */
+    void clickLike(int count, Runnable success, Consumer<Throwable> failed);
+
+    /**
+     * 为直播间点赞
+     *
+     * @since 0.2.0
+     */
+    void clickLike(int count, Runnable success);
+
+    /**
+     * 为直播间点赞
+     *
+     * @since 0.2.0
+     */
+    void clickLike(int count, Consumer<Throwable> failed);
+
+}

+ 139 - 0
ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-client/src/main/java/tech/ordinaryroad/live/chat/client/commons/client/config/BaseLiveChatClientConfig.java

@@ -0,0 +1,139 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.commons.client.config;
+
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+import tech.ordinaryroad.live.chat.client.commons.base.exception.BaseException;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+
+/**
+ * 直播间弹幕客户端配置
+ *
+ * @author mjz
+ * @date 2023/8/26
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@SuperBuilder(toBuilder = true)
+public abstract class BaseLiveChatClientConfig {
+
+    protected PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
+    public static final long DEFAULT_HEARTBEAT_INITIAL_DELAY = 15;
+    public static final long DEFAULT_HEARTBEAT_PERIOD = 25;
+    public static final long DEFAULT_MIN_SEND_DANMU_PERIOD = 3000L;
+
+    private String websocketUri;
+
+    /**
+     * 浏览器中的Cookie
+     */
+    private String cookie;
+
+    /**
+     * 直播间id
+     */
+    private Object roomId;
+
+    /**
+     * 是否启用自动重连
+     */
+    @Builder.Default
+    private boolean autoReconnect = Boolean.TRUE;
+
+    /**
+     * 重试延迟时间(秒),默认5s后重试
+     */
+    @Builder.Default
+    private int reconnectDelay = 5;
+
+    /**
+     * 首次发送心跳包的延迟时间(秒)
+     */
+    @Builder.Default
+    private long heartbeatInitialDelay = DEFAULT_HEARTBEAT_INITIAL_DELAY;
+
+    /**
+     * 心跳包发送周期(秒)
+     */
+    @Builder.Default
+    private long heartbeatPeriod = DEFAULT_HEARTBEAT_PERIOD;
+
+    /**
+     * 最小发送弹幕时间间隔(毫秒)
+     */
+    @Builder.Default
+    private long minSendDanmuPeriod = DEFAULT_MIN_SEND_DANMU_PERIOD;
+
+    public void setCookie(String cookie) {
+        String oldValue = this.cookie;
+        this.cookie = cookie;
+        this.propertyChangeSupport.firePropertyChange("cookie", oldValue, cookie);
+    }
+
+    public void setRoomId(Object roomId) {
+        if (!(roomId instanceof Number || roomId instanceof String)) {
+            throw new BaseException("房间ID仅支持数字或字符串,所传参数类型:" + roomId.getClass() + "值:" + roomId);
+        }
+        Object oldValue = this.roomId;
+        this.roomId = roomId;
+        this.propertyChangeSupport.firePropertyChange("roomId", oldValue, roomId);
+    }
+
+    public void setWebsocketUri(String websocketUri) {
+        String oldValue = this.websocketUri;
+        this.websocketUri = websocketUri;
+        this.propertyChangeSupport.firePropertyChange("websocketUri", oldValue, websocketUri);
+    }
+
+    public void setMinSendDanmuPeriod(long minSendDanmuPeriod) {
+        long oldValue = this.minSendDanmuPeriod;
+        this.minSendDanmuPeriod = minSendDanmuPeriod;
+        this.propertyChangeSupport.firePropertyChange("minSendDanmuPeriod", oldValue, minSendDanmuPeriod);
+    }
+
+    public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
+        this.propertyChangeSupport.addPropertyChangeListener(propertyName, listener);
+    }
+
+    public void addPropertyChangeListener(PropertyChangeListener listener) {
+        this.propertyChangeSupport.addPropertyChangeListener(listener);
+    }
+
+    public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
+        this.propertyChangeSupport.removePropertyChangeListener(propertyName, listener);
+    }
+
+    public void removePropertyChangeListener(PropertyChangeListener listener) {
+        this.propertyChangeSupport.removePropertyChangeListener(listener);
+    }
+}

+ 83 - 0
ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-client/src/main/java/tech/ordinaryroad/live/chat/client/commons/client/enums/ClientStatusEnums.java

@@ -0,0 +1,83 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.commons.client.enums;
+
+/**
+ * @author mjz
+ * @date 2023/8/26
+ */
+public enum ClientStatusEnums {
+    /**
+     * 新创建
+     */
+    NEW(0),
+
+    /**
+     * 已初始化
+     */
+    INITIALIZED(1),
+
+    /**
+     * 连接中
+     */
+    CONNECTING(100),
+
+    /**
+     * 重新连接中
+     */
+    RECONNECTING(101),
+
+    /**
+     * 已连接
+     */
+    CONNECTED(200),
+
+    /**
+     * 连接失败
+     */
+    CONNECT_FAILED(401),
+
+    /**
+     * 已断开连接
+     */
+    DISCONNECTED(400),
+
+    /**
+     * 已销毁
+     */
+    DESTROYED(-1),
+    ;
+
+    public int getCode() {
+        return code;
+    }
+
+    ClientStatusEnums(int order) {
+        this.code = order;
+    }
+
+    private final int code;
+
+}

+ 25 - 0
ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-util/pom.xml

@@ -0,0 +1,25 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.ruoyi</groupId>
+        <artifactId>live-chat-client-commons</artifactId>
+        <version>${revision}</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+    <packaging>jar</packaging>
+
+    <artifactId>live-chat-client-commons-util</artifactId>
+    <name>live-chat-client-commons-util</name>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 80 - 0
ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-util/src/main/java/tech/ordinaryroad/live/chat/client/commons/util/OrLiveChatCookieUtil.java

@@ -0,0 +1,80 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.commons.util;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.StrUtil;
+
+import java.net.HttpCookie;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+/**
+ * @author mjz
+ * @date 2023/8/27
+ */
+public class OrLiveChatCookieUtil {
+
+    public static String toString(List<HttpCookie> cookies) {
+        if (CollUtil.isEmpty(cookies)) {
+            return StrUtil.EMPTY;
+        }
+
+        return cookies.stream().map(httpCookie -> {
+            httpCookie.setVersion(0);
+            return httpCookie.toString();
+        }).collect(Collectors.joining("; "));
+    }
+
+    public static Map<String, String> parseCookieString(String cookies) {
+        Map<String, String> map = new HashMap<>();
+        if (StrUtil.isNotBlank(cookies) && !StrUtil.isNullOrUndefined(cookies)) {
+            try {
+                String[] split = cookies.split("; ");
+                for (String s : split) {
+                    String[] split1 = s.split("=");
+                    map.put(split1[0], split1[1]);
+                }
+            } catch (Exception e) {
+                throw new RuntimeException("cookie解析失败 " + cookies, e);
+            }
+        }
+        return map;
+    }
+
+    public static String getCookieByName(Map<String, String> cookieMap, String name, Supplier<String> supplier) {
+        String str = MapUtil.getStr(cookieMap, name);
+        return str == null ? supplier.get() : str;
+    }
+
+    public static String getCookieByName(String cookie, String name, Supplier<String> supplier) {
+        String str = MapUtil.getStr(parseCookieString(cookie), name);
+        return str == null ? supplier.get() : str;
+    }
+}

+ 40 - 0
ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-util/src/main/java/tech/ordinaryroad/live/chat/client/commons/util/OrLiveChatNumberUtil.java

@@ -0,0 +1,40 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.commons.util;
+
+import cn.hutool.core.util.NumberUtil;
+import cn.hutool.core.util.StrUtil;
+
+/**
+ * @author mjz
+ * @date 2023/12/2
+ */
+public class OrLiveChatNumberUtil extends NumberUtil {
+
+    public static long parseLong(Object object){
+        return NumberUtil.parseLong(StrUtil.toStringOrNull(object));
+    }
+
+}

+ 50 - 0
ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-util/src/main/java/tech/ordinaryroad/live/chat/client/commons/util/OrLiveChatReflectUtil.java

@@ -0,0 +1,50 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.commons.util;
+
+import cn.hutool.core.util.ReflectUtil;
+
+import java.lang.reflect.Method;
+
+/**
+ * @author mjz
+ * @date 2023/8/28
+ */
+public class OrLiveChatReflectUtil extends ReflectUtil {
+
+    public static Method getGetterMethod(Class<?> objectClass, String key) {
+        Method method;
+        if (key.startsWith("is")) {
+            method = ReflectUtil.getMethodByNameIgnoreCase(objectClass, key);
+            if (method == null) {
+                ReflectUtil.getMethodByNameIgnoreCase(objectClass, "get" + key);
+            }
+        } else {
+            method = ReflectUtil.getMethodByNameIgnoreCase(objectClass, "get" + key);
+        }
+        return method;
+    }
+
+}

+ 54 - 0
ruoyi-common/ruoyi-common-live/live-chat-client-commons/live-chat-client-commons-util/src/main/java/tech/ordinaryroad/live/chat/client/commons/util/OrLocalDateTimeUtil.java

@@ -0,0 +1,54 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.commons.util;
+
+import cn.hutool.core.date.LocalDateTimeUtil;
+
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+
+/**
+ * @author mjz
+ * @date 2023/9/7
+ */
+public class OrLocalDateTimeUtil extends LocalDateTimeUtil {
+
+    public static ZoneId ZONE_ID_CTT = ZoneId.of(ZoneId.SHORT_IDS.get("CTT"));
+
+    /**
+     * 获取中国标准时间的当前时间戳(毫秒)
+     */
+    public static long zonedCurrentTimeMillis() {
+        ZonedDateTime now = ZonedDateTime.now(ZONE_ID_CTT);
+        return now.toEpochSecond() * 1000 + now.getNano() / 1_000_000;
+    }
+
+    /**
+     * 获取中国标准时间的当前时间戳(秒)
+     */
+    public static long zonedCurrentTimeSecs() {
+        return ZonedDateTime.now(ZONE_ID_CTT).toEpochSecond();
+    }
+}

+ 44 - 0
ruoyi-common/ruoyi-common-live/live-chat-client-commons/pom.xml

@@ -0,0 +1,44 @@
+<!--
+  ~ MIT License
+  ~
+  ~ Copyright (c) 2023 OrdinaryRoad
+  ~
+  ~ Permission is hereby granted, free of charge, to any person obtaining a copy
+  ~ of this software and associated documentation files (the "Software"), to deal
+  ~ in the Software without restriction, including without limitation the rights
+  ~ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+  ~ copies of the Software, and to permit persons to whom the Software is
+  ~ furnished to do so, subject to the following conditions:
+  ~
+  ~ The above copyright notice and this permission notice shall be included in all
+  ~ copies or substantial portions of the Software.
+  ~
+  ~ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+  ~ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+  ~ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+  ~ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+  ~ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+  ~ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+  ~ SOFTWARE.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.ruoyi</groupId>
+        <artifactId>ruoyi-common-live</artifactId>
+        <version>${revision}</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <packaging>pom</packaging>
+
+    <artifactId>live-chat-client-commons</artifactId>
+
+    <modules>
+        <module>live-chat-client-commons-base</module>
+        <module>live-chat-client-commons-util</module>
+        <module>live-chat-client-commons-client</module>
+    </modules>
+</project>

+ 55 - 0
ruoyi-common/ruoyi-common-live/live-chat-client-servers/live-chat-client-servers-netty-client/pom.xml

@@ -0,0 +1,55 @@
+<!--
+  ~ MIT License
+  ~
+  ~ Copyright (c) 2023 OrdinaryRoad
+  ~
+  ~ Permission is hereby granted, free of charge, to any person obtaining a copy
+  ~ of this software and associated documentation files (the "Software"), to deal
+  ~ in the Software without restriction, including without limitation the rights
+  ~ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+  ~ copies of the Software, and to permit persons to whom the Software is
+  ~ furnished to do so, subject to the following conditions:
+  ~
+  ~ The above copyright notice and this permission notice shall be included in all
+  ~ copies or substantial portions of the Software.
+  ~
+  ~ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+  ~ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+  ~ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+  ~ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+  ~ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+  ~ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+  ~ SOFTWARE.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.ruoyi</groupId>
+        <artifactId>live-chat-client-servers</artifactId>
+        <version>${revision}</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <packaging>jar</packaging>
+
+    <artifactId>live-chat-client-servers-netty-client</artifactId>
+    <name>live-chat-client-servers-netty</name>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.ruoyi</groupId>
+            <artifactId>live-chat-client-commons-client</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.ruoyi</groupId>
+            <artifactId>live-chat-client-servers-netty</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 349 - 0
ruoyi-common/ruoyi-common-live/live-chat-client-servers/live-chat-client-servers-netty-client/src/main/java/tech/ordinaryroad/live/chat/client/servers/netty/client/base/BaseNettyClient.java

@@ -0,0 +1,349 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.servers.netty.client.base;
+
+import io.netty.bootstrap.Bootstrap;
+import io.netty.channel.*;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.codec.http.HttpClientCodec;
+import io.netty.handler.codec.http.HttpObjectAggregator;
+import io.netty.handler.ssl.SslContext;
+import io.netty.handler.ssl.SslContextBuilder;
+import io.netty.handler.stream.ChunkedWriteHandler;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import tech.ordinaryroad.live.chat.client.commons.base.exception.BaseException;
+import tech.ordinaryroad.live.chat.client.commons.base.listener.IBaseConnectionListener;
+import tech.ordinaryroad.live.chat.client.commons.base.listener.IBaseMsgListener;
+import tech.ordinaryroad.live.chat.client.commons.base.msg.IMsg;
+import tech.ordinaryroad.live.chat.client.commons.client.BaseLiveChatClient;
+import tech.ordinaryroad.live.chat.client.commons.client.enums.ClientStatusEnums;
+import tech.ordinaryroad.live.chat.client.servers.netty.client.config.BaseNettyClientConfig;
+import tech.ordinaryroad.live.chat.client.servers.netty.handler.base.BaseBinaryFrameHandler;
+import tech.ordinaryroad.live.chat.client.servers.netty.handler.base.BaseConnectionHandler;
+
+import javax.net.ssl.SSLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+/**
+ * @author mjz
+ * @date 2023/8/26
+ */
+@Slf4j
+public abstract class BaseNettyClient
+        <Config extends BaseNettyClientConfig,
+                CmdEnum extends Enum<CmdEnum>,
+                Msg extends IMsg,
+                MsgListener extends IBaseMsgListener<BinaryFrameHandler, CmdEnum>,
+                ConnectionHandler extends BaseConnectionHandler<ConnectionHandler>,
+                BinaryFrameHandler extends BaseBinaryFrameHandler<BinaryFrameHandler, CmdEnum, Msg, MsgListener>
+                >
+        extends BaseLiveChatClient<Config, MsgListener> {
+
+    @Getter
+    private final EventLoopGroup workerGroup;
+    @Getter
+    private final Bootstrap bootstrap = new Bootstrap();
+    private BinaryFrameHandler binaryFrameHandler;
+    private ConnectionHandler connectionHandler;
+    private IBaseConnectionListener<ConnectionHandler> connectionListener;
+    private Channel channel;
+    @Getter
+    private URI websocketUri;
+    protected IBaseConnectionListener<ConnectionHandler> clientConnectionListener;
+    /**
+     * 控制弹幕发送频率
+     */
+    private volatile long lastSendDanmuTimeInMillis;
+
+    public abstract ConnectionHandler initConnectionHandler(IBaseConnectionListener<ConnectionHandler> clientConnectionListener);
+
+    public abstract BinaryFrameHandler initBinaryFrameHandler();
+
+    protected BaseNettyClient(Config config, EventLoopGroup workerGroup, IBaseConnectionListener<ConnectionHandler> connectionListener) {
+        super(config);
+        this.workerGroup = workerGroup;
+        this.connectionListener = connectionListener;
+    }
+
+    public void onConnected(ConnectionHandler connectionHandler) {
+        this.setStatus(ClientStatusEnums.CONNECTED);
+        if (this.connectionListener != null) {
+            this.connectionListener.onConnected(connectionHandler);
+        }
+    }
+
+    public void onConnectFailed(ConnectionHandler connectionHandler) {
+        this.setStatus(ClientStatusEnums.CONNECT_FAILED);
+        tryReconnect();
+        if (this.connectionListener != null) {
+            this.connectionListener.onConnectFailed(connectionHandler);
+        }
+    }
+
+    public void onDisconnected(ConnectionHandler connectionHandler) {
+        this.setStatus(ClientStatusEnums.DISCONNECTED);
+        tryReconnect();
+        if (this.connectionListener != null) {
+            this.connectionListener.onDisconnected(connectionHandler);
+        }
+    }
+
+    @Override
+    public void init() {
+        if (checkStatus(ClientStatusEnums.INITIALIZED)) {
+            return;
+        }
+        try {
+            this.websocketUri = new URI(getWebSocketUriString());
+            SslContext sslCtx = SslContextBuilder.forClient().build();
+
+            this.clientConnectionListener = new IBaseConnectionListener<ConnectionHandler>() {
+                @Override
+                public void onConnected(ConnectionHandler connectionHandler) {
+                    BaseNettyClient.this.onConnected(connectionHandler);
+                }
+
+                @Override
+                public void onConnectFailed(ConnectionHandler connectionHandler) {
+                    BaseNettyClient.this.onConnectFailed(connectionHandler);
+                }
+
+                @Override
+                public void onDisconnected(ConnectionHandler connectionHandler) {
+                    BaseNettyClient.this.onDisconnected(connectionHandler);
+                }
+            };
+            this.binaryFrameHandler = this.initBinaryFrameHandler();
+            this.connectionHandler = this.initConnectionHandler(this.clientConnectionListener);
+
+            this.bootstrap.group(this.workerGroup)
+                    // 创建Channel
+                    .channel(NioSocketChannel.class)
+                    .remoteAddress(this.websocketUri.getHost(), getInetPort())
+                    .option(ChannelOption.TCP_NODELAY, true)
+                    .option(ChannelOption.SO_KEEPALIVE, true)
+                    // Channel配置
+                    .handler(new ChannelInitializer<SocketChannel>() {
+                        @Override
+                        protected void initChannel(SocketChannel ch) {
+                            // 责任链
+                            ChannelPipeline pipeline = ch.pipeline();
+
+                            // 放到第一位 addFirst 支持wss链接服务端
+                            pipeline.addFirst(sslCtx.newHandler(ch.alloc(), BaseNettyClient.this.websocketUri.getHost(), getInetPort()));
+
+                            // 添加一个http的编解码器
+                            pipeline.addLast(new HttpClientCodec());
+                            // 添加一个用于支持大数据流的支持
+                            pipeline.addLast(new ChunkedWriteHandler());
+                            // 添加一个聚合器,这个聚合器主要是将HttpMessage聚合成FullHttpRequest/Response
+                            pipeline.addLast(new HttpObjectAggregator(BaseNettyClient.this.getConfig().getAggregatorMaxContentLength()));
+
+                            // 连接处理器
+                            pipeline.addLast(BaseNettyClient.this.connectionHandler);
+                            // 弹幕处理器
+                            pipeline.addLast(BaseNettyClient.this.binaryFrameHandler);
+                        }
+                    });
+            this.setStatus(ClientStatusEnums.INITIALIZED);
+        } catch (URISyntaxException e) {
+            throw new BaseException(e);
+        } catch (SSLException e) {
+            throw new BaseException(e);
+        }
+    }
+
+    private int getInetPort() {
+        int port = this.websocketUri.getPort();
+        return port == -1 ? "wss".equalsIgnoreCase(websocketUri.getScheme()) ? 443 : 80 : port;
+    }
+
+    @Override
+    public void connect(Runnable success, Consumer<Throwable> failed) {
+        if (this.cancelReconnect) {
+            this.cancelReconnect = false;
+        }
+        if (!checkStatus(ClientStatusEnums.INITIALIZED)) {
+            return;
+        }
+        if (getStatus() == ClientStatusEnums.CONNECTED) {
+            return;
+        }
+        if (getStatus() != ClientStatusEnums.RECONNECTING) {
+            this.setStatus(ClientStatusEnums.CONNECTING);
+        }
+        this.bootstrap.connect().addListener((ChannelFutureListener) connectFuture -> {
+            if (connectFuture.isSuccess()) {
+                if (log.isDebugEnabled()) {
+                    log.debug("连接建立成功!");
+                }
+                this.channel = connectFuture.channel();
+                // 监听是否握手成功
+                this.connectionHandler.getHandshakeFuture().addListener((ChannelFutureListener) handshakeFuture -> {
+                    try {
+                        connectionHandler.sendAuthRequest(channel);
+                        if (success != null) {
+                            success.run();
+                        }
+                    } catch (Exception e) {
+                        log.error("认证包发送失败,断开连接", e);
+                        this.disconnect();
+                    }
+                });
+            } else {
+                log.error("连接建立失败", connectFuture.cause());
+                this.onConnectFailed(this.connectionHandler);
+                if (failed != null) {
+                    failed.accept(connectFuture.cause());
+                }
+            }
+        });
+    }
+
+    @Override
+    public void disconnect() {
+        if (this.channel == null) {
+            return;
+        }
+        this.channel.close();
+    }
+
+    @Override
+    protected void tryReconnect() {
+        if (this.cancelReconnect) {
+            this.cancelReconnect = false;
+            return;
+        }
+        if (!getConfig().isAutoReconnect()) {
+            return;
+        }
+        if (log.isWarnEnabled()) {
+            log.warn("{}s后将重新连接 {}", getConfig().getReconnectDelay(), getConfig().getRoomId());
+        }
+        workerGroup.schedule(() -> {
+            this.setStatus(ClientStatusEnums.RECONNECTING);
+            this.connect();
+        }, getConfig().getReconnectDelay(), TimeUnit.SECONDS);
+    }
+
+    @Override
+    public void send(Object msg, Runnable success, Consumer<Throwable> failed) {
+        ChannelFuture future = this.channel.writeAndFlush(msg);
+        if (success != null || failed != null) {
+            future.addListener((ChannelFutureListener) channelFuture -> {
+                if (channelFuture.isSuccess()) {
+                    if (success != null) {
+                        success.run();
+                    }
+                } else {
+                    if (failed != null) {
+                        failed.accept(channelFuture.cause());
+                    }
+                }
+            });
+        }
+    }
+
+    @Override
+    public void destroy() {
+        super.destroy();
+
+        // 销毁时不需要重连
+        this.cancelReconnect = true;
+        workerGroup.shutdownGracefully().addListener(future -> {
+            if (future.isSuccess()) {
+                this.setStatus(ClientStatusEnums.DESTROYED);
+            } else {
+                throw new BaseException("client销毁失败", future.cause());
+            }
+        });
+    }
+
+    @Override
+    protected String getWebSocketUriString() {
+        return getConfig().getWebsocketUri();
+    }
+
+    @Override
+    protected void setStatus(ClientStatusEnums status) {
+        if (log.isDebugEnabled()) {
+            if (getStatus() != status) {
+                log.debug("{} 状态变化 {} => {}\n", getClass().getSimpleName(), getStatus(), status);
+            }
+        }
+        super.setStatus(status);
+    }
+
+    @Override
+    public void sendDanmu(Object danmu, Runnable success, Consumer<Throwable> failed) {
+        throw new BaseException("暂未支持该功能");
+    }
+
+    @Override
+    public void clickLike(int count, Runnable success, Consumer<Throwable> failed) {
+        throw new BaseException("暂未支持该功能");
+    }
+
+    /**
+     * 发送弹幕前判断是否可以发送
+     *
+     * @param checkConnected 是否检查Client连接状态
+     */
+    protected boolean checkCanSendDanmu(boolean checkConnected) {
+        if (checkConnected && getStatus() != ClientStatusEnums.CONNECTED) {
+            throw new BaseException("连接未建立,无法发送弹幕");
+        }
+        if (System.currentTimeMillis() - this.lastSendDanmuTimeInMillis <= getConfig().getMinSendDanmuPeriod()) {
+            if (log.isWarnEnabled()) {
+                log.warn("发送弹幕频率过快,忽略该次发送");
+            }
+            return false;
+        }
+        return true;
+    }
+
+    protected boolean checkCanSendDanmu() {
+        return checkCanSendDanmu(true);
+    }
+
+    /**
+     * 发送弹幕后调用该方法
+     */
+    protected void finishSendDanmu() {
+        this.lastSendDanmuTimeInMillis = System.currentTimeMillis();
+        if (log.isDebugEnabled()) {
+            log.debug("弹幕发送完成");
+        }
+    }
+
+    public void iteratorMsgListeners(Consumer<MsgListener> consumer) {
+        binaryFrameHandler.iteratorMsgListeners(consumer);
+    }
+}

+ 65 - 0
ruoyi-common/ruoyi-common-live/live-chat-client-servers/live-chat-client-servers-netty-client/src/main/java/tech/ordinaryroad/live/chat/client/servers/netty/client/config/BaseNettyClientConfig.java

@@ -0,0 +1,65 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.servers.netty.client.config;
+
+import io.netty.handler.codec.http.HttpHeaders;
+import io.netty.handler.codec.http.HttpObjectAggregator;
+import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory;
+import io.netty.handler.codec.http.websocketx.WebSocketVersion;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+import tech.ordinaryroad.live.chat.client.commons.client.config.BaseLiveChatClientConfig;
+
+import java.net.URI;
+
+/**
+ * @author mjz
+ * @date 2023/8/26
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@SuperBuilder(toBuilder = true)
+public abstract class BaseNettyClientConfig extends BaseLiveChatClientConfig {
+
+    /**
+     * 聚合器允许的最大消息体长度,默认 64*1024 byte
+     *
+     * @see HttpObjectAggregator#HttpObjectAggregator(int)
+     */
+    @Builder.Default
+    private int aggregatorMaxContentLength = 64 * 1024;
+
+    /**
+     * WebSocketClientHandshaker最大消息体长度,默认 64*1024 byte
+     *
+     * @see WebSocketClientHandshakerFactory#newHandshaker(URI, WebSocketVersion, String, boolean, HttpHeaders, int)
+     */
+    @Builder.Default
+    private int maxFramePayloadLength = 64 * 1024;
+}

+ 66 - 0
ruoyi-common/ruoyi-common-live/live-chat-client-servers/live-chat-client-servers-netty-client/src/main/java/tech/ordinaryroad/live/chat/client/servers/netty/client/handler/BaseNettyClientBinaryFrameHandler.java

@@ -0,0 +1,66 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.servers.netty.client.handler;
+
+import lombok.Getter;
+import tech.ordinaryroad.live.chat.client.commons.base.listener.IBaseMsgListener;
+import tech.ordinaryroad.live.chat.client.commons.base.msg.IMsg;
+import tech.ordinaryroad.live.chat.client.servers.netty.client.base.BaseNettyClient;
+import tech.ordinaryroad.live.chat.client.servers.netty.handler.base.BaseBinaryFrameHandler;
+
+import java.util.List;
+
+/**
+ * BaseClientBinaryFrameHandler
+ *
+ * @author mjz
+ * @date 2023/8/30
+ */
+public abstract class BaseNettyClientBinaryFrameHandler<
+        Client extends BaseNettyClient<?, ?, ?, ?, ?, ?>,
+        BinaryFrameHandler extends BaseBinaryFrameHandler<BinaryFrameHandler, CmdEnum, Msg, MsgListener>,
+        CmdEnum extends Enum<CmdEnum>,
+        Msg extends IMsg,
+        MsgListener extends IBaseMsgListener<BinaryFrameHandler, CmdEnum>>
+        extends BaseBinaryFrameHandler<BinaryFrameHandler, CmdEnum, Msg, MsgListener> {
+
+    @Getter
+    protected final Client client;
+
+    public BaseNettyClientBinaryFrameHandler(List<MsgListener> msgListeners, Client client, long roomId) {
+        super(msgListeners, roomId);
+        this.client = client;
+    }
+
+    public BaseNettyClientBinaryFrameHandler(List<MsgListener> msgListeners, Client client) {
+        super(msgListeners, client.getConfig().getRoomId());
+        this.client = client;
+    }
+
+    public BaseNettyClientBinaryFrameHandler(List<MsgListener> msgListeners, long roomId) {
+        super(msgListeners, roomId);
+        this.client = null;
+    }
+}

+ 65 - 0
ruoyi-common/ruoyi-common-live/live-chat-client-servers/live-chat-client-servers-netty-client/src/main/java/tech/ordinaryroad/live/chat/client/servers/netty/client/handler/BaseNettyClientConnectionHandler.java

@@ -0,0 +1,65 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.servers.netty.client.handler;
+
+import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker;
+import lombok.Getter;
+import tech.ordinaryroad.live.chat.client.commons.base.listener.IBaseConnectionListener;
+import tech.ordinaryroad.live.chat.client.servers.netty.client.base.BaseNettyClient;
+import tech.ordinaryroad.live.chat.client.servers.netty.handler.base.BaseConnectionHandler;
+
+/**
+ * BaseClientConnectionHandler
+ *
+ * @author mjz
+ * @date 2023/8/27
+ */
+public abstract class BaseNettyClientConnectionHandler<
+        Client extends BaseNettyClient<?, ?, ?, ?, ?, ?>,
+        ConnectionHandler extends BaseConnectionHandler<ConnectionHandler>>
+        extends BaseConnectionHandler<ConnectionHandler> {
+
+    @Getter
+    protected final Client client;
+
+    public BaseNettyClientConnectionHandler(WebSocketClientHandshaker handshaker, Client client, IBaseConnectionListener<ConnectionHandler> listener) {
+        super(handshaker, listener);
+        this.client = client;
+    }
+
+    public BaseNettyClientConnectionHandler(WebSocketClientHandshaker handshaker, Client client) {
+        this(handshaker, client, null);
+    }
+
+    public BaseNettyClientConnectionHandler(WebSocketClientHandshaker handshaker, IBaseConnectionListener<ConnectionHandler> listener) {
+        super(handshaker, listener);
+        this.client = null;
+    }
+
+    public BaseNettyClientConnectionHandler(WebSocketClientHandshaker handshaker, long roomId) {
+        super(handshaker, null);
+        this.client = null;
+    }
+}

+ 59 - 0
ruoyi-common/ruoyi-common-live/live-chat-client-servers/live-chat-client-servers-netty/pom.xml

@@ -0,0 +1,59 @@
+<!--
+  ~ MIT License
+  ~
+  ~ Copyright (c) 2023 OrdinaryRoad
+  ~
+  ~ Permission is hereby granted, free of charge, to any person obtaining a copy
+  ~ of this software and associated documentation files (the "Software"), to deal
+  ~ in the Software without restriction, including without limitation the rights
+  ~ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+  ~ copies of the Software, and to permit persons to whom the Software is
+  ~ furnished to do so, subject to the following conditions:
+  ~
+  ~ The above copyright notice and this permission notice shall be included in all
+  ~ copies or substantial portions of the Software.
+  ~
+  ~ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+  ~ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+  ~ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+  ~ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+  ~ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+  ~ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+  ~ SOFTWARE.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.ruoyi</groupId>
+        <artifactId>live-chat-client-servers</artifactId>
+        <version>${revision}</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+    <packaging>jar</packaging>
+
+    <artifactId>live-chat-client-servers-netty</artifactId>
+    <name>live-chat-client-servers-netty</name>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.ruoyi</groupId>
+            <artifactId>live-chat-client-commons-base</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>io.netty</groupId>
+            <artifactId>netty-all</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-classic</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 39 - 0
ruoyi-common/ruoyi-common-live/live-chat-client-servers/live-chat-client-servers-netty/src/main/java/tech/ordinaryroad/live/chat/client/servers/netty/frame/base/BaseBinaryWebSocketFrame.java

@@ -0,0 +1,39 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.servers.netty.frame.base;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
+
+/**
+ * @author mjz
+ * @date 2023/1/5
+ */
+public abstract class BaseBinaryWebSocketFrame extends BinaryWebSocketFrame {
+
+    public BaseBinaryWebSocketFrame(ByteBuf byteBuf) {
+        super(byteBuf);
+    }
+}

+ 184 - 0
ruoyi-common/ruoyi-common-live/live-chat-client-servers/live-chat-client-servers-netty/src/main/java/tech/ordinaryroad/live/chat/client/servers/netty/handler/base/BaseBinaryFrameHandler.java

@@ -0,0 +1,184 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.servers.netty.handler.base;
+
+import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException;
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.SimpleChannelInboundHandler;
+import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import tech.ordinaryroad.live.chat.client.commons.base.listener.IBaseMsgListener;
+import tech.ordinaryroad.live.chat.client.commons.base.msg.BaseCmdMsg;
+import tech.ordinaryroad.live.chat.client.commons.base.msg.BaseMsg;
+import tech.ordinaryroad.live.chat.client.commons.base.msg.ICmdMsg;
+import tech.ordinaryroad.live.chat.client.commons.base.msg.IMsg;
+
+import java.util.List;
+import java.util.function.Consumer;
+
+
+/**
+ * 消息处理器
+ *
+ * @author mjz
+ * @date 2023/1/4
+ */
+@Slf4j
+public abstract class BaseBinaryFrameHandler<
+        T extends BaseBinaryFrameHandler<?, ?, ?, ?>,
+        CmdEnum extends Enum<CmdEnum>,
+        Msg extends IMsg,
+        MsgListener extends IBaseMsgListener<T, CmdEnum>
+        > extends SimpleChannelInboundHandler<BinaryWebSocketFrame>
+        implements IBaseMsgListener<T, CmdEnum> {
+
+    @Getter
+    private final Object roomId;
+    protected final List<MsgListener> msgListeners;
+
+    public BaseBinaryFrameHandler(List<MsgListener> msgListeners, Object roomId) {
+        this.msgListeners = msgListeners;
+        this.roomId = roomId;
+        if (this.msgListeners == null || this.msgListeners.isEmpty()) {
+            if (log.isDebugEnabled()) {
+                log.debug("listener not set");
+            }
+        }
+    }
+
+    /**
+     * 解码收到的二进制流
+     *
+     * @param byteBuf ByteBuf
+     * @return List<Msg>
+     */
+    protected abstract List<Msg> decode(ByteBuf byteBuf);
+
+    @SuppressWarnings("unchecked")
+    protected void channelRead0(ChannelHandlerContext ctx, BinaryWebSocketFrame message) {
+        ByteBuf byteBuf = message.content();
+        List<Msg> msgList = this.decode(byteBuf);
+        if (msgList == null || msgList.isEmpty()) {
+            if (log.isDebugEnabled()) {
+                log.debug("msgList is empty");
+            }
+            return;
+        }
+        for (Msg msg : msgList) {
+            this.onMsg((T) BaseBinaryFrameHandler.this, msg);
+            if (msg instanceof ICmdMsg<?>) {
+                ICmdMsg<?> cmdMsg = (ICmdMsg<?>) msg;
+                Enum<?> cmdEnum = cmdMsg.getCmdEnum();
+                if (cmdEnum == null) {
+                    this.onUnknownCmd((T) BaseBinaryFrameHandler.this, cmdMsg.getCmd(), cmdMsg);
+                } else {
+                    this.onCmdMsg((T) BaseBinaryFrameHandler.this, (CmdEnum) cmdEnum, (ICmdMsg<CmdEnum>) cmdMsg);
+                }
+            }
+            if (msg instanceof BaseCmdMsg<?>) {
+                BaseCmdMsg<?> cmdMsg = (BaseCmdMsg<?>) msg;
+                Enum<?> cmdEnum = cmdMsg.getCmdEnum();
+                if (cmdEnum == null) {
+                    this.onUnknownCmd((T) BaseBinaryFrameHandler.this, cmdMsg.getCmd(), cmdMsg);
+                } else {
+                    this.onCmdMsg((T) BaseBinaryFrameHandler.this, (CmdEnum) cmdEnum, (BaseCmdMsg<CmdEnum>) cmdMsg);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
+        if (cause.getCause() instanceof UnrecognizedPropertyException) {
+            log.error("缺少字段:{}", cause.getMessage());
+        } else {
+            super.exceptionCaught(ctx, cause);
+        }
+    }
+
+    @Override
+    public void onMsg(T t, IMsg msg) {
+        IBaseMsgListener.super.onMsg(t, msg);
+        iteratorMsgListeners(msgListener -> msgListener.onMsg(t, msg));
+    }
+
+    /**
+     * 重写该方法,判断CMD,或者调用{@link IBaseMsgListener#onOtherCmdMsg(Object, Enum, ICmdMsg)}
+     *
+     * @param t      BaseBinaryFrameHandler
+     * @param cmd    CmdEnum
+     * @param cmdMsg BaseMsg
+     */
+    @Override
+    public void onCmdMsg(T t, CmdEnum cmd, ICmdMsg<CmdEnum> cmdMsg) {
+        IBaseMsgListener.super.onCmdMsg(t, cmd, cmdMsg);
+        iteratorMsgListeners(msgListener -> msgListener.onCmdMsg(t, cmd, cmdMsg));
+    }
+
+    @Override
+    public void onUnknownCmd(T t, String cmdString, IMsg msg) {
+        IBaseMsgListener.super.onUnknownCmd(t, cmdString, msg);
+        iteratorMsgListeners(msgListener -> msgListener.onUnknownCmd(t, cmdString, msg));
+    }
+
+    @SuppressWarnings("ForLoopReplaceableByForEach")
+    public void iteratorMsgListeners(Consumer<MsgListener> consumer) {
+        if (msgListeners.isEmpty()) {
+            return;
+        }
+        for (int i = 0; i < msgListeners.size(); i++) {
+            consumer.accept(msgListeners.get(i));
+        }
+    }
+
+    @Override
+    public void onCmdMsg(T t, CmdEnum cmd, BaseCmdMsg<CmdEnum> cmdMsg) {
+        IBaseMsgListener.super.onCmdMsg(t, cmd, cmdMsg);
+        iteratorMsgListeners(msgListener -> msgListener.onCmdMsg(t, cmd, cmdMsg));
+    }
+
+    @Override
+    public void onUnknownCmd(T t, String cmdString, BaseMsg msg) {
+        IBaseMsgListener.super.onUnknownCmd(t, cmdString, msg);
+        iteratorMsgListeners(msgListener -> msgListener.onUnknownCmd(t, cmdString, msg));
+    }
+
+    public String getRoomIdAsString() {
+        if (this.roomId == null) {
+            return "";
+        }
+        return this.roomId.toString();
+    }
+
+    public long getRoomIdAsLong() {
+        String roomIdAsString = this.getRoomIdAsString();
+        if (roomIdAsString.trim().isEmpty()) {
+            return 0L;
+        }
+        return Long.parseLong(roomIdAsString);
+    }
+}

+ 168 - 0
ruoyi-common/ruoyi-common-live/live-chat-client-servers/live-chat-client-servers-netty/src/main/java/tech/ordinaryroad/live/chat/client/servers/netty/handler/base/BaseConnectionHandler.java

@@ -0,0 +1,168 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.servers.netty.handler.base;
+
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelPromise;
+import io.netty.channel.SimpleChannelInboundHandler;
+import io.netty.handler.codec.http.FullHttpResponse;
+import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker;
+import io.netty.handler.codec.http.websocketx.WebSocketHandshakeException;
+import io.netty.handler.ssl.SslCloseCompletionEvent;
+import io.netty.handler.ssl.SslHandshakeCompletionEvent;
+import io.netty.util.concurrent.ScheduledFuture;
+import lombok.Getter;
+import lombok.extern.slf4j.Slf4j;
+import tech.ordinaryroad.live.chat.client.commons.base.listener.IBaseConnectionListener;
+
+import java.util.concurrent.TimeUnit;
+
+
+/**
+ * 连接处理器
+ *
+ * @author mjz
+ * @date 2023/8/21
+ */
+@Slf4j
+public abstract class BaseConnectionHandler<ConnectionHandler extends BaseConnectionHandler<?>> extends SimpleChannelInboundHandler<FullHttpResponse> {
+
+    private final WebSocketClientHandshaker handshaker;
+    @Getter
+    private ChannelPromise handshakeFuture;
+    private final IBaseConnectionListener<ConnectionHandler> listener;
+    /**
+     * 客户端发送心跳包
+     */
+    private ScheduledFuture<?> scheduledFuture = null;
+
+    public BaseConnectionHandler(WebSocketClientHandshaker handshaker, IBaseConnectionListener<ConnectionHandler> listener) {
+        this.handshaker = handshaker;
+        this.listener = listener;
+    }
+
+    public BaseConnectionHandler(WebSocketClientHandshaker handshaker) {
+        this(handshaker, null);
+    }
+
+
+    @Override
+    public void handlerAdded(ChannelHandlerContext ctx) {
+        this.handshakeFuture = ctx.newPromise();
+    }
+
+    @Override
+    public void channelActive(ChannelHandlerContext ctx) {
+        this.handshaker.handshake(ctx.channel());
+    }
+
+    protected void channelRead0(ChannelHandlerContext ctx, FullHttpResponse msg) throws Exception {
+        // 判断是否正确握手
+        if (this.handshaker.isHandshakeComplete()) {
+            handshakeSuccessfully(ctx, msg);
+        } else {
+            try {
+                handshakeSuccessfully(ctx, msg);
+            } catch (WebSocketHandshakeException e) {
+                handshakeFailed(msg, e);
+            }
+        }
+    }
+
+    @Override
+    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
+        if (log.isDebugEnabled()) {
+            log.debug("userEventTriggered {}", evt.getClass());
+        }
+        if (evt instanceof SslHandshakeCompletionEvent) {
+            heartbeatCancel();
+            heartbeatStart(ctx);
+            if (this.listener != null) {
+                listener.onConnected((ConnectionHandler) BaseConnectionHandler.this);
+            }
+        } else if (evt instanceof SslCloseCompletionEvent) {
+            heartbeatCancel();
+            if (this.listener != null) {
+                listener.onDisconnected((ConnectionHandler) BaseConnectionHandler.this);
+            }
+        } else {
+            log.error("待处理 {}", evt.getClass());
+        }
+        super.userEventTriggered(ctx, evt);
+    }
+
+    /**
+     * 开始发送心跳包
+     */
+    private void heartbeatStart(ChannelHandlerContext ctx) {
+        scheduledFuture = ctx.executor().scheduleAtFixedRate(() -> {
+            sendHeartbeat(ctx);
+        }, getHeartbeatInitialDelay(), getHeartbeatPeriod(), TimeUnit.SECONDS);
+    }
+
+    /**
+     * 取消发送心跳包
+     */
+    private void heartbeatCancel() {
+        if (null != scheduledFuture && !scheduledFuture.isCancelled()) {
+            scheduledFuture.cancel(true);
+            scheduledFuture = null;
+        }
+    }
+
+    protected abstract void sendHeartbeat(ChannelHandlerContext ctx);
+
+    public abstract void sendAuthRequest(Channel channel);
+
+    protected abstract long getHeartbeatPeriod();
+
+    protected abstract long getHeartbeatInitialDelay();
+
+    private void handshakeSuccessfully(ChannelHandlerContext ctx, FullHttpResponse msg) {
+        if (log.isDebugEnabled()) {
+            log.debug("握手完成!");
+        }
+        this.handshaker.finishHandshake(ctx.channel(), msg);
+        this.handshakeFuture.setSuccess();
+    }
+
+    private void handshakeFailed(FullHttpResponse msg, WebSocketHandshakeException e) {
+        log.error("握手失败!status:" + msg.status(), e);
+        this.handshakeFuture.setFailure(e);
+        if (listener != null) {
+            this.listener.onConnectFailed((ConnectionHandler) this);
+        }
+    }
+
+    @Override
+    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
+        log.error("exceptionCaught", cause);
+        if (!this.handshakeFuture.isDone()) {
+            this.handshakeFuture.setFailure(cause);
+        }
+        ctx.close();
+    }
+}

+ 47 - 0
ruoyi-common/ruoyi-common-live/live-chat-client-servers/pom.xml

@@ -0,0 +1,47 @@
+<!--
+  ~ MIT License
+  ~
+  ~ Copyright (c) 2023 OrdinaryRoad
+  ~
+  ~ Permission is hereby granted, free of charge, to any person obtaining a copy
+  ~ of this software and associated documentation files (the "Software"), to deal
+  ~ in the Software without restriction, including without limitation the rights
+  ~ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+  ~ copies of the Software, and to permit persons to whom the Software is
+  ~ furnished to do so, subject to the following conditions:
+  ~
+  ~ The above copyright notice and this permission notice shall be included in all
+  ~ copies or substantial portions of the Software.
+  ~
+  ~ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+  ~ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+  ~ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+  ~ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+  ~ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+  ~ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+  ~ SOFTWARE.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.ruoyi</groupId>
+        <artifactId>ruoyi-common-live</artifactId>
+        <version>${revision}</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+    <packaging>pom</packaging>
+
+    <artifactId>live-chat-client-servers</artifactId>
+    <name>live-chat-client-servers</name>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <modules>
+        <module>live-chat-client-servers-netty</module>
+        <module>live-chat-client-servers-netty-client</module>
+    </modules>
+</project>

+ 71 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/pom.xml

@@ -0,0 +1,71 @@
+<!--
+  ~ MIT License
+  ~
+  ~ Copyright (c) 2023 OrdinaryRoad
+  ~
+  ~ Permission is hereby granted, free of charge, to any person obtaining a copy
+  ~ of this software and associated documentation files (the "Software"), to deal
+  ~ in the Software without restriction, including without limitation the rights
+  ~ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+  ~ copies of the Software, and to permit persons to whom the Software is
+  ~ furnished to do so, subject to the following conditions:
+  ~
+  ~ The above copyright notice and this permission notice shall be included in all
+  ~ copies or substantial portions of the Software.
+  ~
+  ~ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+  ~ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+  ~ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+  ~ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+  ~ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+  ~ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+  ~ SOFTWARE.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.ruoyi</groupId>
+        <artifactId>live-chat-clients</artifactId>
+        <version>${revision}</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+    <packaging>jar</packaging>
+
+    <artifactId>live-chat-client-bilibili</artifactId>
+    <name>live-chat-client-bilibili</name>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.ruoyi</groupId>
+            <artifactId>live-chat-client-servers-netty-client</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.aayushatharva.brotli4j</groupId>
+            <artifactId>brotli4j</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.google.protobuf</groupId>
+            <artifactId>protobuf-java-util</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-classic</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter</artifactId>
+            <version>${junit-jupiter.version}</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>

+ 247 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/api/BilibiliApis.java

@@ -0,0 +1,247 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.bilibili.api;
+
+import cn.hutool.cache.impl.TimedCache;
+import cn.hutool.core.bean.BeanUtil;
+import cn.hutool.core.thread.ThreadUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.http.HttpRequest;
+import cn.hutool.http.HttpResponse;
+import cn.hutool.http.HttpUtil;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import lombok.*;
+import lombok.extern.slf4j.Slf4j;
+import tech.ordinaryroad.live.chat.client.bilibili.api.request.BilibiliLikeReportV3Request;
+import tech.ordinaryroad.live.chat.client.bilibili.api.request.BilibiliSendMsgRequest;
+import tech.ordinaryroad.live.chat.client.commons.base.exception.BaseException;
+import tech.ordinaryroad.live.chat.client.commons.util.OrLiveChatCookieUtil;
+
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+import static tech.ordinaryroad.live.chat.client.commons.base.msg.BaseMsg.OBJECT_MAPPER;
+
+/**
+ * B站API简易版
+ *
+ * @author mjz
+ * @date 2023/5/5
+ */
+@Slf4j
+public class BilibiliApis {
+
+    public static final TimedCache<Long, String> GIFT_IMG_CACHE = new TimedCache<>(TimeUnit.DAYS.toMillis(1));
+    public static final String KEY_COOKIE_CSRF = "bili_jct";
+    public static final String KEY_UID = "DedeUserID";
+
+    @SneakyThrows
+    public static RoomInitResult roomInit(long roomId, String cookie) {
+        @Cleanup
+        HttpResponse response = createGetRequest("https://api.live.bilibili.com/room/v1/Room/room_init?id=" + roomId, cookie).execute();
+        JsonNode dataJsonNode = responseInterceptor(response.body());
+        return OBJECT_MAPPER.readValue(dataJsonNode.toString(), RoomInitResult.class);
+    }
+
+    public static JsonNode roomGiftConfig(long roomId, String cookie) {
+        @Cleanup
+        HttpResponse response = createGetRequest("https://api.live.bilibili.com/xlive/web-room/v1/giftPanel/roomGiftConfig?platform=pc&source=live&build=0&global_version=0&room_id=" + roomId, cookie).execute();
+        return responseInterceptor(response.body());
+    }
+
+    /**
+     * @param roomId
+     * @param type   直播间用0
+     * @return <pre>{@code
+     * {
+     * 	"group": "live",
+     * 	"business_id": 0,
+     * 	"refresh_row_factor": 0.125,
+     * 	"refresh_rate": 100,
+     * 	"max_delay": 5000,
+     * 	"token": "-wm5-Qo4BBAztd1qp5ZJpgyTMRBhCc7yikz5d9rAd63PV46G9BMwl0R10kMM8Ilb-UieZGjLtipPrz4Cvi0DdhGFwOi8PJpFN9K-LoXh6Z_4yjEIwgRerDiMIstHzJ80J3B7wnRisAYkWA==",
+     * 	"host_list": [{
+     * 		"host": "ali-bj-live-comet-09.chat.bilibili.com",
+     * 		"port": 2243,
+     * 		"wss_port": 443,
+     * 		"ws_port": 2244
+     *        }, {
+     * 		"host": "ali-gz-live-comet-02.chat.bilibili.com",
+     * 		"port": 2243,
+     * 		"wss_port": 443,
+     * 		"ws_port": 2244
+     *    }, {
+     * 		"host": "broadcastlv.chat.bilibili.com",
+     * 		"port": 2243,
+     * 		"wss_port": 443,
+     * 		"ws_port": 2244
+     *    }]
+     * }
+     * }</pre>
+     */
+    public static JsonNode getDanmuInfo(long roomId, int type, String cookie) {
+        @Cleanup
+        HttpResponse response = createGetRequest("https://api.live.bilibili.com/xlive/web-room/v1/index/getDanmuInfo?id=" + roomId + "&type=" + type, cookie).execute();
+        return responseInterceptor(response.body());
+    }
+
+    public static String getGiftImgById(long giftId, long roomId) {
+        if (!GIFT_IMG_CACHE.containsKey(giftId)) {
+            ThreadUtil.execAsync(() -> {
+                updateGiftImgCache(roomId, null);
+            });
+        }
+
+        return GIFT_IMG_CACHE.get(giftId);
+    }
+
+    /**
+     * 更新礼物图片缓存
+     */
+    public static void updateGiftImgCache(long roomId, String cookie) {
+        JsonNode jsonNode = roomGiftConfig(roomId, cookie);
+        for (JsonNode node : jsonNode.get("global_gift").get("list")) {
+            long giftId = node.get("id").asLong();
+            String giftImgUrl = node.get("webp").asText();
+            GIFT_IMG_CACHE.put(giftId, giftImgUrl);
+        }
+    }
+
+    /**
+     * 发送弹幕
+     *
+     * @param request {@link BilibiliSendMsgRequest}
+     * @param cookie  Cookie
+     */
+    public static void sendMsg(BilibiliSendMsgRequest request, String cookie) {
+        if (StrUtil.isBlank(cookie)) {
+            throw new BaseException("发送弹幕接口cookie不能为空");
+        }
+        Map<String, Object> stringObjectMap = BeanUtil.beanToMap(request);
+        @Cleanup HttpResponse execute = HttpUtil.createPost("https://api.live.bilibili.com/msg/send")
+                .cookie(cookie)
+                .form(stringObjectMap)
+                .execute();
+        responseInterceptor(execute.body());
+    }
+
+    /**
+     * 发送弹幕
+     *
+     * @param msg        内容
+     * @param realRoomId 真实房间id
+     * @param cookie     Cookie
+     */
+    public static void sendMsg(String msg, long realRoomId, String cookie) {
+        String biliJct = OrLiveChatCookieUtil.getCookieByName(cookie, KEY_COOKIE_CSRF, () -> {
+            throw new BaseException("cookie中缺少参数" + KEY_COOKIE_CSRF);
+        });
+        BilibiliSendMsgRequest request = new BilibiliSendMsgRequest(msg, StrUtil.toString(ZonedDateTime.now(ZoneId.of("Asia/Shanghai")).toEpochSecond()), realRoomId, biliJct, biliJct);
+        sendMsg(request, cookie);
+    }
+
+    /**
+     * 为主播点赞
+     *
+     * @param request {@link BilibiliLikeReportV3Request}
+     * @param cookie  Cookie
+     */
+    public static void likeReportV3(BilibiliLikeReportV3Request request, String cookie) {
+        if (StrUtil.isBlank(cookie)) {
+            throw new BaseException("为主播点赞接口cookie不能为空");
+        }
+        Map<String, Object> stringObjectMap = BeanUtil.beanToMap(request);
+        @Cleanup HttpResponse execute = HttpUtil.createPost("https://api.live.bilibili.com/xlive/app-ucenter/v1/like_info_v3/like/likeReportV3")
+                .cookie(cookie)
+                .form(stringObjectMap)
+                .execute();
+        responseInterceptor(execute.body());
+    }
+
+    /**
+     * 为主播点赞
+     *
+     * @param anchor_id  主播Uid {@link RoomInitResult#uid}
+     * @param realRoomId 真实房间Id {@link RoomInitResult#room_id}
+     * @param cookie     Cookie
+     */
+    public static void likeReportV3(long anchor_id, long realRoomId, String cookie) {
+        String uid = OrLiveChatCookieUtil.getCookieByName(cookie, KEY_UID, () -> {
+            throw new BaseException("cookie中缺少参数" + KEY_UID);
+        });
+        String biliJct = OrLiveChatCookieUtil.getCookieByName(cookie, KEY_COOKIE_CSRF, () -> {
+            throw new BaseException("cookie中缺少参数" + KEY_COOKIE_CSRF);
+        });
+        BilibiliLikeReportV3Request request = new BilibiliLikeReportV3Request(realRoomId, uid, anchor_id, biliJct, biliJct);
+        likeReportV3(request, cookie);
+    }
+
+    public static HttpRequest createGetRequest(String url, String cookies) {
+        return HttpUtil.createGet(url)
+                .cookie(cookies);
+    }
+
+    private static JsonNode responseInterceptor(String responseString) {
+        try {
+            JsonNode jsonNode = OBJECT_MAPPER.readTree(responseString);
+            int code = jsonNode.get("code").asInt();
+            if (code == 0) {
+                // 成功
+                return jsonNode.get("data");
+            } else {
+                throw new BaseException(jsonNode.get("message").asText());
+            }
+        } catch (JsonProcessingException e) {
+            throw new BaseException(e);
+        }
+    }
+
+    @Data
+    @AllArgsConstructor
+    @NoArgsConstructor
+    @Builder
+    public static class RoomInitResult {
+        private long room_id;
+        private int short_id;
+        private long uid;
+        private int need_p2p;
+        private boolean is_hidden;
+        private boolean is_locked;
+        private boolean is_portrait;
+        private int live_status;
+        private int hidden_till;
+        private int lock_till;
+        private boolean encrypted;
+        private boolean pwd_verified;
+        private long live_time;
+        private int room_shield;
+        private int is_sp;
+        private int special_type;
+    }
+
+}

+ 74 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/api/request/BilibiliLikeReportV3Request.java

@@ -0,0 +1,74 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.bilibili.api.request;
+
+import cn.hutool.core.util.StrUtil;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author mjz
+ * @date 2024/1/31
+ */
+@Data
+@NoArgsConstructor
+public class BilibiliLikeReportV3Request {
+    /**
+     * 本次点赞次数
+     */
+    private int click_time = 1;
+    /**
+     * 房间真实ID
+     */
+    private long room_id;
+    /**
+     * Cookie中的DedeUserID
+     */
+    private String uid;
+    /**
+     * RoomInitResult中的uid
+     */
+    private long anchor_id;
+    /**
+     * Cookie中的bili_jct
+     */
+    private String csrf;
+    /**
+     * Cookie中的bili_jct
+     */
+    private String csrf_token;
+    /**
+     * 暂时留空
+     */
+    private String visit_id = StrUtil.EMPTY;
+
+    public BilibiliLikeReportV3Request(long room_id, String uid, long anchor_id, String csrf, String csrf_token) {
+        this.room_id = room_id;
+        this.uid = uid;
+        this.anchor_id = anchor_id;
+        this.csrf = csrf;
+        this.csrf_token = csrf_token;
+    }
+}

+ 77 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/api/request/BilibiliSendMsgRequest.java

@@ -0,0 +1,77 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.bilibili.api.request;
+
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author mjz
+ * @date 2023/9/7
+ */
+@Data
+@NoArgsConstructor
+public class BilibiliSendMsgRequest {
+    private String bubble = "0";
+    /**
+     * 弹幕内容
+     */
+    private String msg;
+    /**
+     * 弹幕颜色
+     */
+    private String color = "16777215";
+    private String mode = "1";
+    private String room_type = "0";
+    private String jumpfrom = "0";
+    /**
+     * 字体大小
+     */
+    private String fontsize = "25";
+    /**
+     * 时间戳(秒)
+     */
+    private String rnd;
+    /**
+     * 房间真实ID
+     */
+    private long roomid;
+    /**
+     * Cookie中的bili_jct
+     */
+    private String csrf;
+    /**
+     * Cookie中的bili_jct
+     */
+    private String csrf_token;
+
+    public BilibiliSendMsgRequest(String msg, String rnd, long roomid, String csrf, String csrf_token) {
+        this.msg = msg;
+        this.rnd = rnd;
+        this.roomid = roomid;
+        this.csrf = csrf;
+        this.csrf_token = csrf_token;
+    }
+}

+ 184 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/client/BilibiliLiveChatClient.java

@@ -0,0 +1,184 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.bilibili.client;
+
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.handler.codec.http.DefaultHttpHeaders;
+import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory;
+import io.netty.handler.codec.http.websocketx.WebSocketVersion;
+import lombok.extern.slf4j.Slf4j;
+import tech.ordinaryroad.live.chat.client.bilibili.api.BilibiliApis;
+import tech.ordinaryroad.live.chat.client.bilibili.config.BilibiliLiveChatClientConfig;
+import tech.ordinaryroad.live.chat.client.bilibili.constant.BilibiliCmdEnum;
+import tech.ordinaryroad.live.chat.client.bilibili.listener.IBilibiliConnectionListener;
+import tech.ordinaryroad.live.chat.client.bilibili.listener.IBilibiliMsgListener;
+import tech.ordinaryroad.live.chat.client.bilibili.msg.base.IBilibiliMsg;
+import tech.ordinaryroad.live.chat.client.bilibili.netty.handler.BilibiliBinaryFrameHandler;
+import tech.ordinaryroad.live.chat.client.bilibili.netty.handler.BilibiliConnectionHandler;
+import tech.ordinaryroad.live.chat.client.commons.base.exception.BaseException;
+import tech.ordinaryroad.live.chat.client.commons.base.listener.IBaseConnectionListener;
+import tech.ordinaryroad.live.chat.client.servers.netty.client.base.BaseNettyClient;
+
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * B站直播间弹幕客户端
+ *
+ * @author mjz
+ * @date 2023/8/20
+ */
+@Slf4j
+public class BilibiliLiveChatClient extends BaseNettyClient<
+        BilibiliLiveChatClientConfig,
+        BilibiliCmdEnum,
+        IBilibiliMsg,
+        IBilibiliMsgListener,
+        BilibiliConnectionHandler,
+        BilibiliBinaryFrameHandler
+        > {
+
+    private BilibiliApis.RoomInitResult roomInitResult = new BilibiliApis.RoomInitResult();
+
+    public BilibiliLiveChatClient(BilibiliLiveChatClientConfig config, List<IBilibiliMsgListener> msgListeners, IBilibiliConnectionListener connectionListener, EventLoopGroup workerGroup) {
+        super(config, workerGroup, connectionListener);
+        addMsgListeners(msgListeners);
+
+        // 初始化
+        this.init();
+    }
+
+    public BilibiliLiveChatClient(BilibiliLiveChatClientConfig config, IBilibiliMsgListener msgListener, IBilibiliConnectionListener connectionListener, EventLoopGroup workerGroup) {
+        super(config, workerGroup, connectionListener);
+        addMsgListener(msgListener);
+
+        // 初始化
+        this.init();
+    }
+
+    public BilibiliLiveChatClient(BilibiliLiveChatClientConfig config, IBilibiliMsgListener msgListener, IBilibiliConnectionListener connectionListener) {
+        this(config, msgListener, connectionListener, new NioEventLoopGroup());
+    }
+
+    public BilibiliLiveChatClient(BilibiliLiveChatClientConfig config, IBilibiliMsgListener msgListener) {
+        this(config, msgListener, null, new NioEventLoopGroup());
+    }
+
+    public BilibiliLiveChatClient(BilibiliLiveChatClientConfig config) {
+        this(config, null);
+    }
+
+    @Override
+    public void init() {
+        roomInitResult = BilibiliApis.roomInit(getConfig().getRoomId(), getConfig().getCookie());
+        super.init();
+    }
+
+    @Override
+    public BilibiliConnectionHandler initConnectionHandler(IBaseConnectionListener<BilibiliConnectionHandler> clientConnectionListener) {
+        return new BilibiliConnectionHandler(
+                WebSocketClientHandshakerFactory.newHandshaker(getWebsocketUri(), WebSocketVersion.V13, null, true, new DefaultHttpHeaders(), getConfig().getMaxFramePayloadLength()),
+                BilibiliLiveChatClient.this, clientConnectionListener
+        );
+    }
+
+    @Override
+    public BilibiliBinaryFrameHandler initBinaryFrameHandler() {
+        return new BilibiliBinaryFrameHandler(super.msgListeners, BilibiliLiveChatClient.this);
+    }
+
+    @Override
+    public void sendDanmu(Object danmu, Runnable success, Consumer<Throwable> failed) {
+        if (!checkCanSendDanmu(false)) {
+            return;
+        }
+        if (danmu instanceof String) {
+            String msg = (String) danmu;
+            try {
+                if (log.isDebugEnabled()) {
+                    log.debug("{} bilibili发送弹幕 {}", getConfig().getRoomId(), danmu);
+                }
+
+                boolean sendSuccess = false;
+                try {
+                    BilibiliApis.sendMsg(msg, roomInitResult.getRoom_id(), getConfig().getCookie());
+                    sendSuccess = true;
+                } catch (Exception e) {
+                    log.error("bilibili弹幕发送失败", e);
+                    if (failed != null) {
+                        failed.accept(e);
+                    }
+                }
+                if (!sendSuccess) {
+                    return;
+                }
+
+                if (log.isDebugEnabled()) {
+                    log.debug("bilibili弹幕发送成功 {}", danmu);
+                }
+                if (success != null) {
+                    success.run();
+                }
+                finishSendDanmu();
+            } catch (Exception e) {
+                log.error("bilibili弹幕发送失败", e);
+                if (failed != null) {
+                    failed.accept(e);
+                }
+            }
+        } else {
+            super.sendDanmu(danmu, success, failed);
+        }
+    }
+
+    @Override
+    public void clickLike(int count, Runnable success, Consumer<Throwable> failed) {
+        if (count <= 0) {
+            throw new BaseException("点赞次数必须大于0");
+        }
+
+        boolean successfullyClicked = false;
+        try {
+            BilibiliApis.likeReportV3(roomInitResult.getUid(), roomInitResult.getRoom_id(), getConfig().getCookie());
+            successfullyClicked = true;
+        } catch (Exception e) {
+            log.error("Bilibili为直播间点赞失败", e);
+            if (failed != null) {
+                failed.accept(e);
+            }
+        }
+        if (!successfullyClicked) {
+            return;
+        }
+
+        if (log.isDebugEnabled()) {
+            log.debug("Bilibili为直播间点赞成功");
+        }
+        if (success != null) {
+            success.run();
+        }
+    }
+}

+ 67 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/config/BilibiliLiveChatClientConfig.java

@@ -0,0 +1,67 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.bilibili.config;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+import tech.ordinaryroad.live.chat.client.bilibili.constant.ProtoverEnum;
+import tech.ordinaryroad.live.chat.client.commons.util.OrLiveChatNumberUtil;
+import tech.ordinaryroad.live.chat.client.servers.netty.client.config.BaseNettyClientConfig;
+
+/**
+ * B站直播间弹幕客户端配置
+ *
+ * @author mjz
+ * @date 2023/8/21
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@SuperBuilder(toBuilder = true)
+public class BilibiliLiveChatClientConfig extends BaseNettyClientConfig {
+
+    /**
+     * @see ProtoverEnum
+     */
+    @Builder.Default
+    private ProtoverEnum protover = ProtoverEnum.NORMAL_ZLIB;
+
+    @Builder.Default
+    private String websocketUri = "wss://broadcastlv.chat.bilibili.com:443/sub";
+
+    @Override
+    public Long getRoomId() {
+        return OrLiveChatNumberUtil.parseLong(super.getRoomId());
+    }
+
+    public void setProtover(ProtoverEnum protover) {
+        ProtoverEnum oldValue = this.protover;
+        this.protover = protover;
+        super.propertyChangeSupport.firePropertyChange("protover", oldValue, protover);
+    }
+}

+ 143 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/constant/BilibiliCmdEnum.java

@@ -0,0 +1,143 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.bilibili.constant;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+/**
+ * @author mjz
+ * @date 2023/1/6
+ */
+@Getter
+@RequiredArgsConstructor
+public enum BilibiliCmdEnum {
+    /**
+     * 游客状态下,5分钟后会出现登录提示,弹幕中的用户名、用户id等信息将不再可见
+     */
+    LOG_IN_NOTICE,
+    /**
+     * 收到弹幕
+     */
+    DANMU_MSG,
+    /**
+     * 收到礼物
+     */
+    SEND_GIFT,
+    /**
+     * 有人上舰
+     */
+    GUARD_BUY,
+    /**
+     * 欢迎舰长
+     */
+    WELCOME_GUARD,
+    WELCOME,
+    /**
+     * 礼物连击
+     */
+    COMBO_SEND,
+    /**
+     * 欢迎高能用户、(舰长?待验证)特殊消息
+     */
+    ENTRY_EFFECT,
+    HOT_RANK_CHANGED,
+    HOT_RANK_CHANGED_V2,
+    INTERACT_WORD,
+    /**
+     * 开始直播
+     */
+    LIVE,
+    LIVE_INTERACTIVE_GAME,
+    NOTICE_MSG,
+    /**
+     * 高能榜数量更新
+     */
+    ONLINE_RANK_COUNT,
+    ONLINE_RANK_TOP3,
+    ONLINE_RANK_V2,
+    PK_BATTLE_END,
+    PK_BATTLE_FINAL_PROCESS,
+    PK_BATTLE_PROCESS,
+    PK_BATTLE_PROCESS_NEW,
+    PK_BATTLE_SETTLE,
+    PK_BATTLE_SETTLE_USER,
+    PK_BATTLE_SETTLE_V2,
+    /**
+     * 主播准备中
+     */
+    PREPARING,
+    ROOM_REAL_TIME_MESSAGE_UPDATE,
+    /**
+     * 停止直播的房间ID列表
+     */
+    STOP_LIVE_ROOM_LIST,
+    /**
+     * 醒目留言
+     */
+    SUPER_CHAT_MESSAGE,
+    SUPER_CHAT_MESSAGE_JPN,
+    /**
+     * 删除醒目留言
+     */
+    SUPER_CHAT_MESSAGE_DELETE,
+    WIDGET_BANNER,
+    /**
+     * 点赞数更新
+     */
+    LIKE_INFO_V3_UPDATE,
+    /**
+     * 为主播点赞
+     */
+    LIKE_INFO_V3_CLICK,
+    HOT_ROOM_NOTIFY,
+    /**
+     * 观看人数变化
+     */
+    WATCHED_CHANGE,
+    POPULAR_RANK_CHANGED,
+    COMMON_NOTICE_DANMAKU,
+    LIVE_MULTI_VIEW_CHANGE,
+    RECOMMEND_CARD,
+    PK_BATTLE_START_NEW,
+    PK_BATTLE_ENTRANCE,
+    AREA_RANK_CHANGED,
+    ROOM_BLOCK_MSG,
+    USER_TOAST_MSG,
+    PK_BATTLE_PRE_NEW,
+    PK_BATTLE_RANK_CHANGE,
+    PK_BATTLE_START,
+    PK_BATTLE_PRE,
+    PLAY_TAG,
+    ;
+
+    public static BilibiliCmdEnum getByString(String cmd) {
+        try {
+            return BilibiliCmdEnum.valueOf(cmd);
+        } catch (Exception e) {
+            return null;
+        }
+    }
+}

+ 86 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/constant/OperationEnum.java

@@ -0,0 +1,86 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.bilibili.constant;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+/**
+ * @author mjz
+ * @date 2023/1/5
+ */
+@Getter
+@RequiredArgsConstructor
+public enum OperationEnum {
+    HANDSHAKE(0),
+    HANDSHAKE_REPLY(1),
+    /**
+     * 心跳包
+     */
+    HEARTBEAT(2),
+    /**
+     * 心跳包回复(人气值)
+     */
+    HEARTBEAT_REPLY(3),
+    SEND_MSG(4),
+
+    /**
+     * 普通包(命令)
+     */
+    SEND_SMS_REPLY(5),
+    DISCONNECT_REPLY(6),
+
+    /**
+     * 认证包
+     */
+    AUTH(7),
+
+    /**
+     * 认证包回复
+     */
+    AUTH_REPLY(8),
+    RAW(9),
+    PROTO_READY(10),
+    PROTO_FINISH(11),
+    CHANGE_ROOM(12),
+    CHANGE_ROOM_REPLY(13),
+    REGISTER(14),
+    REGISTER_REPLY(15),
+    UNREGISTER(16),
+    UNREGISTER_REPLY(17),
+    ;
+
+    private final int code;
+
+    public static OperationEnum getByCode(int code) {
+        for (OperationEnum value : OperationEnum.values()) {
+            if (value.code == code) {
+                return value;
+            }
+        }
+        return null;
+    }
+
+}

+ 67 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/constant/ProtoverEnum.java

@@ -0,0 +1,67 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.bilibili.constant;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+/**
+ * @author mjz
+ * @date 2023/1/5
+ */
+@Getter
+@RequiredArgsConstructor
+public enum ProtoverEnum {
+    /**
+     * 普通包正文不使用压缩
+     */
+    NORMAL_NO_COMPRESSION(0),
+    /**
+     * 心跳及认证包正文不使用压缩
+     */
+    HEARTBEAT_AUTH_NO_COMPRESSION(1),
+    /**
+     * 普通包正文使用zlib压缩
+     */
+    NORMAL_ZLIB(2),
+    /**
+     * 普通包正文使用brotli压缩,解压为一个带头部的协议0普通包
+     */
+    NORMAL_BROTLI(3),
+    ;
+
+    private final int code;
+
+
+    public static ProtoverEnum getByCode(int code) {
+        for (ProtoverEnum value : ProtoverEnum.values()) {
+            if (value.code == code) {
+                return value;
+            }
+        }
+        return null;
+    }
+
+}

+ 38 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/listener/IBilibiliConnectionListener.java

@@ -0,0 +1,38 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.bilibili.listener;
+
+
+import tech.ordinaryroad.live.chat.client.bilibili.netty.handler.BilibiliConnectionHandler;
+import tech.ordinaryroad.live.chat.client.commons.base.listener.IBaseConnectionListener;
+
+/**
+ * 连接回调
+ *
+ * @author mjz
+ * @date 2023/8/21
+ */
+public interface IBilibiliConnectionListener extends IBaseConnectionListener<BilibiliConnectionHandler> {
+}

+ 138 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/listener/IBilibiliMsgListener.java

@@ -0,0 +1,138 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.bilibili.listener;
+
+import tech.ordinaryroad.live.chat.client.bilibili.constant.BilibiliCmdEnum;
+import tech.ordinaryroad.live.chat.client.bilibili.msg.*;
+import tech.ordinaryroad.live.chat.client.bilibili.netty.handler.BilibiliBinaryFrameHandler;
+import tech.ordinaryroad.live.chat.client.commons.base.listener.*;
+
+/**
+ * @author mjz
+ * @date 2023/1/7
+ */
+public interface IBilibiliMsgListener extends IBaseMsgListener<BilibiliBinaryFrameHandler, BilibiliCmdEnum>,
+        IDanmuMsgListener<BilibiliBinaryFrameHandler, DanmuMsgMsg>,
+        IGiftMsgListener<BilibiliBinaryFrameHandler, SendGiftMsg>,
+        ISuperChatMsgListener<BilibiliBinaryFrameHandler, SuperChatMessageMsg>,
+        IEnterRoomMsgListener<BilibiliBinaryFrameHandler, InteractWordMsg>,
+        ILikeMsgListener<BilibiliBinaryFrameHandler, LikeInfoV3ClickMsg> {
+
+    /**
+     * 收到礼物
+     *
+     * @param binaryFrameHandler BilibiliBinaryFrameHandler
+     * @param msg                SendSmsReplyMsg
+     * @deprecated use {@link IGiftMsgListener#onGiftMsg(Object, Object)}
+     */
+    default void onSendGift(BilibiliBinaryFrameHandler binaryFrameHandler, SendSmsReplyMsg msg) {
+        this.onSendGift(msg);
+    }
+
+    /**
+     * @deprecated use {@link IGiftMsgListener#onGiftMsg(Object)}
+     */
+    default void onSendGift(SendSmsReplyMsg msg) {
+        // ignore
+    }
+
+    /**
+     * 普通用户进入直播间
+     *
+     * @param binaryFrameHandler BilibiliBinaryFrameHandler
+     * @param msg                SendSmsReplyMsg
+     * @deprecated use {@link IEnterRoomMsgListener#onEnterRoomMsg}
+     */
+    default void onEnterRoom(BilibiliBinaryFrameHandler binaryFrameHandler, SendSmsReplyMsg msg) {
+        this.onEnterRoom(msg);
+    }
+
+    /**
+     * @deprecated use {@link IEnterRoomMsgListener#onEnterRoomMsg}
+     */
+    default void onEnterRoom(SendSmsReplyMsg msg) {
+        // ignore
+    }
+
+    /**
+     * 入场效果(高能用户)
+     *
+     * @param binaryFrameHandler BilibiliBinaryFrameHandler
+     * @param sendSmsReplyMsg    SendSmsReplyMsg
+     */
+    default void onEntryEffect(BilibiliBinaryFrameHandler binaryFrameHandler, SendSmsReplyMsg sendSmsReplyMsg) {
+        this.onEntryEffect(sendSmsReplyMsg);
+    }
+
+    default void onEntryEffect(SendSmsReplyMsg sendSmsReplyMsg) {
+        // ignore
+    }
+
+    /**
+     * 观看人数变化
+     *
+     * @param binaryFrameHandler BilibiliBinaryFrameHandler
+     * @param msg                SendSmsReplyMsg
+     */
+    default void onWatchedChange(BilibiliBinaryFrameHandler binaryFrameHandler, SendSmsReplyMsg msg) {
+        this.onWatchedChange(msg);
+    }
+
+    default void onWatchedChange(SendSmsReplyMsg msg) {
+        // ignore
+    }
+
+    /**
+     * 为主播点赞
+     *
+     * @param binaryFrameHandler BilibiliBinaryFrameHandler
+     * @param msg                SendSmsReplyMsg
+     * @deprecated use {@link ILikeMsgListener#onLikeMsg}
+     */
+    default void onClickLike(BilibiliBinaryFrameHandler binaryFrameHandler, SendSmsReplyMsg msg) {
+        this.onClickLike(msg);
+    }
+
+    /**
+     * @deprecated use {@link ILikeMsgListener#onLikeMsg}
+     */
+    default void onClickLike(SendSmsReplyMsg msg) {
+        // ignore
+    }
+
+    /**
+     * 点赞数更新
+     *
+     * @param binaryFrameHandler BilibiliBinaryFrameHandler
+     * @param msg                SendSmsReplyMsg
+     */
+    default void onClickUpdate(BilibiliBinaryFrameHandler binaryFrameHandler, SendSmsReplyMsg msg) {
+        this.onClickUpdate(msg);
+    }
+
+    default void onClickUpdate(SendSmsReplyMsg msg) {
+        // ignore
+    }
+}

+ 92 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/msg/AuthMsg.java

@@ -0,0 +1,92 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.bilibili.msg;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.Setter;
+import tech.ordinaryroad.live.chat.client.bilibili.constant.OperationEnum;
+import tech.ordinaryroad.live.chat.client.bilibili.constant.ProtoverEnum;
+import tech.ordinaryroad.live.chat.client.bilibili.msg.base.BaseBilibiliMsg;
+
+/**
+ * @author mjz
+ * @date 2023/1/6
+ */
+@Getter
+@Setter
+@RequiredArgsConstructor
+public class AuthMsg extends BaseBilibiliMsg {
+
+    /**
+     * 用户uid,0代表游客
+     */
+    private long uid;
+
+    /**
+     * 房间id room_id,不是短id short_id
+     * 可以通过将url参数id改为直播地址中的数字来查询房间真实id
+     * example: <a href="https://api.live.bilibili.com/room/v1/Room/room_init?id=6">https://api.live.bilibili.com/room/v1/Room/room_init?id=6</a>
+     */
+    private final long roomid;
+
+    /**
+     * 协议版本
+     *
+     * @see ProtoverEnum#getCode()
+     */
+    private final int protover;
+
+    /**
+     * 平台标识
+     */
+    private String platform = "web";
+    private int type = 2;
+
+    /**
+     * 必须字段
+     *
+     * @since 2023-08-19
+     */
+    private final String buvid;
+
+    /**
+     * 认证秘钥(必须字段)
+     *
+     * @since @since 2023-08-19
+     */
+    private final String key;
+
+    @Override
+    public ProtoverEnum getProtoverEnum() {
+        return ProtoverEnum.getByCode(this.protover);
+    }
+
+    @Override
+    public OperationEnum getOperationEnum() {
+        return OperationEnum.AUTH;
+    }
+
+}

+ 63 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/msg/AuthReplyMsg.java

@@ -0,0 +1,63 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.bilibili.msg;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import tech.ordinaryroad.live.chat.client.bilibili.constant.OperationEnum;
+import tech.ordinaryroad.live.chat.client.bilibili.constant.ProtoverEnum;
+import tech.ordinaryroad.live.chat.client.bilibili.msg.base.BaseBilibiliMsg;
+
+/**
+ * @author mjz
+ * @date 2023/1/6
+ */
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+public class AuthReplyMsg extends BaseBilibiliMsg {
+
+    /**
+     * 0: OK,-101: TOKEN_ERROR
+     */
+    private int code;
+
+    @JsonIgnore
+    private int protover;
+
+    @Override
+    public ProtoverEnum getProtoverEnum() {
+        return ProtoverEnum.getByCode(protover);
+    }
+
+    @Override
+    public OperationEnum getOperationEnum() {
+        return OperationEnum.AUTH_REPLY;
+    }
+}

+ 102 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/msg/DanmuMsgMsg.java

@@ -0,0 +1,102 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.bilibili.msg;
+
+import cn.hutool.core.codec.Base64;
+import com.fasterxml.jackson.databind.JsonNode;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import tech.ordinaryroad.live.chat.client.bilibili.constant.OperationEnum;
+import tech.ordinaryroad.live.chat.client.bilibili.msg.base.BaseBilibiliMsg;
+import tech.ordinaryroad.live.chat.client.commons.base.msg.IDanmuMsg;
+
+/**
+ * @author mjz
+ * @date 2023/9/8
+ */
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+public class DanmuMsgMsg extends BaseBilibiliMsg implements IDanmuMsg {
+
+    private JsonNode info;
+    private String dm_v2;
+
+    @Override
+    public OperationEnum getOperationEnum() {
+        return OperationEnum.SEND_SMS_REPLY;
+    }
+
+    @Override
+    public String getBadgeName() {
+        JsonNode jsonNode3 = info.get(3);
+        if (jsonNode3.isEmpty()) {
+            return "";
+        }
+        return jsonNode3.get(1).asText();
+    }
+
+    @Override
+    public byte getBadgeLevel() {
+        JsonNode jsonNode3 = info.get(3);
+        if (jsonNode3.isEmpty()) {
+            return 0;
+        }
+        return (byte) jsonNode3.get(0).asInt();
+    }
+
+    @Override
+    public String getUid() {
+        JsonNode jsonNode2 = info.get(2);
+        return jsonNode2.get(0).asText();
+    }
+
+    @Override
+    public String getUsername() {
+        JsonNode jsonNode2 = info.get(2);
+        return jsonNode2.get(1).asText();
+    }
+
+    @Override
+    public String getUserAvatar() {
+        String avatar = null;
+        try {
+            tech.ordinaryroad.live.chat.client.bilibili.protobuf.dm_v2 dmV2 = tech.ordinaryroad.live.chat.client.bilibili.protobuf.dm_v2.parseFrom(Base64.decode(dm_v2));
+            avatar = dmV2.getDmV220().getAvatar();
+        } catch (Exception e) {
+            // ignore
+        }
+        return avatar;
+    }
+
+    @Override
+    public String getContent() {
+        JsonNode jsonNode1 = info.get(1);
+        return jsonNode1.asText();
+    }
+}

+ 58 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/msg/HeartbeatMsg.java

@@ -0,0 +1,58 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.bilibili.msg;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import tech.ordinaryroad.live.chat.client.bilibili.constant.OperationEnum;
+import tech.ordinaryroad.live.chat.client.bilibili.constant.ProtoverEnum;
+import tech.ordinaryroad.live.chat.client.bilibili.msg.base.BaseBilibiliMsg;
+
+/**
+ * @author mjz
+ * @date 2023/1/6
+ */
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+public class HeartbeatMsg extends BaseBilibiliMsg {
+
+    @JsonIgnore
+    private int protover;
+
+    @Override
+    public ProtoverEnum getProtoverEnum() {
+        return ProtoverEnum.getByCode(protover);
+    }
+
+    @Override
+    public OperationEnum getOperationEnum() {
+        return OperationEnum.HEARTBEAT;
+    }
+}

+ 60 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/msg/HeartbeatReplyMsg.java

@@ -0,0 +1,60 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.bilibili.msg;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import tech.ordinaryroad.live.chat.client.bilibili.constant.OperationEnum;
+import tech.ordinaryroad.live.chat.client.bilibili.constant.ProtoverEnum;
+import tech.ordinaryroad.live.chat.client.bilibili.msg.base.BaseBilibiliMsg;
+
+/**
+ * @author mjz
+ * @date 2023/1/6
+ */
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+public class HeartbeatReplyMsg extends BaseBilibiliMsg {
+
+    private int popularity;
+
+    @JsonIgnore
+    private int protover;
+
+    @Override
+    public ProtoverEnum getProtoverEnum() {
+        return ProtoverEnum.getByCode(protover);
+    }
+
+    @Override
+    public OperationEnum getOperationEnum() {
+        return OperationEnum.HEARTBEAT_REPLY;
+    }
+}

+ 396 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/msg/InteractWordMsg.java

@@ -0,0 +1,396 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.bilibili.msg;
+
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.databind.JsonNode;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import tech.ordinaryroad.live.chat.client.bilibili.constant.OperationEnum;
+import tech.ordinaryroad.live.chat.client.bilibili.msg.base.BaseBilibiliMsg;
+import tech.ordinaryroad.live.chat.client.commons.base.msg.IEnterRoomMsg;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author mjz
+ * @date 2023/12/26
+ */
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+public class InteractWordMsg extends BaseBilibiliMsg implements IEnterRoomMsg {
+
+    private Data data;
+
+    @Override
+    public OperationEnum getOperationEnum() {
+        return OperationEnum.SEND_SMS_REPLY;
+    }
+
+    @Override
+    public String getBadgeName() {
+        if (data == null || data.fans_medal == null) {
+            return null;
+        }
+        return data.fans_medal.medal_name;
+    }
+
+    @Override
+    public byte getBadgeLevel() {
+        if (data == null || data.fans_medal == null) {
+            return 0;
+        }
+        return data.fans_medal.medal_level;
+    }
+
+    @Override
+    public String getUid() {
+        if (data == null) {
+            return null;
+        }
+        return Long.toString(data.uid);
+    }
+
+    @Override
+    public String getUsername() {
+        if (data == null) {
+            return null;
+        }
+        return data.uname;
+    }
+
+    @Override
+    public String getUserAvatar() {
+        if (data == null || data.uinfo == null || data.uinfo.base == null) {
+            return null;
+        }
+        return data.uinfo.base.face;
+    }
+
+    @lombok.Data
+    public static class Data {
+
+        private Contribution contribution;
+        private Contribution_v2 contribution_v2;
+        private int core_user_type;
+        private int dmscore;
+        private Fans_medal fans_medal;
+        private String group_medal;
+        private List<Integer> identities;
+        private boolean is_mystery;
+        private int is_spread;
+        private int msg_type;
+        private int privilege_type;
+        private long roomid;
+        private long score;
+        private String spread_desc;
+        private String spread_info;
+        private int tail_icon;
+        private String tail_text;
+        private long timestamp;
+        private long trigger_time;
+        private long uid;
+        private Uinfo uinfo;
+        private String uname;
+        private String uname_color;
+
+        /**
+         * 未知属性都放在这
+         */
+        private final Map<String, JsonNode> unknownProperties = new HashMap<>();
+
+        @JsonAnyGetter
+        public Map<String, JsonNode> getUnknownProperties() {
+            return unknownProperties;
+        }
+
+        @JsonAnySetter
+        public void setOther(String key, JsonNode value) {
+            this.unknownProperties.put(key, value);
+        }
+    }
+
+    @lombok.Data
+    public static class Contribution {
+
+        private int grade;
+
+        /**
+         * 未知属性都放在这
+         */
+        private final Map<String, JsonNode> unknownProperties = new HashMap<>();
+
+        @JsonAnyGetter
+        public Map<String, JsonNode> getUnknownProperties() {
+            return unknownProperties;
+        }
+
+        @JsonAnySetter
+        public void setOther(String key, JsonNode value) {
+            this.unknownProperties.put(key, value);
+        }
+    }
+
+    @lombok.Data
+    public static class Contribution_v2 {
+
+        private int grade;
+        private String rank_type;
+        private String text;
+
+        /**
+         * 未知属性都放在这
+         */
+        private final Map<String, JsonNode> unknownProperties = new HashMap<>();
+
+        @JsonAnyGetter
+        public Map<String, JsonNode> getUnknownProperties() {
+            return unknownProperties;
+        }
+
+        @JsonAnySetter
+        public void setOther(String key, JsonNode value) {
+            this.unknownProperties.put(key, value);
+        }
+    }
+
+    @lombok.Data
+    public static class Fans_medal {
+
+        private long anchor_roomid;
+        private int guard_level;
+        private int icon_id;
+        private int is_lighted;
+        private long medal_color;
+        private long medal_color_border;
+        private long medal_color_end;
+        private long medal_color_start;
+        private byte medal_level;
+        private String medal_name;
+        private long score;
+        private String special;
+        private long target_id;
+
+        /**
+         * 未知属性都放在这
+         */
+        private final Map<String, JsonNode> unknownProperties = new HashMap<>();
+
+        @JsonAnyGetter
+        public Map<String, JsonNode> getUnknownProperties() {
+            return unknownProperties;
+        }
+
+        @JsonAnySetter
+        public void setOther(String key, JsonNode value) {
+            this.unknownProperties.put(key, value);
+        }
+    }
+
+    @lombok.Data
+    public static class Origin_info {
+
+        private String face;
+        private String name;
+
+        /**
+         * 未知属性都放在这
+         */
+        private final Map<String, JsonNode> unknownProperties = new HashMap<>();
+
+        @JsonAnyGetter
+        public Map<String, JsonNode> getUnknownProperties() {
+            return unknownProperties;
+        }
+
+        @JsonAnySetter
+        public void setOther(String key, JsonNode value) {
+            this.unknownProperties.put(key, value);
+        }
+    }
+
+    @lombok.Data
+    public static class Risk_ctrl_info {
+
+        private String face;
+        private String name;
+
+        /**
+         * 未知属性都放在这
+         */
+        private final Map<String, JsonNode> unknownProperties = new HashMap<>();
+
+        @JsonAnyGetter
+        public Map<String, JsonNode> getUnknownProperties() {
+            return unknownProperties;
+        }
+
+        @JsonAnySetter
+        public void setOther(String key, JsonNode value) {
+            this.unknownProperties.put(key, value);
+        }
+    }
+
+    @lombok.Data
+    public static class Official_info {
+
+        private int role;
+        private String title;
+        private String desc;
+        private int type;
+
+        /**
+         * 未知属性都放在这
+         */
+        private final Map<String, JsonNode> unknownProperties = new HashMap<>();
+
+        @JsonAnyGetter
+        public Map<String, JsonNode> getUnknownProperties() {
+            return unknownProperties;
+        }
+
+        @JsonAnySetter
+        public void setOther(String key, JsonNode value) {
+            this.unknownProperties.put(key, value);
+        }
+    }
+
+    @lombok.Data
+    public static class Base {
+
+        private String face;
+        private boolean is_mystery;
+        private String name;
+        private int name_color;
+        private Origin_info origin_info;
+        private Risk_ctrl_info risk_ctrl_info;
+        private Official_info official_info;
+
+        /**
+         * 未知属性都放在这
+         */
+        private final Map<String, JsonNode> unknownProperties = new HashMap<>();
+
+        @JsonAnyGetter
+        public Map<String, JsonNode> getUnknownProperties() {
+            return unknownProperties;
+        }
+
+        @JsonAnySetter
+        public void setOther(String key, JsonNode value) {
+            this.unknownProperties.put(key, value);
+        }
+    }
+
+    @lombok.Data
+    public static class Medal {
+
+        private String name;
+        private int level;
+        private long color_start;
+        private long color_end;
+        private long color_border;
+        private long color;
+        private int id;
+        private int typ;
+        private int is_light;
+        private long ruid;
+        private int guard_level;
+        private int score;
+        private String guard_icon;
+        private String honor_icon;
+
+        /**
+         * 未知属性都放在这
+         */
+        private final Map<String, JsonNode> unknownProperties = new HashMap<>();
+
+        @JsonAnyGetter
+        public Map<String, JsonNode> getUnknownProperties() {
+            return unknownProperties;
+        }
+
+        @JsonAnySetter
+        public void setOther(String key, JsonNode value) {
+            this.unknownProperties.put(key, value);
+        }
+    }
+
+    @lombok.Data
+    public static class Guard {
+
+        private int level;
+        private String expired_str;
+
+        /**
+         * 未知属性都放在这
+         */
+        private final Map<String, JsonNode> unknownProperties = new HashMap<>();
+
+        @JsonAnyGetter
+        public Map<String, JsonNode> getUnknownProperties() {
+            return unknownProperties;
+        }
+
+        @JsonAnySetter
+        public void setOther(String key, JsonNode value) {
+            this.unknownProperties.put(key, value);
+        }
+    }
+
+
+    @lombok.Data
+    public static class Uinfo {
+
+        private long uid;
+        private Base base;
+        private Medal medal;
+        private String wealth;
+        private String title;
+        private Guard guard;
+
+        /**
+         * 未知属性都放在这
+         */
+        private final Map<String, JsonNode> unknownProperties = new HashMap<>();
+
+        @JsonAnyGetter
+        public Map<String, JsonNode> getUnknownProperties() {
+            return unknownProperties;
+        }
+
+        @JsonAnySetter
+        public void setOther(String key, JsonNode value) {
+            this.unknownProperties.put(key, value);
+        }
+    }
+}

+ 158 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/msg/LikeInfoV3ClickMsg.java

@@ -0,0 +1,158 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.bilibili.msg;
+
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.databind.JsonNode;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import tech.ordinaryroad.live.chat.client.bilibili.constant.OperationEnum;
+import tech.ordinaryroad.live.chat.client.bilibili.msg.base.BaseBilibiliMsg;
+import tech.ordinaryroad.live.chat.client.commons.base.msg.ILikeMsg;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author mjz
+ * @date 2024/1/31
+ */
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+public class LikeInfoV3ClickMsg extends BaseBilibiliMsg implements ILikeMsg {
+
+    private Data data;
+
+    @Override
+    public OperationEnum getOperationEnum() {
+        return OperationEnum.SEND_SMS_REPLY;
+    }
+
+    @Override
+    public String getBadgeName() {
+        if (this.data == null || this.data.getFans_medal() == null) {
+            return null;
+        }
+
+        return this.data.getFans_medal().getMedal_name();
+    }
+
+    @Override
+    public byte getBadgeLevel() {
+        if (this.data == null || this.data.getFans_medal() == null) {
+            return 0;
+        }
+
+        return this.data.getFans_medal().getMedal_level();
+    }
+
+    @Override
+    public String getUid() {
+        if (this.data == null) {
+            return null;
+        }
+
+        return Long.toString(this.data.getUid());
+    }
+
+    @Override
+    public String getUsername() {
+        if (this.data == null) {
+            return "";
+        }
+
+        return this.data.getUname();
+    }
+
+    @Override
+    public String getUserAvatar() {
+        if (this.data == null || this.data.getUinfo() == null || this.data.getUinfo().getBase() == null) {
+            return "";
+        }
+
+        return this.data.getUinfo().getBase().getFace();
+    }
+
+    @lombok.Data
+    public static class Data {
+
+        private int show_area;
+        private int msg_type;
+        private String like_icon;
+        private long uid;
+        private String like_text;
+        private String uname;
+        private String uname_color;
+        private List<Integer> identities;
+        private InteractWordMsg.Fans_medal fans_medal;
+        private Contribution_info contribution_info;
+        private int dmscore;
+        private String group_medal;
+        private boolean is_mystery;
+        private InteractWordMsg.Uinfo uinfo;
+
+        /**
+         * 未知属性都放在这
+         */
+        private final Map<String, JsonNode> unknownProperties = new HashMap<>();
+
+        @JsonAnyGetter
+        public Map<String, JsonNode> getUnknownProperties() {
+            return unknownProperties;
+        }
+
+        @JsonAnySetter
+        public void setOther(String key, JsonNode value) {
+            this.unknownProperties.put(key, value);
+        }
+    }
+
+    @lombok.Data
+    public static class Contribution_info {
+
+        private int grade;
+
+        /**
+         * 未知属性都放在这
+         */
+        private final Map<String, JsonNode> unknownProperties = new HashMap<>();
+
+        @JsonAnyGetter
+        public Map<String, JsonNode> getUnknownProperties() {
+            return unknownProperties;
+        }
+
+        @JsonAnySetter
+        public void setOther(String key, JsonNode value) {
+            this.unknownProperties.put(key, value);
+        }
+    }
+}

+ 351 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/msg/SendGiftMsg.java

@@ -0,0 +1,351 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.bilibili.msg;
+
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.JsonNode;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import tech.ordinaryroad.live.chat.client.bilibili.api.BilibiliApis;
+import tech.ordinaryroad.live.chat.client.bilibili.constant.OperationEnum;
+import tech.ordinaryroad.live.chat.client.bilibili.msg.base.BaseBilibiliMsg;
+import tech.ordinaryroad.live.chat.client.bilibili.msg.dto.MedalInfo;
+import tech.ordinaryroad.live.chat.client.commons.base.msg.IGiftMsg;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author mjz
+ * @date 2023/9/8
+ */
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+public class SendGiftMsg extends BaseBilibiliMsg implements IGiftMsg {
+
+    private Data data;
+
+    /**
+     * 额外属性,获取礼物图片时可能会用到
+     */
+    private long roomId;
+
+    @Override
+    public String getBadgeName() {
+        if (data == null || data.medal_info == null) {
+            return IGiftMsg.super.getBadgeName();
+        }
+
+        return data.medal_info.getMedal_name();
+    }
+
+    @Override
+    public byte getBadgeLevel() {
+        if (data == null || data.medal_info == null) {
+            return IGiftMsg.super.getBadgeLevel();
+        }
+
+        return data.medal_info.getMedal_level();
+    }
+
+    @Override
+    public String getUid() {
+        if (this.data == null) {
+            return null;
+        }
+
+        return Long.toString(this.data.getUid());
+    }
+
+    @Override
+    public String getUsername() {
+        if (this.data == null) {
+            return "";
+        }
+
+        return this.data.getUname();
+    }
+
+    @Override
+    public String getUserAvatar() {
+        if (this.data == null) {
+            return "";
+        }
+
+        return this.data.getFace();
+    }
+
+    @Override
+    public String getGiftName() {
+        if (this.data == null) {
+            return "未知礼物";
+        }
+
+        return this.data.getGiftName();
+    }
+
+    @Override
+    public String getGiftImg() {
+        return BilibiliApis.getGiftImgById(this.data.giftId, this.roomId);
+    }
+
+    @Override
+    public String getGiftId() {
+        if (this.data == null) {
+            return null;
+        }
+
+        return Long.toString(data.getGiftId());
+    }
+
+    @Override
+    public int getGiftCount() {
+        if (this.data == null) {
+            return 0;
+        }
+
+        return data.getNum();
+    }
+
+    @Override
+    public int getGiftPrice() {
+        if (this.data == null) {
+            return -1;
+        }
+
+        return data.getPrice();
+    }
+
+    @Override
+    public String getReceiveUid() {
+        if (this.data == null || this.data.getReceive_user_info() == null) {
+            return null;
+        }
+
+        return Long.toString(data.getReceive_user_info().getUid());
+    }
+
+    @Override
+    public String getReceiveUsername() {
+        if (this.data == null || this.data.getReceive_user_info() == null) {
+            return "";
+        }
+
+        return data.getReceive_user_info().getUname();
+    }
+
+    @Override
+    public OperationEnum getOperationEnum() {
+        return OperationEnum.SEND_SMS_REPLY;
+    }
+
+    @lombok.Data
+    public static class Data {
+
+        private int draw;
+        private int gold;
+        private int silver;
+        private int num;
+        private int total_coin;
+        private int effect;
+        private int broadcast_id;
+        private int crit_prob;
+        private int guard_level;
+        private long rcost;
+        private long uid;
+        private long timestamp;
+        private int giftId;
+        private int giftType;
+        @JsonProperty("super")
+        private int _super;
+        private int super_gift_num;
+        private int super_batch_gift_num;
+        private int remain;
+        private int discount_price;
+        private int price;
+        private String beatId;
+        private String biz_source;
+        private String action;
+        private String coin_type;
+        private String uname;
+        private String face;
+        private String batch_combo_id;
+        private String rnd;
+        private String giftName;
+        private String original_gift_name;
+        private Combo_send combo_send;
+        private Batch_combo_send batch_combo_send;
+        private String tag_image;
+        private String top_list;
+        private String send_master;
+        private boolean is_first;
+        private int demarcation;
+        private int combo_stay_time;
+        private int combo_total_coin;
+        private String tid;
+        private int effect_block;
+        private int is_special_batch;
+        private int combo_resources_id;
+        private int magnification;
+        private String name_color;
+        private MedalInfo medal_info;
+        private int svga_block;
+        private JsonNode blind_gift;
+        private int float_sc_resource_id;
+        @JsonProperty("switch")
+        private boolean _switch;
+        private int face_effect_type;
+        private int face_effect_id;
+        private boolean is_naming;
+        private Receive_user_info receive_user_info;
+        private boolean is_join_receiver;
+        private Bag_gift bag_gift;
+        private int wealth_level;
+
+        /**
+         * 未知属性都放在这
+         */
+        private final Map<String, JsonNode> unknownProperties = new HashMap<>();
+
+        @JsonAnyGetter
+        public Map<String, JsonNode> getUnknownProperties() {
+            return unknownProperties;
+        }
+
+        @JsonAnySetter
+        public void setOther(String key, JsonNode value) {
+            this.unknownProperties.put(key, value);
+        }
+    }
+
+    @lombok.Data
+    public static class Combo_send {
+
+        private long uid;
+        private int gift_num;
+        private int combo_num;
+        private int gift_id;
+        private String combo_id;
+        private String gift_name;
+        private String action;
+        private String uname;
+        private String send_master;
+
+        /**
+         * 未知属性都放在这
+         */
+        private final Map<String, JsonNode> unknownProperties = new HashMap<>();
+
+        @JsonAnyGetter
+        public Map<String, JsonNode> getUnknownProperties() {
+            return unknownProperties;
+        }
+
+        @JsonAnySetter
+        public void setOther(String key, JsonNode value) {
+            this.unknownProperties.put(key, value);
+        }
+    }
+
+    @lombok.Data
+    public static class Receive_user_info {
+
+        private String uname;
+        private long uid;
+
+        /**
+         * 未知属性都放在这
+         */
+        private final Map<String, JsonNode> unknownProperties = new HashMap<>();
+
+        @JsonAnyGetter
+        public Map<String, JsonNode> getUnknownProperties() {
+            return unknownProperties;
+        }
+
+        @JsonAnySetter
+        public void setOther(String key, JsonNode value) {
+            this.unknownProperties.put(key, value);
+        }
+    }
+
+    @lombok.Data
+    public static class Batch_combo_send {
+
+        private long uid;
+        private int gift_num;
+        private int batch_combo_num;
+        private int gift_id;
+        private String batch_combo_id;
+        private String gift_name;
+        private String action;
+        private String uname;
+        private String send_master;
+        private JsonNode blind_gift;
+
+        /**
+         * 未知属性都放在这
+         */
+        private final Map<String, JsonNode> unknownProperties = new HashMap<>();
+
+        @JsonAnyGetter
+        public Map<String, JsonNode> getUnknownProperties() {
+            return unknownProperties;
+        }
+
+        @JsonAnySetter
+        public void setOther(String key, JsonNode value) {
+            this.unknownProperties.put(key, value);
+        }
+    }
+
+    @lombok.Data
+    public static class Bag_gift {
+
+        private int show_price;
+        private int price_for_show;
+
+        /**
+         * 未知属性都放在这
+         */
+        private final Map<String, JsonNode> unknownProperties = new HashMap<>();
+
+        @JsonAnyGetter
+        public Map<String, JsonNode> getUnknownProperties() {
+            return unknownProperties;
+        }
+
+        @JsonAnySetter
+        public void setOther(String key, JsonNode value) {
+            this.unknownProperties.put(key, value);
+        }
+    }
+}

+ 81 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/msg/SendSmsReplyMsg.java

@@ -0,0 +1,81 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.bilibili.msg;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import tech.ordinaryroad.live.chat.client.bilibili.constant.OperationEnum;
+import tech.ordinaryroad.live.chat.client.bilibili.msg.base.BaseBilibiliCmdMsg;
+
+/**
+ * @author mjz
+ * @date 2023/1/6
+ */
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+public class SendSmsReplyMsg extends BaseBilibiliCmdMsg {
+
+    private Long id;
+
+    private String name;
+
+    private JsonNode full;
+
+    private JsonNode half;
+
+    private JsonNode side;
+
+    private JsonNode data;
+
+    private JsonNode info;
+
+    private JsonNode msg_common;
+
+    private JsonNode msg_self;
+
+    private JsonNode link_url;
+
+    private JsonNode msg_type;
+
+    private JsonNode shield_uid;
+
+    private JsonNode business_id;
+
+    private JsonNode scatter;
+
+    private long roomid;
+
+    private long real_roomid;
+
+    @Override
+    public OperationEnum getOperationEnum() {
+        return OperationEnum.SEND_SMS_REPLY;
+    }
+}

+ 202 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/msg/SuperChatMessageMsg.java

@@ -0,0 +1,202 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+package tech.ordinaryroad.live.chat.client.bilibili.msg;
+
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.databind.JsonNode;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import tech.ordinaryroad.live.chat.client.bilibili.constant.OperationEnum;
+import tech.ordinaryroad.live.chat.client.bilibili.msg.base.BaseBilibiliMsg;
+import tech.ordinaryroad.live.chat.client.bilibili.msg.dto.MedalInfo;
+import tech.ordinaryroad.live.chat.client.commons.base.msg.ISuperChatMsg;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author mjz
+ * @date 2023/9/24
+ */
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+public class SuperChatMessageMsg extends BaseBilibiliMsg implements ISuperChatMsg {
+
+    private long roomid;
+    private Data data;
+
+    @Override
+    public OperationEnum getOperationEnum() {
+        return OperationEnum.SEND_SMS_REPLY;
+    }
+
+    @Override
+    public String getUid() {
+        if (this.data == null) {
+            return null;
+        }
+
+        return Long.toString(this.data.uid);
+    }
+
+    @Override
+    public String getUsername() {
+        if (this.data == null || this.data.getUser_info() == null) {
+            return "";
+        }
+
+        return this.data.user_info.uname;
+    }
+
+    @Override
+    public String getUserAvatar() {
+        if (this.data == null || this.data.getUser_info() == null) {
+            return "";
+        }
+
+        return this.data.user_info.face;
+    }
+
+    @Override
+    public String getContent() {
+        if (this.data == null) {
+            return "";
+        }
+
+        return this.data.message;
+    }
+
+    @Override
+    public int getDuration() {
+        if (this.data == null) {
+            return 0;
+        }
+
+        return this.data.time;
+    }
+
+    @lombok.Data
+    public static class Data {
+        private String background_bottom_color;
+        private String background_color;
+        private String background_color_end;
+        private String background_color_start;
+        private String background_icon;
+        private String background_image;
+        private String background_price_color;
+        private double color_point;
+        private int dmscore;
+        private long end_time;
+        private Gift gift;
+        private long id;
+        private int is_ranked;
+        private int is_send_audit;
+        private MedalInfo medal_info;
+        private String message;
+        private String message_font_color;
+        private String message_trans;
+        private int price;
+        private int rate;
+        private long start_time;
+        private int time;
+        private String token;
+        private int trans_mark;
+        private long ts;
+        private long uid;
+        private User_info user_info;
+
+        /**
+         * 未知属性都放在这
+         */
+        private final Map<String, JsonNode> unknownProperties = new HashMap<>();
+
+        @JsonAnyGetter
+        public Map<String, JsonNode> getUnknownProperties() {
+            return unknownProperties;
+        }
+
+        @JsonAnySetter
+        public void setOther(String key, JsonNode value) {
+            this.unknownProperties.put(key, value);
+        }
+    }
+
+    @lombok.Data
+    public static class Gift {
+        private int gift_id;
+        private String gift_name;
+        private int num;
+
+        /**
+         * 未知属性都放在这
+         */
+        private final Map<String, JsonNode> unknownProperties = new HashMap<>();
+
+        @JsonAnyGetter
+        public Map<String, JsonNode> getUnknownProperties() {
+            return unknownProperties;
+        }
+
+        @JsonAnySetter
+        public void setOther(String key, JsonNode value) {
+            this.unknownProperties.put(key, value);
+        }
+    }
+
+    @lombok.Data
+    public static class User_info {
+        private String face;
+        private String face_frame;
+        private int guard_level;
+        private int is_main_vip;
+        private int is_svip;
+        private int is_vip;
+        private String level_color;
+        private int manager;
+        private String name_color;
+        private String title;
+        private String uname;
+        private int user_level;
+
+        /**
+         * 未知属性都放在这
+         */
+        private final Map<String, JsonNode> unknownProperties = new HashMap<>();
+
+        @JsonAnyGetter
+        public Map<String, JsonNode> getUnknownProperties() {
+            return unknownProperties;
+        }
+
+        @JsonAnySetter
+        public void setOther(String key, JsonNode value) {
+            this.unknownProperties.put(key, value);
+        }
+    }
+}

+ 68 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/msg/base/BaseBilibiliCmdMsg.java

@@ -0,0 +1,68 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.bilibili.msg.base;
+
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import tech.ordinaryroad.live.chat.client.bilibili.constant.BilibiliCmdEnum;
+import tech.ordinaryroad.live.chat.client.bilibili.constant.ProtoverEnum;
+import tech.ordinaryroad.live.chat.client.commons.base.msg.BaseCmdMsg;
+
+/**
+ * @author mjz
+ * @date 2023/1/6
+ */
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+public abstract class BaseBilibiliCmdMsg extends BaseCmdMsg<BilibiliCmdEnum> implements IBilibiliMsg {
+
+    private int protover;
+    private String cmd;
+
+    @Override
+    public String getCmd() {
+        return this.cmd;
+    }
+
+    @Override
+    public void setCmd(String cmd) {
+        this.cmd = cmd;
+    }
+
+    @Override
+    public BilibiliCmdEnum getCmdEnum() {
+        return BilibiliCmdEnum.getByString(getCmd());
+    }
+
+    @Override
+    public ProtoverEnum getProtoverEnum() {
+        return ProtoverEnum.getByCode(this.protover);
+    }
+}

+ 50 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/msg/base/BaseBilibiliMsg.java

@@ -0,0 +1,50 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.bilibili.msg.base;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import tech.ordinaryroad.live.chat.client.bilibili.constant.ProtoverEnum;
+import tech.ordinaryroad.live.chat.client.commons.base.msg.BaseMsg;
+
+/**
+ * @author mjz
+ * @date 2023/1/6
+ */
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+public abstract class BaseBilibiliMsg extends BaseMsg implements IBilibiliMsg {
+
+    private int protover;
+
+    @Override
+    public ProtoverEnum getProtoverEnum() {
+        return ProtoverEnum.getByCode(protover);
+    }
+}

+ 44 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/msg/base/IBilibiliMsg.java

@@ -0,0 +1,44 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.bilibili.msg.base;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import tech.ordinaryroad.live.chat.client.bilibili.constant.OperationEnum;
+import tech.ordinaryroad.live.chat.client.bilibili.constant.ProtoverEnum;
+import tech.ordinaryroad.live.chat.client.commons.base.msg.IMsg;
+
+/**
+ * @author mjz
+ * @date 2023/8/26
+ */
+public interface IBilibiliMsg extends IMsg {
+
+    @JsonIgnore
+    ProtoverEnum getProtoverEnum();
+
+    @JsonIgnore
+    OperationEnum getOperationEnum();
+
+}

+ 66 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/msg/dto/MedalInfo.java

@@ -0,0 +1,66 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.bilibili.msg.dto;
+
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.databind.JsonNode;
+import lombok.Data;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@Data
+public class MedalInfo {
+
+    private long target_id;
+    private String special;
+    private int icon_id;
+    private String anchor_uname;
+    private int anchor_roomid;
+    private byte medal_level;
+    private String medal_name;
+    private String medal_color;
+    private long medal_color_start;
+    private long medal_color_end;
+    private long medal_color_border;
+    private int is_lighted;
+    private int guard_level;
+
+    /**
+     * 未知属性都放在这
+     */
+    private final Map<String, JsonNode> unknownProperties = new HashMap<>();
+
+    @JsonAnyGetter
+    public Map<String, JsonNode> getUnknownProperties() {
+        return unknownProperties;
+    }
+
+    @JsonAnySetter
+    public void setOther(String key, JsonNode value) {
+        this.unknownProperties.put(key, value);
+    }
+}

+ 40 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/netty/frame/AuthWebSocketFrame.java

@@ -0,0 +1,40 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.bilibili.netty.frame;
+
+import io.netty.buffer.ByteBuf;
+import tech.ordinaryroad.live.chat.client.bilibili.netty.frame.base.BaseBilibiliWebSocketFrame;
+
+/**
+ * @author mjz
+ * @date 2023/1/5
+ */
+public class AuthWebSocketFrame extends BaseBilibiliWebSocketFrame {
+
+    public AuthWebSocketFrame(ByteBuf byteBuf) {
+        super(byteBuf);
+    }
+
+}

+ 40 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/netty/frame/HeartbeatWebSocketFrame.java

@@ -0,0 +1,40 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.bilibili.netty.frame;
+
+import io.netty.buffer.ByteBuf;
+import tech.ordinaryroad.live.chat.client.bilibili.netty.frame.base.BaseBilibiliWebSocketFrame;
+
+/**
+ * @author mjz
+ * @date 2023/1/5
+ */
+public class HeartbeatWebSocketFrame extends BaseBilibiliWebSocketFrame {
+
+    public HeartbeatWebSocketFrame(ByteBuf byteBuf) {
+        super(byteBuf);
+    }
+
+}

+ 54 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/netty/frame/base/BaseBilibiliWebSocketFrame.java

@@ -0,0 +1,54 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.bilibili.netty.frame.base;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
+import tech.ordinaryroad.live.chat.client.bilibili.constant.OperationEnum;
+import tech.ordinaryroad.live.chat.client.bilibili.constant.ProtoverEnum;
+
+/**
+ * 实现Bilibili协议的BinaryWebSocketFrame
+ * <a href="https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/live/message_stream.md#数据包格式">数据包格式</a>
+ *
+ * @author mjz
+ * @date 2023/1/5
+ */
+public abstract class BaseBilibiliWebSocketFrame extends BinaryWebSocketFrame {
+
+    public static int sequence = 0;
+
+    public ProtoverEnum getProtoverEnum() {
+        return ProtoverEnum.getByCode(super.content().getShort(6));
+    }
+
+    public OperationEnum getOperationEnum() {
+        return OperationEnum.getByCode(super.content().getInt(8));
+    }
+
+    public BaseBilibiliWebSocketFrame(ByteBuf byteBuf) {
+        super(byteBuf);
+    }
+}

+ 113 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/netty/frame/factory/BilibiliWebSocketFrameFactory.java

@@ -0,0 +1,113 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.bilibili.netty.frame.factory;
+
+import cn.hutool.core.lang.UUID;
+import cn.hutool.core.util.NumberUtil;
+import com.fasterxml.jackson.databind.JsonNode;
+import tech.ordinaryroad.live.chat.client.bilibili.api.BilibiliApis;
+import tech.ordinaryroad.live.chat.client.bilibili.constant.ProtoverEnum;
+import tech.ordinaryroad.live.chat.client.bilibili.msg.AuthMsg;
+import tech.ordinaryroad.live.chat.client.bilibili.msg.HeartbeatMsg;
+import tech.ordinaryroad.live.chat.client.bilibili.netty.frame.AuthWebSocketFrame;
+import tech.ordinaryroad.live.chat.client.bilibili.netty.frame.HeartbeatWebSocketFrame;
+import tech.ordinaryroad.live.chat.client.bilibili.util.BilibiliCodecUtil;
+import tech.ordinaryroad.live.chat.client.commons.base.exception.BaseException;
+import tech.ordinaryroad.live.chat.client.commons.util.OrLiveChatCookieUtil;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author mjz
+ * @date 2023/1/5
+ */
+public class BilibiliWebSocketFrameFactory {
+
+    private static final ConcurrentHashMap<Long, BilibiliWebSocketFrameFactory> CACHE = new ConcurrentHashMap<>();
+
+    /**
+     * 浏览器地址中的房间id,支持短id
+     */
+    private final long roomId;
+    private volatile static HeartbeatMsg heartbeatMsg;
+
+    public BilibiliWebSocketFrameFactory(long roomId) {
+        this.roomId = roomId;
+    }
+
+    public synchronized static BilibiliWebSocketFrameFactory getInstance(long roomId) {
+        return CACHE.computeIfAbsent(roomId, aLong -> new BilibiliWebSocketFrameFactory(roomId));
+    }
+
+    /**
+     * 创建认证包
+     *
+     * @param protover {@link ProtoverEnum}
+     * @param cookie   浏览器cookie,仅用来维持登录状态
+     * @return AuthWebSocketFrame
+     */
+    public AuthWebSocketFrame createAuth(ProtoverEnum protover, String cookie) {
+        try {
+            Map<String, String> cookieMap = OrLiveChatCookieUtil.parseCookieString(cookie);
+            String buvid3 = OrLiveChatCookieUtil.getCookieByName(cookieMap, "buvid3", () -> UUID.randomUUID().toString());
+            String uid = OrLiveChatCookieUtil.getCookieByName(cookieMap, "DedeUserID", () -> "0");
+            BilibiliApis.RoomInitResult data = BilibiliApis.roomInit(roomId, cookie);
+            JsonNode danmuInfo = BilibiliApis.getDanmuInfo(roomId, 0, cookie);
+            long realRoomId = data.getRoom_id();
+            AuthMsg authMsg = new AuthMsg(realRoomId, protover.getCode(), buvid3, danmuInfo.get("token").asText());
+            authMsg.setUid(NumberUtil.parseLong(uid));
+            return new AuthWebSocketFrame(BilibiliCodecUtil.encode(authMsg));
+        } catch (Exception e) {
+            throw new BaseException(String.format("认证包创建失败,请检查房间号是否正确。roomId: %d, msg: %s", roomId, e.getMessage()));
+        }
+    }
+
+    public AuthWebSocketFrame createAuth(ProtoverEnum protover) {
+        return this.createAuth(protover, null);
+    }
+
+    public HeartbeatWebSocketFrame createHeartbeat(ProtoverEnum protover) {
+        return new HeartbeatWebSocketFrame(BilibiliCodecUtil.encode(this.getHeartbeatMsg(protover)));
+    }
+
+    /**
+     * 心跳包单例模式
+     *
+     * @param protover {@link ProtoverEnum}
+     * @return HeartbeatWebSocketFrame
+     */
+    public HeartbeatMsg getHeartbeatMsg(ProtoverEnum protover) {
+        if (heartbeatMsg == null) {
+            synchronized (BilibiliWebSocketFrameFactory.this) {
+                if (heartbeatMsg == null) {
+                    heartbeatMsg = new HeartbeatMsg(protover.getCode());
+                }
+            }
+        }
+        return heartbeatMsg;
+    }
+
+}

+ 153 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/netty/handler/BilibiliBinaryFrameHandler.java

@@ -0,0 +1,153 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.bilibili.netty.handler;
+
+import cn.hutool.core.util.StrUtil;
+import io.netty.buffer.ByteBuf;
+import io.netty.channel.ChannelHandler;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import tech.ordinaryroad.live.chat.client.bilibili.client.BilibiliLiveChatClient;
+import tech.ordinaryroad.live.chat.client.bilibili.constant.BilibiliCmdEnum;
+import tech.ordinaryroad.live.chat.client.bilibili.listener.IBilibiliMsgListener;
+import tech.ordinaryroad.live.chat.client.bilibili.msg.*;
+import tech.ordinaryroad.live.chat.client.bilibili.msg.base.BaseBilibiliMsg;
+import tech.ordinaryroad.live.chat.client.bilibili.msg.base.IBilibiliMsg;
+import tech.ordinaryroad.live.chat.client.bilibili.util.BilibiliCodecUtil;
+import tech.ordinaryroad.live.chat.client.commons.base.msg.ICmdMsg;
+import tech.ordinaryroad.live.chat.client.servers.netty.client.handler.BaseNettyClientBinaryFrameHandler;
+
+import java.util.List;
+
+
+/**
+ * 消息处理器
+ *
+ * @author mjz
+ * @date 2023/1/4
+ */
+@Slf4j
+@ChannelHandler.Sharable
+public class BilibiliBinaryFrameHandler extends BaseNettyClientBinaryFrameHandler<BilibiliLiveChatClient, BilibiliBinaryFrameHandler, BilibiliCmdEnum, IBilibiliMsg, IBilibiliMsgListener> {
+
+    public BilibiliBinaryFrameHandler(List<IBilibiliMsgListener> msgListeners, BilibiliLiveChatClient client) {
+        super(msgListeners, client);
+    }
+
+    public BilibiliBinaryFrameHandler(List<IBilibiliMsgListener> msgListeners, long roomId) {
+        super(msgListeners, roomId);
+    }
+
+    @SneakyThrows
+    @Override
+    public void onCmdMsg(BilibiliCmdEnum cmd, ICmdMsg<BilibiliCmdEnum> cmdMsg) {
+        if (super.msgListeners.isEmpty()) {
+            return;
+        }
+
+        SendSmsReplyMsg sendSmsReplyMsg = (SendSmsReplyMsg) cmdMsg;
+        switch (cmd) {
+            case DANMU_MSG: {
+                DanmuMsgMsg danmuMsgMsg = new DanmuMsgMsg();
+                danmuMsgMsg.setProtover(sendSmsReplyMsg.getProtover());
+                danmuMsgMsg.setInfo(sendSmsReplyMsg.getInfo());
+                danmuMsgMsg.setDm_v2(StrUtil.toStringOrNull(sendSmsReplyMsg.getUnknownProperties().get("dm_v2")));
+                iteratorMsgListeners(msgListener -> msgListener.onDanmuMsg(BilibiliBinaryFrameHandler.this, danmuMsgMsg));
+                break;
+            }
+
+            case SEND_GIFT: {
+                SendGiftMsg sendGiftMsg = new SendGiftMsg();
+                sendGiftMsg.setRoomId(getRoomIdAsLong());
+                sendGiftMsg.setProtover(sendSmsReplyMsg.getProtover());
+                SendGiftMsg.Data data = BaseBilibiliMsg.OBJECT_MAPPER.treeToValue(sendSmsReplyMsg.getData(), SendGiftMsg.Data.class);
+                sendGiftMsg.setData(data);
+                iteratorMsgListeners(msgListener -> {
+                    msgListener.onGiftMsg(BilibiliBinaryFrameHandler.this, sendGiftMsg);
+                    msgListener.onSendGift(BilibiliBinaryFrameHandler.this, sendSmsReplyMsg);
+                });
+                break;
+            }
+
+            case SUPER_CHAT_MESSAGE: {
+                SuperChatMessageMsg superChatMessageMsg = new SuperChatMessageMsg();
+                superChatMessageMsg.setProtover(sendSmsReplyMsg.getProtover());
+                superChatMessageMsg.setRoomid(sendSmsReplyMsg.getRoomid());
+                SuperChatMessageMsg.Data data = BaseBilibiliMsg.OBJECT_MAPPER.treeToValue(sendSmsReplyMsg.getData(), SuperChatMessageMsg.Data.class);
+                superChatMessageMsg.setData(data);
+                iteratorMsgListeners(msgListener -> msgListener.onSuperChatMsg(BilibiliBinaryFrameHandler.this, superChatMessageMsg));
+                break;
+            }
+
+            case INTERACT_WORD: {
+                InteractWordMsg interactWordMsg = new InteractWordMsg();
+                interactWordMsg.setProtover(sendSmsReplyMsg.getProtover());
+                InteractWordMsg.Data data = BaseBilibiliMsg.OBJECT_MAPPER.treeToValue(sendSmsReplyMsg.getData(), InteractWordMsg.Data.class);
+                interactWordMsg.setData(data);
+                iteratorMsgListeners(msgListener -> {
+                    msgListener.onEnterRoomMsg(BilibiliBinaryFrameHandler.this, interactWordMsg);
+                    msgListener.onEnterRoom(BilibiliBinaryFrameHandler.this, sendSmsReplyMsg);
+                });
+                break;
+            }
+
+            case ENTRY_EFFECT: {
+                iteratorMsgListeners(msgListener -> msgListener.onEntryEffect(BilibiliBinaryFrameHandler.this, sendSmsReplyMsg));
+                break;
+            }
+
+            case WATCHED_CHANGE: {
+                iteratorMsgListeners(msgListener -> msgListener.onWatchedChange(BilibiliBinaryFrameHandler.this, sendSmsReplyMsg));
+                break;
+            }
+
+            case LIKE_INFO_V3_CLICK: {
+                LikeInfoV3ClickMsg likeInfoV3ClickMsg = new LikeInfoV3ClickMsg();
+                likeInfoV3ClickMsg.setProtover(sendSmsReplyMsg.getProtover());
+                LikeInfoV3ClickMsg.Data data = BaseBilibiliMsg.OBJECT_MAPPER.treeToValue(sendSmsReplyMsg.getData(), LikeInfoV3ClickMsg.Data.class);
+                likeInfoV3ClickMsg.setData(data);
+                iteratorMsgListeners(msgListener -> {
+                    msgListener.onLikeMsg(BilibiliBinaryFrameHandler.this, likeInfoV3ClickMsg);
+                    msgListener.onClickLike(BilibiliBinaryFrameHandler.this, sendSmsReplyMsg);
+                });
+                break;
+            }
+
+            case LIKE_INFO_V3_UPDATE: {
+                iteratorMsgListeners(msgListener -> msgListener.onClickUpdate(BilibiliBinaryFrameHandler.this, sendSmsReplyMsg));
+                break;
+            }
+
+            default: {
+                iteratorMsgListeners(msgListener -> msgListener.onOtherCmdMsg(BilibiliBinaryFrameHandler.this, cmd, cmdMsg));
+            }
+        }
+    }
+
+    @Override
+    protected List<IBilibiliMsg> decode(ByteBuf byteBuf) {
+        return BilibiliCodecUtil.decode(byteBuf);
+    }
+}

+ 154 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/netty/handler/BilibiliConnectionHandler.java

@@ -0,0 +1,154 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.bilibili.netty.handler;
+
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFutureListener;
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker;
+import lombok.extern.slf4j.Slf4j;
+import tech.ordinaryroad.live.chat.client.bilibili.client.BilibiliLiveChatClient;
+import tech.ordinaryroad.live.chat.client.bilibili.config.BilibiliLiveChatClientConfig;
+import tech.ordinaryroad.live.chat.client.bilibili.constant.ProtoverEnum;
+import tech.ordinaryroad.live.chat.client.bilibili.netty.frame.factory.BilibiliWebSocketFrameFactory;
+import tech.ordinaryroad.live.chat.client.commons.base.listener.IBaseConnectionListener;
+import tech.ordinaryroad.live.chat.client.servers.netty.client.handler.BaseNettyClientConnectionHandler;
+
+
+/**
+ * 连接处理器
+ *
+ * @author mjz
+ * @date 2023/8/21
+ */
+@Slf4j
+@ChannelHandler.Sharable
+public class BilibiliConnectionHandler extends BaseNettyClientConnectionHandler<BilibiliLiveChatClient, BilibiliConnectionHandler> {
+
+    /**
+     * 以ClientConfig为主
+     */
+    private final long roomId;
+    /**
+     * 以ClientConfig为主
+     */
+    private final ProtoverEnum protover;
+    /**
+     * 以ClientConfig为主
+     */
+    private String cookie;
+
+    public BilibiliConnectionHandler(WebSocketClientHandshaker handshaker, BilibiliLiveChatClient client, IBaseConnectionListener<BilibiliConnectionHandler> listener) {
+        super(handshaker, client, listener);
+        this.roomId = client.getConfig().getRoomId();
+        this.protover = client.getConfig().getProtover();
+        this.cookie = client.getConfig().getCookie();
+    }
+
+    public BilibiliConnectionHandler(WebSocketClientHandshaker handshaker, BilibiliLiveChatClient client) {
+        this(handshaker, client, null);
+    }
+
+    public BilibiliConnectionHandler(WebSocketClientHandshaker handshaker, long roomId, ProtoverEnum protover, IBaseConnectionListener<BilibiliConnectionHandler> listener, String cookie) {
+        super(handshaker, listener);
+        this.roomId = roomId;
+        this.protover = protover;
+        this.cookie = cookie;
+    }
+
+    public BilibiliConnectionHandler(WebSocketClientHandshaker handshaker, long roomId, ProtoverEnum protover, IBaseConnectionListener<BilibiliConnectionHandler> listener) {
+        this(handshaker, roomId, protover, listener, null);
+    }
+
+    public BilibiliConnectionHandler(WebSocketClientHandshaker handshaker, long roomId, ProtoverEnum protover, String cookie) {
+        this(handshaker, roomId, protover, null, cookie);
+    }
+
+    public BilibiliConnectionHandler(WebSocketClientHandshaker handshaker, long roomId, ProtoverEnum protover) {
+        this(handshaker, roomId, protover, null, null);
+    }
+
+    @Override
+    protected void sendHeartbeat(ChannelHandlerContext ctx) {
+        if (log.isDebugEnabled()) {
+            log.debug("发送心跳包");
+        }
+        ctx.writeAndFlush(
+                getWebSocketFrameFactory(getRoomId()).createHeartbeat(getProtover())
+        ).addListener((ChannelFutureListener) future -> {
+            if (future.isSuccess()) {
+                if (log.isDebugEnabled()) {
+                    log.debug("心跳包发送完成");
+                }
+            } else {
+                log.error("心跳包发送失败", future.cause());
+            }
+        });
+    }
+
+    private static BilibiliWebSocketFrameFactory getWebSocketFrameFactory(long roomId) {
+        return BilibiliWebSocketFrameFactory.getInstance(roomId);
+    }
+
+    @Override
+    public void sendAuthRequest(Channel channel) {
+        // 5s内认证
+        if (log.isDebugEnabled()) {
+            log.debug("发送认证包");
+        }
+        channel.writeAndFlush(getWebSocketFrameFactory(getRoomId()).createAuth(getProtover(), getCookie()));
+    }
+
+    public long getRoomId() {
+        return client != null ? client.getConfig().getRoomId() : roomId;
+    }
+
+    private ProtoverEnum getProtover() {
+        return client != null ? client.getConfig().getProtover() : protover;
+    }
+
+    private String getCookie() {
+        return client != null ? client.getConfig().getCookie() : cookie;
+    }
+
+    @Override
+    protected long getHeartbeatPeriod() {
+        if (client == null) {
+            return BilibiliLiveChatClientConfig.DEFAULT_HEARTBEAT_PERIOD;
+        } else {
+            return client.getConfig().getHeartbeatPeriod();
+        }
+    }
+
+    @Override
+    protected long getHeartbeatInitialDelay() {
+        if (client == null) {
+            return BilibiliLiveChatClientConfig.DEFAULT_HEARTBEAT_INITIAL_DELAY;
+        } else {
+            return client.getConfig().getHeartbeatInitialDelay();
+        }
+    }
+}

+ 78 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/protobuf/Dm_v2Proto.java

@@ -0,0 +1,78 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: dm_v2.proto
+
+package tech.ordinaryroad.live.chat.client.bilibili.protobuf;
+
+public final class Dm_v2Proto {
+  private Dm_v2Proto() {}
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistryLite registry) {
+  }
+
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistry registry) {
+    registerAllExtensions(
+        (com.google.protobuf.ExtensionRegistryLite) registry);
+  }
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_tech_ordinaryroad_live_chat_client_bilibili_protobuf_dm_v2_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_tech_ordinaryroad_live_chat_client_bilibili_protobuf_dm_v2_fieldAccessorTable;
+
+  public static com.google.protobuf.Descriptors.FileDescriptor
+      getDescriptor() {
+    return descriptor;
+  }
+  private static  com.google.protobuf.Descriptors.FileDescriptor
+      descriptor;
+  static {
+    String[] descriptorData = {
+      "\n\013dm_v2.proto\0224tech.ordinaryroad.live.ch" +
+      "at.client.bilibili.protobuf\032\016dm_v2_20.pr" +
+      "oto\"Y\n\005dm_v2\022P\n\010dm_v2_20\030\024 \001(\0132>.tech.or" +
+      "dinaryroad.live.chat.client.bilibili.pro" +
+      "tobuf.dm_v2_20BJ\n4tech.ordinaryroad.live" +
+      ".chat.client.bilibili.protobufB\nDm_v2Pro" +
+      "toP\001\242\002\003GPBb\006proto3"
+    };
+    descriptor = com.google.protobuf.Descriptors.FileDescriptor
+      .internalBuildGeneratedFileFrom(descriptorData,
+        new com.google.protobuf.Descriptors.FileDescriptor[] {
+          Dm_v2_20Proto.getDescriptor(),
+        });
+    internal_static_tech_ordinaryroad_live_chat_client_bilibili_protobuf_dm_v2_descriptor =
+      getDescriptor().getMessageTypes().get(0);
+    internal_static_tech_ordinaryroad_live_chat_client_bilibili_protobuf_dm_v2_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_tech_ordinaryroad_live_chat_client_bilibili_protobuf_dm_v2_descriptor,
+        new String[] { "DmV220", });
+    Dm_v2_20Proto.getDescriptor();
+  }
+
+  // @@protoc_insertion_point(outer_class_scope)
+}

+ 74 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/protobuf/Dm_v2_20Proto.java

@@ -0,0 +1,74 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: dm_v2_20.proto
+
+package tech.ordinaryroad.live.chat.client.bilibili.protobuf;
+
+public final class Dm_v2_20Proto {
+  private Dm_v2_20Proto() {}
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistryLite registry) {
+  }
+
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistry registry) {
+    registerAllExtensions(
+        (com.google.protobuf.ExtensionRegistryLite) registry);
+  }
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_tech_ordinaryroad_live_chat_client_bilibili_protobuf_dm_v2_20_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_tech_ordinaryroad_live_chat_client_bilibili_protobuf_dm_v2_20_fieldAccessorTable;
+
+  public static com.google.protobuf.Descriptors.FileDescriptor
+      getDescriptor() {
+    return descriptor;
+  }
+  private static  com.google.protobuf.Descriptors.FileDescriptor
+      descriptor;
+  static {
+    String[] descriptorData = {
+      "\n\016dm_v2_20.proto\0224tech.ordinaryroad.live" +
+      ".chat.client.bilibili.protobuf\"\032\n\010dm_v2_" +
+      "20\022\016\n\006avatar\030\004 \001(\tBM\n4tech.ordinaryroad." +
+      "live.chat.client.bilibili.protobufB\rDm_v" +
+      "2_20ProtoP\001\242\002\003GPBb\006proto3"
+    };
+    descriptor = com.google.protobuf.Descriptors.FileDescriptor
+      .internalBuildGeneratedFileFrom(descriptorData,
+        new com.google.protobuf.Descriptors.FileDescriptor[] {
+        });
+    internal_static_tech_ordinaryroad_live_chat_client_bilibili_protobuf_dm_v2_20_descriptor =
+      getDescriptor().getMessageTypes().get(0);
+    internal_static_tech_ordinaryroad_live_chat_client_bilibili_protobuf_dm_v2_20_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_tech_ordinaryroad_live_chat_client_bilibili_protobuf_dm_v2_20_descriptor,
+        new String[] { "Avatar", });
+  }
+
+  // @@protoc_insertion_point(outer_class_scope)
+}

+ 610 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/protobuf/dm_v2.java

@@ -0,0 +1,610 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: dm_v2.proto
+
+package tech.ordinaryroad.live.chat.client.bilibili.protobuf;
+
+/**
+ * Protobuf type {@code tech.ordinaryroad.live.chat.client.bilibili.protobuf.dm_v2}
+ */
+public final class dm_v2 extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:tech.ordinaryroad.live.chat.client.bilibili.protobuf.dm_v2)
+    dm_v2OrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use dm_v2.newBuilder() to construct.
+  private dm_v2(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private dm_v2() {
+  }
+
+  @Override
+  @SuppressWarnings({"unused"})
+  protected Object newInstance(
+      UnusedPrivateParameter unused) {
+    return new dm_v2();
+  }
+
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return Dm_v2Proto.internal_static_tech_ordinaryroad_live_chat_client_bilibili_protobuf_dm_v2_descriptor;
+  }
+
+  @Override
+  protected FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return Dm_v2Proto.internal_static_tech_ordinaryroad_live_chat_client_bilibili_protobuf_dm_v2_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            dm_v2.class, Builder.class);
+  }
+
+  public static final int DM_V2_20_FIELD_NUMBER = 20;
+  private dm_v2_20 dmV220_;
+  /**
+   * <code>.tech.ordinaryroad.live.chat.client.bilibili.protobuf.dm_v2_20 dm_v2_20 = 20;</code>
+   * @return Whether the dmV220 field is set.
+   */
+  @Override
+  public boolean hasDmV220() {
+    return dmV220_ != null;
+  }
+  /**
+   * <code>.tech.ordinaryroad.live.chat.client.bilibili.protobuf.dm_v2_20 dm_v2_20 = 20;</code>
+   * @return The dmV220.
+   */
+  @Override
+  public dm_v2_20 getDmV220() {
+    return dmV220_ == null ? dm_v2_20.getDefaultInstance() : dmV220_;
+  }
+  /**
+   * <code>.tech.ordinaryroad.live.chat.client.bilibili.protobuf.dm_v2_20 dm_v2_20 = 20;</code>
+   */
+  @Override
+  public dm_v2_20OrBuilder getDmV220OrBuilder() {
+    return dmV220_ == null ? dm_v2_20.getDefaultInstance() : dmV220_;
+  }
+
+  private byte memoizedIsInitialized = -1;
+  @Override
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  @Override
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (dmV220_ != null) {
+      output.writeMessage(20, getDmV220());
+    }
+    getUnknownFields().writeTo(output);
+  }
+
+  @Override
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (dmV220_ != null) {
+      size += com.google.protobuf.CodedOutputStream
+        .computeMessageSize(20, getDmV220());
+    }
+    size += getUnknownFields().getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @Override
+  public boolean equals(final Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof dm_v2)) {
+      return super.equals(obj);
+    }
+    dm_v2 other = (dm_v2) obj;
+
+    if (hasDmV220() != other.hasDmV220()) return false;
+    if (hasDmV220()) {
+      if (!getDmV220()
+          .equals(other.getDmV220())) return false;
+    }
+    if (!getUnknownFields().equals(other.getUnknownFields())) return false;
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    if (hasDmV220()) {
+      hash = (37 * hash) + DM_V2_20_FIELD_NUMBER;
+      hash = (53 * hash) + getDmV220().hashCode();
+    }
+    hash = (29 * hash) + getUnknownFields().hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static dm_v2 parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static dm_v2 parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static dm_v2 parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static dm_v2 parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static dm_v2 parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static dm_v2 parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static dm_v2 parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static dm_v2 parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public static dm_v2 parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+
+  public static dm_v2 parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static dm_v2 parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static dm_v2 parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  @Override
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(dm_v2 prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  @Override
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @Override
+  protected Builder newBuilderForType(
+      BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * Protobuf type {@code tech.ordinaryroad.live.chat.client.bilibili.protobuf.dm_v2}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:tech.ordinaryroad.live.chat.client.bilibili.protobuf.dm_v2)
+      dm_v2OrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return Dm_v2Proto.internal_static_tech_ordinaryroad_live_chat_client_bilibili_protobuf_dm_v2_descriptor;
+    }
+
+    @Override
+    protected FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return Dm_v2Proto.internal_static_tech_ordinaryroad_live_chat_client_bilibili_protobuf_dm_v2_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              dm_v2.class, Builder.class);
+    }
+
+    // Construct using tech.ordinaryroad.live.chat.client.bilibili.protobuf.dm_v2.newBuilder()
+    private Builder() {
+
+    }
+
+    private Builder(
+        BuilderParent parent) {
+      super(parent);
+
+    }
+    @Override
+    public Builder clear() {
+      super.clear();
+      bitField0_ = 0;
+      dmV220_ = null;
+      if (dmV220Builder_ != null) {
+        dmV220Builder_.dispose();
+        dmV220Builder_ = null;
+      }
+      return this;
+    }
+
+    @Override
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return Dm_v2Proto.internal_static_tech_ordinaryroad_live_chat_client_bilibili_protobuf_dm_v2_descriptor;
+    }
+
+    @Override
+    public dm_v2 getDefaultInstanceForType() {
+      return dm_v2.getDefaultInstance();
+    }
+
+    @Override
+    public dm_v2 build() {
+      dm_v2 result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    @Override
+    public dm_v2 buildPartial() {
+      dm_v2 result = new dm_v2(this);
+      if (bitField0_ != 0) { buildPartial0(result); }
+      onBuilt();
+      return result;
+    }
+
+    private void buildPartial0(dm_v2 result) {
+      int from_bitField0_ = bitField0_;
+      if (((from_bitField0_ & 0x00000001) != 0)) {
+        result.dmV220_ = dmV220Builder_ == null
+            ? dmV220_
+            : dmV220Builder_.build();
+      }
+    }
+
+    @Override
+    public Builder clone() {
+      return super.clone();
+    }
+    @Override
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        Object value) {
+      return super.setField(field, value);
+    }
+    @Override
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return super.clearField(field);
+    }
+    @Override
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return super.clearOneof(oneof);
+    }
+    @Override
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, Object value) {
+      return super.setRepeatedField(field, index, value);
+    }
+    @Override
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        Object value) {
+      return super.addRepeatedField(field, value);
+    }
+    @Override
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof dm_v2) {
+        return mergeFrom((dm_v2)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(dm_v2 other) {
+      if (other == dm_v2.getDefaultInstance()) return this;
+      if (other.hasDmV220()) {
+        mergeDmV220(other.getDmV220());
+      }
+      this.mergeUnknownFields(other.getUnknownFields());
+      onChanged();
+      return this;
+    }
+
+    @Override
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    @Override
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      if (extensionRegistry == null) {
+        throw new NullPointerException();
+      }
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            case 162: {
+              input.readMessage(
+                  getDmV220FieldBuilder().getBuilder(),
+                  extensionRegistry);
+              bitField0_ |= 0x00000001;
+              break;
+            } // case 162
+            default: {
+              if (!super.parseUnknownField(input, extensionRegistry, tag)) {
+                done = true; // was an endgroup tag
+              }
+              break;
+            } // default:
+          } // switch (tag)
+        } // while (!done)
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.unwrapIOException();
+      } finally {
+        onChanged();
+      } // finally
+      return this;
+    }
+    private int bitField0_;
+
+    private dm_v2_20 dmV220_;
+    private com.google.protobuf.SingleFieldBuilderV3<
+        dm_v2_20, dm_v2_20.Builder, dm_v2_20OrBuilder> dmV220Builder_;
+    /**
+     * <code>.tech.ordinaryroad.live.chat.client.bilibili.protobuf.dm_v2_20 dm_v2_20 = 20;</code>
+     * @return Whether the dmV220 field is set.
+     */
+    public boolean hasDmV220() {
+      return ((bitField0_ & 0x00000001) != 0);
+    }
+    /**
+     * <code>.tech.ordinaryroad.live.chat.client.bilibili.protobuf.dm_v2_20 dm_v2_20 = 20;</code>
+     * @return The dmV220.
+     */
+    public dm_v2_20 getDmV220() {
+      if (dmV220Builder_ == null) {
+        return dmV220_ == null ? dm_v2_20.getDefaultInstance() : dmV220_;
+      } else {
+        return dmV220Builder_.getMessage();
+      }
+    }
+    /**
+     * <code>.tech.ordinaryroad.live.chat.client.bilibili.protobuf.dm_v2_20 dm_v2_20 = 20;</code>
+     */
+    public Builder setDmV220(dm_v2_20 value) {
+      if (dmV220Builder_ == null) {
+        if (value == null) {
+          throw new NullPointerException();
+        }
+        dmV220_ = value;
+      } else {
+        dmV220Builder_.setMessage(value);
+      }
+      bitField0_ |= 0x00000001;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>.tech.ordinaryroad.live.chat.client.bilibili.protobuf.dm_v2_20 dm_v2_20 = 20;</code>
+     */
+    public Builder setDmV220(
+        dm_v2_20.Builder builderForValue) {
+      if (dmV220Builder_ == null) {
+        dmV220_ = builderForValue.build();
+      } else {
+        dmV220Builder_.setMessage(builderForValue.build());
+      }
+      bitField0_ |= 0x00000001;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>.tech.ordinaryroad.live.chat.client.bilibili.protobuf.dm_v2_20 dm_v2_20 = 20;</code>
+     */
+    public Builder mergeDmV220(dm_v2_20 value) {
+      if (dmV220Builder_ == null) {
+        if (((bitField0_ & 0x00000001) != 0) &&
+          dmV220_ != null &&
+          dmV220_ != dm_v2_20.getDefaultInstance()) {
+          getDmV220Builder().mergeFrom(value);
+        } else {
+          dmV220_ = value;
+        }
+      } else {
+        dmV220Builder_.mergeFrom(value);
+      }
+      bitField0_ |= 0x00000001;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>.tech.ordinaryroad.live.chat.client.bilibili.protobuf.dm_v2_20 dm_v2_20 = 20;</code>
+     */
+    public Builder clearDmV220() {
+      bitField0_ = (bitField0_ & ~0x00000001);
+      dmV220_ = null;
+      if (dmV220Builder_ != null) {
+        dmV220Builder_.dispose();
+        dmV220Builder_ = null;
+      }
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>.tech.ordinaryroad.live.chat.client.bilibili.protobuf.dm_v2_20 dm_v2_20 = 20;</code>
+     */
+    public dm_v2_20.Builder getDmV220Builder() {
+      bitField0_ |= 0x00000001;
+      onChanged();
+      return getDmV220FieldBuilder().getBuilder();
+    }
+    /**
+     * <code>.tech.ordinaryroad.live.chat.client.bilibili.protobuf.dm_v2_20 dm_v2_20 = 20;</code>
+     */
+    public dm_v2_20OrBuilder getDmV220OrBuilder() {
+      if (dmV220Builder_ != null) {
+        return dmV220Builder_.getMessageOrBuilder();
+      } else {
+        return dmV220_ == null ?
+            dm_v2_20.getDefaultInstance() : dmV220_;
+      }
+    }
+    /**
+     * <code>.tech.ordinaryroad.live.chat.client.bilibili.protobuf.dm_v2_20 dm_v2_20 = 20;</code>
+     */
+    private com.google.protobuf.SingleFieldBuilderV3<
+        dm_v2_20, dm_v2_20.Builder, dm_v2_20OrBuilder>
+        getDmV220FieldBuilder() {
+      if (dmV220Builder_ == null) {
+        dmV220Builder_ = new com.google.protobuf.SingleFieldBuilderV3<
+            dm_v2_20, dm_v2_20.Builder, dm_v2_20OrBuilder>(
+                getDmV220(),
+                getParentForChildren(),
+                isClean());
+        dmV220_ = null;
+      }
+      return dmV220Builder_;
+    }
+    @Override
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFields(unknownFields);
+    }
+
+    @Override
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:tech.ordinaryroad.live.chat.client.bilibili.protobuf.dm_v2)
+  }
+
+  // @@protoc_insertion_point(class_scope:tech.ordinaryroad.live.chat.client.bilibili.protobuf.dm_v2)
+  private static final dm_v2 DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new dm_v2();
+  }
+
+  public static dm_v2 getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<dm_v2>
+      PARSER = new com.google.protobuf.AbstractParser<dm_v2>() {
+    @Override
+    public dm_v2 parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      Builder builder = newBuilder();
+      try {
+        builder.mergeFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(builder.buildPartial());
+      } catch (com.google.protobuf.UninitializedMessageException e) {
+        throw e.asInvalidProtocolBufferException().setUnfinishedMessage(builder.buildPartial());
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(e)
+            .setUnfinishedMessage(builder.buildPartial());
+      }
+      return builder.buildPartial();
+    }
+  };
+
+  public static com.google.protobuf.Parser<dm_v2> parser() {
+    return PARSER;
+  }
+
+  @Override
+  public com.google.protobuf.Parser<dm_v2> getParserForType() {
+    return PARSER;
+  }
+
+  @Override
+  public dm_v2 getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+

+ 48 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/protobuf/dm_v2OrBuilder.java

@@ -0,0 +1,48 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: dm_v2.proto
+
+package tech.ordinaryroad.live.chat.client.bilibili.protobuf;
+
+public interface dm_v2OrBuilder extends
+    // @@protoc_insertion_point(interface_extends:tech.ordinaryroad.live.chat.client.bilibili.protobuf.dm_v2)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <code>.tech.ordinaryroad.live.chat.client.bilibili.protobuf.dm_v2_20 dm_v2_20 = 20;</code>
+   * @return Whether the dmV220 field is set.
+   */
+  boolean hasDmV220();
+  /**
+   * <code>.tech.ordinaryroad.live.chat.client.bilibili.protobuf.dm_v2_20 dm_v2_20 = 20;</code>
+   * @return The dmV220.
+   */
+  dm_v2_20 getDmV220();
+  /**
+   * <code>.tech.ordinaryroad.live.chat.client.bilibili.protobuf.dm_v2_20 dm_v2_20 = 20;</code>
+   */
+  dm_v2_20OrBuilder getDmV220OrBuilder();
+}

+ 565 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/protobuf/dm_v2_20.java

@@ -0,0 +1,565 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: dm_v2_20.proto
+
+package tech.ordinaryroad.live.chat.client.bilibili.protobuf;
+
+/**
+ * Protobuf type {@code tech.ordinaryroad.live.chat.client.bilibili.protobuf.dm_v2_20}
+ */
+public final class dm_v2_20 extends
+    com.google.protobuf.GeneratedMessageV3 implements
+    // @@protoc_insertion_point(message_implements:tech.ordinaryroad.live.chat.client.bilibili.protobuf.dm_v2_20)
+    dm_v2_20OrBuilder {
+private static final long serialVersionUID = 0L;
+  // Use dm_v2_20.newBuilder() to construct.
+  private dm_v2_20(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
+    super(builder);
+  }
+  private dm_v2_20() {
+    avatar_ = "";
+  }
+
+  @Override
+  @SuppressWarnings({"unused"})
+  protected Object newInstance(
+      UnusedPrivateParameter unused) {
+    return new dm_v2_20();
+  }
+
+  public static final com.google.protobuf.Descriptors.Descriptor
+      getDescriptor() {
+    return Dm_v2_20Proto.internal_static_tech_ordinaryroad_live_chat_client_bilibili_protobuf_dm_v2_20_descriptor;
+  }
+
+  @Override
+  protected FieldAccessorTable
+      internalGetFieldAccessorTable() {
+    return Dm_v2_20Proto.internal_static_tech_ordinaryroad_live_chat_client_bilibili_protobuf_dm_v2_20_fieldAccessorTable
+        .ensureFieldAccessorsInitialized(
+            dm_v2_20.class, Builder.class);
+  }
+
+  public static final int AVATAR_FIELD_NUMBER = 4;
+  @SuppressWarnings("serial")
+  private volatile Object avatar_ = "";
+  /**
+   * <code>string avatar = 4;</code>
+   * @return The avatar.
+   */
+  @Override
+  public String getAvatar() {
+    Object ref = avatar_;
+    if (ref instanceof String) {
+      return (String) ref;
+    } else {
+      com.google.protobuf.ByteString bs = 
+          (com.google.protobuf.ByteString) ref;
+      String s = bs.toStringUtf8();
+      avatar_ = s;
+      return s;
+    }
+  }
+  /**
+   * <code>string avatar = 4;</code>
+   * @return The bytes for avatar.
+   */
+  @Override
+  public com.google.protobuf.ByteString
+      getAvatarBytes() {
+    Object ref = avatar_;
+    if (ref instanceof String) {
+      com.google.protobuf.ByteString b = 
+          com.google.protobuf.ByteString.copyFromUtf8(
+              (String) ref);
+      avatar_ = b;
+      return b;
+    } else {
+      return (com.google.protobuf.ByteString) ref;
+    }
+  }
+
+  private byte memoizedIsInitialized = -1;
+  @Override
+  public final boolean isInitialized() {
+    byte isInitialized = memoizedIsInitialized;
+    if (isInitialized == 1) return true;
+    if (isInitialized == 0) return false;
+
+    memoizedIsInitialized = 1;
+    return true;
+  }
+
+  @Override
+  public void writeTo(com.google.protobuf.CodedOutputStream output)
+                      throws java.io.IOException {
+    if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(avatar_)) {
+      com.google.protobuf.GeneratedMessageV3.writeString(output, 4, avatar_);
+    }
+    getUnknownFields().writeTo(output);
+  }
+
+  @Override
+  public int getSerializedSize() {
+    int size = memoizedSize;
+    if (size != -1) return size;
+
+    size = 0;
+    if (!com.google.protobuf.GeneratedMessageV3.isStringEmpty(avatar_)) {
+      size += com.google.protobuf.GeneratedMessageV3.computeStringSize(4, avatar_);
+    }
+    size += getUnknownFields().getSerializedSize();
+    memoizedSize = size;
+    return size;
+  }
+
+  @Override
+  public boolean equals(final Object obj) {
+    if (obj == this) {
+     return true;
+    }
+    if (!(obj instanceof dm_v2_20)) {
+      return super.equals(obj);
+    }
+    dm_v2_20 other = (dm_v2_20) obj;
+
+    if (!getAvatar()
+        .equals(other.getAvatar())) return false;
+    if (!getUnknownFields().equals(other.getUnknownFields())) return false;
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    if (memoizedHashCode != 0) {
+      return memoizedHashCode;
+    }
+    int hash = 41;
+    hash = (19 * hash) + getDescriptor().hashCode();
+    hash = (37 * hash) + AVATAR_FIELD_NUMBER;
+    hash = (53 * hash) + getAvatar().hashCode();
+    hash = (29 * hash) + getUnknownFields().hashCode();
+    memoizedHashCode = hash;
+    return hash;
+  }
+
+  public static dm_v2_20 parseFrom(
+      java.nio.ByteBuffer data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static dm_v2_20 parseFrom(
+      java.nio.ByteBuffer data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static dm_v2_20 parseFrom(
+      com.google.protobuf.ByteString data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static dm_v2_20 parseFrom(
+      com.google.protobuf.ByteString data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static dm_v2_20 parseFrom(byte[] data)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data);
+  }
+  public static dm_v2_20 parseFrom(
+      byte[] data,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws com.google.protobuf.InvalidProtocolBufferException {
+    return PARSER.parseFrom(data, extensionRegistry);
+  }
+  public static dm_v2_20 parseFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static dm_v2_20 parseFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  public static dm_v2_20 parseDelimitedFrom(java.io.InputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input);
+  }
+
+  public static dm_v2_20 parseDelimitedFrom(
+      java.io.InputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseDelimitedWithIOException(PARSER, input, extensionRegistry);
+  }
+  public static dm_v2_20 parseFrom(
+      com.google.protobuf.CodedInputStream input)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input);
+  }
+  public static dm_v2_20 parseFrom(
+      com.google.protobuf.CodedInputStream input,
+      com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+      throws java.io.IOException {
+    return com.google.protobuf.GeneratedMessageV3
+        .parseWithIOException(PARSER, input, extensionRegistry);
+  }
+
+  @Override
+  public Builder newBuilderForType() { return newBuilder(); }
+  public static Builder newBuilder() {
+    return DEFAULT_INSTANCE.toBuilder();
+  }
+  public static Builder newBuilder(dm_v2_20 prototype) {
+    return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype);
+  }
+  @Override
+  public Builder toBuilder() {
+    return this == DEFAULT_INSTANCE
+        ? new Builder() : new Builder().mergeFrom(this);
+  }
+
+  @Override
+  protected Builder newBuilderForType(
+      BuilderParent parent) {
+    Builder builder = new Builder(parent);
+    return builder;
+  }
+  /**
+   * Protobuf type {@code tech.ordinaryroad.live.chat.client.bilibili.protobuf.dm_v2_20}
+   */
+  public static final class Builder extends
+      com.google.protobuf.GeneratedMessageV3.Builder<Builder> implements
+      // @@protoc_insertion_point(builder_implements:tech.ordinaryroad.live.chat.client.bilibili.protobuf.dm_v2_20)
+      dm_v2_20OrBuilder {
+    public static final com.google.protobuf.Descriptors.Descriptor
+        getDescriptor() {
+      return Dm_v2_20Proto.internal_static_tech_ordinaryroad_live_chat_client_bilibili_protobuf_dm_v2_20_descriptor;
+    }
+
+    @Override
+    protected FieldAccessorTable
+        internalGetFieldAccessorTable() {
+      return Dm_v2_20Proto.internal_static_tech_ordinaryroad_live_chat_client_bilibili_protobuf_dm_v2_20_fieldAccessorTable
+          .ensureFieldAccessorsInitialized(
+              dm_v2_20.class, Builder.class);
+    }
+
+    // Construct using tech.ordinaryroad.live.chat.client.bilibili.protobuf.dm_v2_20.newBuilder()
+    private Builder() {
+
+    }
+
+    private Builder(
+        BuilderParent parent) {
+      super(parent);
+
+    }
+    @Override
+    public Builder clear() {
+      super.clear();
+      bitField0_ = 0;
+      avatar_ = "";
+      return this;
+    }
+
+    @Override
+    public com.google.protobuf.Descriptors.Descriptor
+        getDescriptorForType() {
+      return Dm_v2_20Proto.internal_static_tech_ordinaryroad_live_chat_client_bilibili_protobuf_dm_v2_20_descriptor;
+    }
+
+    @Override
+    public dm_v2_20 getDefaultInstanceForType() {
+      return dm_v2_20.getDefaultInstance();
+    }
+
+    @Override
+    public dm_v2_20 build() {
+      dm_v2_20 result = buildPartial();
+      if (!result.isInitialized()) {
+        throw newUninitializedMessageException(result);
+      }
+      return result;
+    }
+
+    @Override
+    public dm_v2_20 buildPartial() {
+      dm_v2_20 result = new dm_v2_20(this);
+      if (bitField0_ != 0) { buildPartial0(result); }
+      onBuilt();
+      return result;
+    }
+
+    private void buildPartial0(dm_v2_20 result) {
+      int from_bitField0_ = bitField0_;
+      if (((from_bitField0_ & 0x00000001) != 0)) {
+        result.avatar_ = avatar_;
+      }
+    }
+
+    @Override
+    public Builder clone() {
+      return super.clone();
+    }
+    @Override
+    public Builder setField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        Object value) {
+      return super.setField(field, value);
+    }
+    @Override
+    public Builder clearField(
+        com.google.protobuf.Descriptors.FieldDescriptor field) {
+      return super.clearField(field);
+    }
+    @Override
+    public Builder clearOneof(
+        com.google.protobuf.Descriptors.OneofDescriptor oneof) {
+      return super.clearOneof(oneof);
+    }
+    @Override
+    public Builder setRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        int index, Object value) {
+      return super.setRepeatedField(field, index, value);
+    }
+    @Override
+    public Builder addRepeatedField(
+        com.google.protobuf.Descriptors.FieldDescriptor field,
+        Object value) {
+      return super.addRepeatedField(field, value);
+    }
+    @Override
+    public Builder mergeFrom(com.google.protobuf.Message other) {
+      if (other instanceof dm_v2_20) {
+        return mergeFrom((dm_v2_20)other);
+      } else {
+        super.mergeFrom(other);
+        return this;
+      }
+    }
+
+    public Builder mergeFrom(dm_v2_20 other) {
+      if (other == dm_v2_20.getDefaultInstance()) return this;
+      if (!other.getAvatar().isEmpty()) {
+        avatar_ = other.avatar_;
+        bitField0_ |= 0x00000001;
+        onChanged();
+      }
+      this.mergeUnknownFields(other.getUnknownFields());
+      onChanged();
+      return this;
+    }
+
+    @Override
+    public final boolean isInitialized() {
+      return true;
+    }
+
+    @Override
+    public Builder mergeFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws java.io.IOException {
+      if (extensionRegistry == null) {
+        throw new NullPointerException();
+      }
+      try {
+        boolean done = false;
+        while (!done) {
+          int tag = input.readTag();
+          switch (tag) {
+            case 0:
+              done = true;
+              break;
+            case 34: {
+              avatar_ = input.readStringRequireUtf8();
+              bitField0_ |= 0x00000001;
+              break;
+            } // case 34
+            default: {
+              if (!super.parseUnknownField(input, extensionRegistry, tag)) {
+                done = true; // was an endgroup tag
+              }
+              break;
+            } // default:
+          } // switch (tag)
+        } // while (!done)
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.unwrapIOException();
+      } finally {
+        onChanged();
+      } // finally
+      return this;
+    }
+    private int bitField0_;
+
+    private Object avatar_ = "";
+    /**
+     * <code>string avatar = 4;</code>
+     * @return The avatar.
+     */
+    public String getAvatar() {
+      Object ref = avatar_;
+      if (!(ref instanceof String)) {
+        com.google.protobuf.ByteString bs =
+            (com.google.protobuf.ByteString) ref;
+        String s = bs.toStringUtf8();
+        avatar_ = s;
+        return s;
+      } else {
+        return (String) ref;
+      }
+    }
+    /**
+     * <code>string avatar = 4;</code>
+     * @return The bytes for avatar.
+     */
+    public com.google.protobuf.ByteString
+        getAvatarBytes() {
+      Object ref = avatar_;
+      if (ref instanceof String) {
+        com.google.protobuf.ByteString b = 
+            com.google.protobuf.ByteString.copyFromUtf8(
+                (String) ref);
+        avatar_ = b;
+        return b;
+      } else {
+        return (com.google.protobuf.ByteString) ref;
+      }
+    }
+    /**
+     * <code>string avatar = 4;</code>
+     * @param value The avatar to set.
+     * @return This builder for chaining.
+     */
+    public Builder setAvatar(
+        String value) {
+      if (value == null) { throw new NullPointerException(); }
+      avatar_ = value;
+      bitField0_ |= 0x00000001;
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>string avatar = 4;</code>
+     * @return This builder for chaining.
+     */
+    public Builder clearAvatar() {
+      avatar_ = getDefaultInstance().getAvatar();
+      bitField0_ = (bitField0_ & ~0x00000001);
+      onChanged();
+      return this;
+    }
+    /**
+     * <code>string avatar = 4;</code>
+     * @param value The bytes for avatar to set.
+     * @return This builder for chaining.
+     */
+    public Builder setAvatarBytes(
+        com.google.protobuf.ByteString value) {
+      if (value == null) { throw new NullPointerException(); }
+      checkByteStringIsUtf8(value);
+      avatar_ = value;
+      bitField0_ |= 0x00000001;
+      onChanged();
+      return this;
+    }
+    @Override
+    public final Builder setUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.setUnknownFields(unknownFields);
+    }
+
+    @Override
+    public final Builder mergeUnknownFields(
+        final com.google.protobuf.UnknownFieldSet unknownFields) {
+      return super.mergeUnknownFields(unknownFields);
+    }
+
+
+    // @@protoc_insertion_point(builder_scope:tech.ordinaryroad.live.chat.client.bilibili.protobuf.dm_v2_20)
+  }
+
+  // @@protoc_insertion_point(class_scope:tech.ordinaryroad.live.chat.client.bilibili.protobuf.dm_v2_20)
+  private static final dm_v2_20 DEFAULT_INSTANCE;
+  static {
+    DEFAULT_INSTANCE = new dm_v2_20();
+  }
+
+  public static dm_v2_20 getDefaultInstance() {
+    return DEFAULT_INSTANCE;
+  }
+
+  private static final com.google.protobuf.Parser<dm_v2_20>
+      PARSER = new com.google.protobuf.AbstractParser<dm_v2_20>() {
+    @Override
+    public dm_v2_20 parsePartialFrom(
+        com.google.protobuf.CodedInputStream input,
+        com.google.protobuf.ExtensionRegistryLite extensionRegistry)
+        throws com.google.protobuf.InvalidProtocolBufferException {
+      Builder builder = newBuilder();
+      try {
+        builder.mergeFrom(input, extensionRegistry);
+      } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(builder.buildPartial());
+      } catch (com.google.protobuf.UninitializedMessageException e) {
+        throw e.asInvalidProtocolBufferException().setUnfinishedMessage(builder.buildPartial());
+      } catch (java.io.IOException e) {
+        throw new com.google.protobuf.InvalidProtocolBufferException(e)
+            .setUnfinishedMessage(builder.buildPartial());
+      }
+      return builder.buildPartial();
+    }
+  };
+
+  public static com.google.protobuf.Parser<dm_v2_20> parser() {
+    return PARSER;
+  }
+
+  @Override
+  public com.google.protobuf.Parser<dm_v2_20> getParserForType() {
+    return PARSER;
+  }
+
+  @Override
+  public dm_v2_20 getDefaultInstanceForType() {
+    return DEFAULT_INSTANCE;
+  }
+
+}
+

+ 45 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/protobuf/dm_v2_20OrBuilder.java

@@ -0,0 +1,45 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: dm_v2_20.proto
+
+package tech.ordinaryroad.live.chat.client.bilibili.protobuf;
+
+public interface dm_v2_20OrBuilder extends
+    // @@protoc_insertion_point(interface_extends:tech.ordinaryroad.live.chat.client.bilibili.protobuf.dm_v2_20)
+    com.google.protobuf.MessageOrBuilder {
+
+  /**
+   * <code>string avatar = 4;</code>
+   * @return The avatar.
+   */
+  String getAvatar();
+  /**
+   * <code>string avatar = 4;</code>
+   * @return The bytes for avatar.
+   */
+  com.google.protobuf.ByteString
+      getAvatarBytes();
+}

+ 259 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/java/tech/ordinaryroad/live/chat/client/bilibili/util/BilibiliCodecUtil.java

@@ -0,0 +1,259 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.bilibili.util;
+
+import cn.hutool.core.util.StrUtil;
+import com.aayushatharva.brotli4j.Brotli4jLoader;
+import com.aayushatharva.brotli4j.decoder.BrotliInputStream;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import lombok.extern.slf4j.Slf4j;
+import tech.ordinaryroad.live.chat.client.bilibili.constant.OperationEnum;
+import tech.ordinaryroad.live.chat.client.bilibili.constant.ProtoverEnum;
+import tech.ordinaryroad.live.chat.client.bilibili.msg.AuthReplyMsg;
+import tech.ordinaryroad.live.chat.client.bilibili.msg.HeartbeatMsg;
+import tech.ordinaryroad.live.chat.client.bilibili.msg.HeartbeatReplyMsg;
+import tech.ordinaryroad.live.chat.client.bilibili.msg.SendSmsReplyMsg;
+import tech.ordinaryroad.live.chat.client.bilibili.msg.base.BaseBilibiliMsg;
+import tech.ordinaryroad.live.chat.client.bilibili.msg.base.IBilibiliMsg;
+import tech.ordinaryroad.live.chat.client.commons.base.exception.BaseException;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+import java.util.zip.DataFormatException;
+import java.util.zip.Inflater;
+
+/**
+ * @author mjz
+ * @date 2023/1/6
+ */
+@Slf4j
+public class BilibiliCodecUtil {
+
+    public static int sequence = 0;
+
+    public static final short FRAME_HEADER_LENGTH = 16;
+
+    public static ByteBuf encode(BaseBilibiliMsg msg) {
+        ByteBuf out = Unpooled.buffer(FRAME_HEADER_LENGTH);
+        String bodyJsonString = StrUtil.EMPTY;
+        // HeartbeatMsg不需要正文,如果序列化后得到`{}`,则替换为空字符串
+        if (!(msg instanceof HeartbeatMsg)) {
+            bodyJsonString = msg.toString();
+            if (StrUtil.EMPTY_JSON.equals(bodyJsonString)) {
+                bodyJsonString = StrUtil.EMPTY;
+            }
+        }
+        byte[] bodyBytes = bodyJsonString.getBytes(StandardCharsets.UTF_8);
+        int length = bodyBytes.length + FRAME_HEADER_LENGTH;
+        out.writeInt(length);
+        out.writeShort(FRAME_HEADER_LENGTH);
+        out.writeShort(msg.getProtoverEnum().getCode());
+        out.writeInt(msg.getOperationEnum().getCode());
+        out.writeInt(sequence++);
+        out.writeBytes(bodyBytes);
+        return out;
+    }
+
+    public static List<IBilibiliMsg> decode(ByteBuf in) {
+        List<IBilibiliMsg> msgList = new ArrayList<>();
+        Queue<ByteBuf> pendingByteBuf = new LinkedList<>();
+
+        do {
+            Optional<IBilibiliMsg> msg = doDecode(in, pendingByteBuf);
+            msg.ifPresent(msgList::add);
+            in = pendingByteBuf.poll();
+        } while (in != null);
+
+        return msgList;
+    }
+
+    /**
+     * 执行解码操作,有压缩则先解压,解压后可能得到多条消息
+     *
+     * @param in             handler收到的一条消息
+     * @param pendingByteBuf 用于存放未读取完的ByteBuf
+     * @return Optional<IBilibiliMsg> 何时为空值:不支持的{@link OperationEnum},不支持的{@link ProtoverEnum},{@link #parse(OperationEnum, String)}反序列化失败
+     * @see OperationEnum
+     * @see ProtoverEnum
+     */
+    private static Optional<IBilibiliMsg> doDecode(ByteBuf in, Queue<ByteBuf> pendingByteBuf) {
+        int length = in.readInt();
+        short frameHeaderLength = in.readShort();
+        short protoverCode = in.readShort();
+        int operationCode = in.readInt();
+        int sequence = in.readInt();
+        int contentLength = length - frameHeaderLength;
+        byte[] inputBytes = new byte[contentLength];
+        in.readBytes(inputBytes);
+        if (in.readableBytes() != 0) {
+            // log.error("in.readableBytes() {}", in.readableBytes());
+            pendingByteBuf.offer(in);
+        }
+
+        OperationEnum operationEnum = OperationEnum.getByCode(operationCode);
+        if (operationEnum == null) {
+            throw new BaseException(String.format("未知operation: %d", operationCode));
+        }
+        if (protoverCode == ProtoverEnum.NORMAL_ZLIB.getCode()) {
+            switch (operationEnum) {
+                case SEND_SMS_REPLY: {
+                    // Decompress the bytes
+                    Inflater inflater = new Inflater();
+                    inflater.reset();
+                    inflater.setInput(inputBytes);
+                    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(contentLength);
+                    try {
+                        byte[] bytes = new byte[1024];
+                        while (!inflater.finished()) {
+                            int count = inflater.inflate(bytes);
+                            byteArrayOutputStream.write(bytes, 0, count);
+                        }
+                    } catch (DataFormatException e) {
+                        throw new BaseException(e);
+                    }
+                    inflater.end();
+
+                    return doDecode(Unpooled.wrappedBuffer(byteArrayOutputStream.toByteArray()), pendingByteBuf);
+                }
+                case HEARTBEAT_REPLY: {
+                    BigInteger bigInteger = new BigInteger(inputBytes);
+                    return parse(operationEnum, String.format("{\"popularity\":%d}", bigInteger));
+                }
+                default: {
+                    String s = new String(inputBytes, StandardCharsets.UTF_8);
+                    return parse(operationEnum, s);
+                }
+            }
+        } else if (protoverCode == ProtoverEnum.NORMAL_NO_COMPRESSION.getCode()) {
+            switch (operationEnum) {
+                case HEARTBEAT_REPLY: {
+                    BigInteger bigInteger = new BigInteger(inputBytes);
+                    return parse(operationEnum, String.format("{\"popularity\":%d}", bigInteger));
+                }
+                default: {
+                    String s = new String(inputBytes, StandardCharsets.UTF_8);
+                    return parse(operationEnum, s);
+                }
+            }
+        } else if (protoverCode == ProtoverEnum.HEARTBEAT_AUTH_NO_COMPRESSION.getCode()) {
+            switch (operationEnum) {
+                case HEARTBEAT_REPLY: {
+                    BigInteger bigInteger = new BigInteger(inputBytes);
+                    return parse(operationEnum, String.format("{\"popularity\":%d}", bigInteger));
+                }
+                default: {
+                    String s = new String(inputBytes, StandardCharsets.UTF_8);
+                    return parse(operationEnum, s);
+                }
+            }
+        } else if (protoverCode == ProtoverEnum.NORMAL_BROTLI.getCode()) {
+            switch (operationEnum) {
+                case SEND_SMS_REPLY: {
+                    // Load the native library
+                    Brotli4jLoader.ensureAvailability();
+
+                    ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(inputBytes);
+                    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(contentLength);
+                    byte[] bytes = new byte[1024];
+                    BrotliInputStream brotliInputStream = null;
+                    ByteBuf wrappedBuffer = null;
+                    try {
+                        brotliInputStream = new BrotliInputStream(byteArrayInputStream);
+                        int count;
+                        while ((count = brotliInputStream.read(bytes)) > -1) {
+                            byteArrayOutputStream.write(bytes, 0, count);
+                        }
+                        wrappedBuffer = Unpooled.wrappedBuffer(byteArrayOutputStream.toByteArray());
+                    } catch (IOException e) {
+                        throw new BaseException(e);
+                    } finally {
+                        try {
+                            // Close the BrotliInputStream. This also closes the InputStream.
+                            if (brotliInputStream != null) {
+                                brotliInputStream.close();
+                            }
+                            byteArrayOutputStream.close();
+                        } catch (IOException e) {
+                            log.error("解压失败", e);
+                        }
+                    }
+                    return doDecode(wrappedBuffer, pendingByteBuf);
+                }
+                case HEARTBEAT_REPLY: {
+                    BigInteger bigInteger = new BigInteger(inputBytes);
+                    return parse(operationEnum, String.format("{\"popularity\":%d}", bigInteger));
+                }
+                default: {
+                    String s = new String(inputBytes, StandardCharsets.UTF_8);
+                    return parse(operationEnum, s);
+                }
+            }
+        } else {
+            if (log.isWarnEnabled()) {
+                log.warn("暂不支持的版本:{}", protoverCode);
+            }
+            return Optional.empty();
+        }
+    }
+
+    public static Optional<IBilibiliMsg> parse(OperationEnum operation, String jsonString) {
+        switch (operation) {
+            case SEND_SMS_REPLY: {
+                try {
+                    return Optional.ofNullable(BaseBilibiliMsg.OBJECT_MAPPER.readValue(jsonString, SendSmsReplyMsg.class));
+                } catch (JsonProcessingException e) {
+                    throw new BaseException(e);
+                }
+            }
+            case AUTH_REPLY: {
+                try {
+                    return Optional.ofNullable(BaseBilibiliMsg.OBJECT_MAPPER.readValue(jsonString, AuthReplyMsg.class));
+                } catch (JsonProcessingException e) {
+                    throw new BaseException(e);
+                }
+            }
+            case HEARTBEAT_REPLY: {
+                try {
+                    return Optional.ofNullable(BaseBilibiliMsg.OBJECT_MAPPER.readValue(jsonString, HeartbeatReplyMsg.class));
+                } catch (JsonProcessingException e) {
+                    throw new BaseException(e);
+                }
+            }
+            default: {
+                if (log.isWarnEnabled()) {
+                    log.warn("暂不支持 {}", operation);
+                }
+                return Optional.empty();
+            }
+        }
+    }
+
+}

+ 14 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/resources/proto/dm_v2.proto

@@ -0,0 +1,14 @@
+syntax = "proto3";
+
+package tech.ordinaryroad.live.chat.client.bilibili.protobuf;
+
+option java_package = "tech.ordinaryroad.live.chat.client.bilibili.protobuf";
+option java_outer_classname = "Dm_v2Proto";
+option java_multiple_files = true;
+option objc_class_prefix = "GPB";
+
+import "dm_v2_20.proto";
+
+message dm_v2 {
+  dm_v2_20 dm_v2_20 = 20;
+}

+ 12 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/main/resources/proto/dm_v2_20.proto

@@ -0,0 +1,12 @@
+syntax = "proto3";
+
+package tech.ordinaryroad.live.chat.client.bilibili.protobuf;
+
+option java_package = "tech.ordinaryroad.live.chat.client.bilibili.protobuf";
+option java_outer_classname = "Dm_v2_20Proto";
+option java_multiple_files = true;
+option objc_class_prefix = "GPB";
+
+message dm_v2_20 {
+  string avatar = 4;
+}

+ 16 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/test/java/tech/ordinaryroad/live/chat/client/bilibili/api/BilibiliApisTest.java

@@ -0,0 +1,16 @@
+package tech.ordinaryroad.live.chat.client.bilibili.api;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * @author mjz
+ * @date 2023/9/7
+ */
+class BilibiliApisTest {
+
+    @Test
+    void sendMsg() {
+        String cookie = System.getenv("cookie");
+        BilibiliApis.sendMsg("666", 545068, cookie);
+    }
+}

+ 176 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-bilibili/src/test/java/tech/ordinaryroad/live/chat/client/bilibili/client/BilibiliLiveChatClientTest.java

@@ -0,0 +1,176 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.bilibili.client;
+
+import cn.hutool.core.thread.ThreadUtil;
+import com.fasterxml.jackson.databind.JsonNode;
+import lombok.extern.slf4j.Slf4j;
+import org.junit.jupiter.api.Test;
+import tech.ordinaryroad.live.chat.client.bilibili.config.BilibiliLiveChatClientConfig;
+import tech.ordinaryroad.live.chat.client.bilibili.constant.BilibiliCmdEnum;
+import tech.ordinaryroad.live.chat.client.bilibili.listener.IBilibiliMsgListener;
+import tech.ordinaryroad.live.chat.client.bilibili.msg.*;
+import tech.ordinaryroad.live.chat.client.bilibili.netty.handler.BilibiliBinaryFrameHandler;
+import tech.ordinaryroad.live.chat.client.commons.base.msg.ICmdMsg;
+import tech.ordinaryroad.live.chat.client.commons.base.msg.IMsg;
+import tech.ordinaryroad.live.chat.client.commons.client.enums.ClientStatusEnums;
+
+/**
+ * @author mjz
+ * @date 2023/8/26
+ */
+@Slf4j
+class BilibiliLiveChatClientTest {
+
+    static Object lock = new Object();
+    BilibiliLiveChatClient client;
+
+    @Test
+    void example() throws InterruptedException {
+        String cookie = System.getenv("cookie");
+        log.error("cookie: {}", cookie);
+        BilibiliLiveChatClientConfig config = BilibiliLiveChatClientConfig.builder()
+                // TODO 浏览器Cookie
+                .cookie(cookie)
+                .roomId(7777)
+                .roomId(697)
+                .build();
+
+        client = new BilibiliLiveChatClient(config, new IBilibiliMsgListener() {
+            @Override
+            public void onDanmuMsg(BilibiliBinaryFrameHandler binaryFrameHandler, DanmuMsgMsg msg) {
+                IBilibiliMsgListener.super.onDanmuMsg(binaryFrameHandler, msg);
+                log.info("{} 收到弹幕 {} {}({}):{}", binaryFrameHandler.getRoomId(), msg.getBadgeLevel() != 0 ? msg.getBadgeLevel() + msg.getBadgeName() : "", msg.getUsername(), msg.getUid(), msg.getContent());
+            }
+
+            @Override
+            public void onGiftMsg(BilibiliBinaryFrameHandler binaryFrameHandler, SendGiftMsg msg) {
+                IBilibiliMsgListener.super.onGiftMsg(binaryFrameHandler, msg);
+                log.info("{} 收到礼物 {} {}({}) {} {}({})x{}({})", binaryFrameHandler.getRoomId(), msg.getBadgeLevel() != 0 ? msg.getBadgeLevel() + msg.getBadgeName() : "", msg.getUsername(), msg.getUid(), msg.getData().getAction(), msg.getGiftName(), msg.getGiftId(), msg.getGiftCount(), msg.getGiftPrice());
+            }
+
+            @Override
+            public void onSuperChatMsg(BilibiliBinaryFrameHandler binaryFrameHandler, SuperChatMessageMsg msg) {
+                IBilibiliMsgListener.super.onSuperChatMsg(binaryFrameHandler, msg);
+                log.info("{} 收到醒目留言 {}({}):{}", binaryFrameHandler.getRoomId(), msg.getUsername(), msg.getUid(), msg.getContent());
+            }
+
+            @Override
+            public void onEnterRoomMsg(InteractWordMsg msg) {
+                log.info("{} {}({}) 进入直播间", msg.getBadgeLevel() != 0 ? msg.getBadgeLevel() + msg.getBadgeName() : "", msg.getUsername(), msg.getUid());
+            }
+
+            @Override
+            public void onLikeMsg(BilibiliBinaryFrameHandler binaryFrameHandler, LikeInfoV3ClickMsg msg) {
+                IBilibiliMsgListener.super.onLikeMsg(binaryFrameHandler, msg);
+                log.info("{} 收到点赞 {} {}({})", binaryFrameHandler.getRoomId(), msg.getBadgeLevel() != 0 ? msg.getBadgeLevel() + msg.getBadgeName() : "", msg.getUsername(), msg.getUid());
+            }
+
+            @Override
+            public void onEntryEffect(SendSmsReplyMsg msg) {
+                JsonNode data = msg.getData();
+                String copyWriting = data.get("copy_writing").asText();
+                log.info("入场效果 {}", copyWriting);
+            }
+
+            @Override
+            public void onWatchedChange(SendSmsReplyMsg msg) {
+                JsonNode data = msg.getData();
+                int num = data.get("num").asInt();
+                String textSmall = data.get("text_small").asText();
+                String textLarge = data.get("text_large").asText();
+                log.debug("观看人数变化 {} {} {}", num, textSmall, textLarge);
+            }
+
+            @Override
+            public void onClickLike(SendSmsReplyMsg msg) {
+                JsonNode data = msg.getData();
+                String uname = data.get("uname").asText();
+                String likeText = data.get("like_text").asText();
+                log.debug("为主播点赞 {} {}", uname, likeText);
+            }
+
+            @Override
+            public void onClickUpdate(SendSmsReplyMsg msg) {
+                JsonNode data = msg.getData();
+                int clickCount = data.get("click_count").asInt();
+                log.debug("点赞数更新 {}", clickCount);
+            }
+
+            @Override
+            public void onMsg(IMsg msg) {
+                log.debug("收到{}消息 {}", msg.getClass(), msg);
+            }
+
+            @Override
+            public void onCmdMsg(BilibiliCmdEnum cmd, ICmdMsg<BilibiliCmdEnum> cmdMsg) {
+                log.debug("收到CMD消息{} {}", cmd, cmdMsg);
+            }
+
+            @Override
+            public void onOtherCmdMsg(BilibiliCmdEnum cmd, ICmdMsg<BilibiliCmdEnum> cmdMsg) {
+//                log.debug("收到其他CMD消息 {}", cmd);
+                switch (cmd) {
+                    case GUARD_BUY: {
+                        // 有人上舰
+                        SendSmsReplyMsg sendSmsReplyMsg = (SendSmsReplyMsg) cmdMsg;
+                        break;
+                    }
+                    case SUPER_CHAT_MESSAGE_DELETE: {
+                        // 删除醒目留言
+                        SendSmsReplyMsg sendSmsReplyMsg = (SendSmsReplyMsg) cmdMsg;
+                        break;
+                    }
+                }
+            }
+
+            @Override
+            public void onUnknownCmd(String cmdString, IMsg msg) {
+                log.debug("收到未知CMD消息 {}", cmdString);
+            }
+        });
+        client.connect();
+
+        client.addStatusChangeListener(evt -> {
+            ClientStatusEnums newValue = (ClientStatusEnums) evt.getNewValue();
+            if (newValue == ClientStatusEnums.CONNECTED) {
+                ThreadUtil.execAsync(() -> {
+                    ThreadUtil.sleep(5000);
+                    client.clickLike(5, () -> {
+                        log.warn("为主播点赞成功");
+                    });
+                });
+            }
+        });
+
+        // 防止测试时直接退出
+        while (true) {
+            synchronized (lock) {
+                lock.wait();
+            }
+        }
+    }
+
+}

+ 42 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-douyin/pom.xml

@@ -0,0 +1,42 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.ruoyi</groupId>
+        <artifactId>live-chat-clients</artifactId>
+        <version>${revision}</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+    <packaging>jar</packaging>
+
+    <artifactId>live-chat-client-douyin</artifactId>
+    <name>live-chat-client-douyin</name>
+
+    <properties>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.ruoyi</groupId>
+            <artifactId>live-chat-client-servers-netty-client</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>com.google.protobuf</groupId>
+            <artifactId>protobuf-java-util</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>ch.qos.logback</groupId>
+            <artifactId>logback-classic</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.junit.jupiter</groupId>
+            <artifactId>junit-jupiter</artifactId>
+            <version>${junit-jupiter.version}</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+</project>

+ 81 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-douyin/src/main/java/tech/ordinaryroad/live/chat/client/douyin/ClientModeExample.java

@@ -0,0 +1,81 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.douyin;
+
+import cn.hutool.core.util.RandomUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import tech.ordinaryroad.live.chat.client.commons.base.msg.IMsg;
+import tech.ordinaryroad.live.chat.client.commons.client.enums.ClientStatusEnums;
+import tech.ordinaryroad.live.chat.client.douyin.client.DouyinLiveChatClient;
+import tech.ordinaryroad.live.chat.client.douyin.config.DouyinLiveChatClientConfig;
+import tech.ordinaryroad.live.chat.client.douyin.listener.IDouyinMsgListener;
+import tech.ordinaryroad.live.chat.client.douyin.msg.DouyinDanmuMsg;
+import tech.ordinaryroad.live.chat.client.douyin.netty.handler.DouyinBinaryFrameHandler;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+
+public class ClientModeExample {
+    static Logger log = LoggerFactory.getLogger(ClientModeExample.class);
+
+    public static void main(String[] args) {
+        // 1. 创建配置
+        DouyinLiveChatClientConfig config = DouyinLiveChatClientConfig.builder()
+                // TODO 浏览器Cookie
+                .cookie("did=web_6c4ac2a8ef8855d35df4d564baeaa8e8; kuaishou.live.bfb1s=7206d814e5c089a58c910ed8bf52ace5; clientid=3; did=web_6c4ac2a8ef8855d35df4d564baeaa8e8; client_key=65890b29; kpn=GAME_ZONE; userId=3941614875; kuaishou.live.web_st=ChRrdWFpc2hvdS5saXZlLndlYi5zdBKgAbRhgemDxM_Z_lBn7EZ_-5unvtslsh7ci5VY_eg80qqjxy5yb7oBFrdcWSPlz6jMIBz9h_yoLzATE3ngj2WawpxvbJhmq0EnRYIHv308kTBmg4KN2JQf7w2mfrsp1vusFbZ3NkfsEO4PrGQfX0L6mJRbgXeBqyz4tUM5O0q2Jte_NzWkaOnezvIGRAG8Y6yA1dxGUmiA9syTrPrdnSOzZvAaEoJNhwQ4OUDtgURWN6k9Xgm8PSIgAfV-ZvahtgaYhopZno6OuS2pkaFZjrz4ymoEZ1DSnj0oBTAB; kuaishou.live.web_ph=a287be6ab01dce264c0554eed94c2d6ac991; userId=3941614875")
+                // TODO 直播间id(支持短id)
+                .roomId("Jiazi-9931")
+                .build();
+
+        // 2. 创建Client并传入配置、添加消息回调
+        DouyinLiveChatClient client = new DouyinLiveChatClient(config, new IDouyinMsgListener() {
+//            @Override
+//            public void onMsg(IMsg msg) {
+//                log.debug("收到{}消息 {}", msg.getClass(), msg);
+//            }
+//            @Override
+//            public void onUnknownCmd(String cmdString, IMsg msg) {
+//                log.debug("收到未知CMD消息 {}", cmdString);
+//            }
+
+            @Override
+            public void onDanmuMsg(DouyinBinaryFrameHandler douyinBinaryFrameHandler, DouyinDanmuMsg msg) {
+                log.info("{} 收到弹幕 [{}] {}({}):{}", douyinBinaryFrameHandler.getRoomId(), msg.getBadgeLevel() != 0 ? msg.getBadgeLevel() + msg.getBadgeName() : "", msg.getUsername(), msg.getUid(), msg.getContent());
+            }
+
+        });
+        // 3. 开始监听直播间
+        client.connect();
+
+        // 客户端连接状态回调
+//        client.addStatusChangeListener(evt -> {
+//            if (evt.getNewValue().equals(ClientStatusEnums.CONNECTED)) {
+//                // TODO 要发送的弹幕内容,请注意控制发送频率;框架内置支持设置发送弹幕的最少时间间隔,小于时将忽略该次发送
+//                client.sendDanmu("666666" + RandomUtil.randomNumbers(1));
+//            }
+//        });
+    }
+}

+ 108 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-douyin/src/main/java/tech/ordinaryroad/live/chat/client/douyin/api/DouyinApis.java

@@ -0,0 +1,108 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.douyin.api;
+
+import cn.hutool.core.util.NumberUtil;
+import cn.hutool.core.util.RandomUtil;
+import cn.hutool.core.util.ReUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.http.HttpResponse;
+import cn.hutool.http.HttpStatus;
+import cn.hutool.http.HttpUtil;
+import lombok.*;
+import lombok.extern.slf4j.Slf4j;
+import tech.ordinaryroad.live.chat.client.commons.base.exception.BaseException;
+import tech.ordinaryroad.live.chat.client.commons.util.OrLiveChatCookieUtil;
+
+import java.util.Map;
+
+/**
+ * @author mjz
+ * @date 2024/1/3
+ */
+@Slf4j
+public class DouyinApis {
+
+    public static final String KEY_COOKIE_TTWID = "ttwid";
+    public static final String KEY_COOKIE_MS_TOKEN = "msToken";
+    public static final String KEY_COOKIE_AC_NONCE = "__ac_nonce";
+    public static final String MS_TOKEN_BASE_STRING = RandomUtil.BASE_CHAR_NUMBER_LOWER + "=_";
+    public static final int MS_TOKEN_LENGTH = 107;
+    public static final int AC_NONCE_LENGTH = 21;
+    public static final String PATTERN_USER_UNIQUE_ID = "\\\\\"user_unique_id\\\\\":\\\\\"(\\d+)\\\\\"";
+    public static final String PATTERN_ROOM_ID = "\\\\\"roomId\\\\\":\\\\\"(\\d+)\\\\\"";
+
+    public static RoomInitResult roomInit(Object roomId, String cookie) {
+        Map<String, String> cookieMap = OrLiveChatCookieUtil.parseCookieString(cookie);
+
+        @Cleanup
+        HttpResponse response1 = HttpUtil.createGet("https://live.douyin.com/").cookie(cookie).execute();
+        String ttwid = OrLiveChatCookieUtil.getCookieByName(cookieMap, KEY_COOKIE_TTWID, () -> response1.getCookie(KEY_COOKIE_TTWID).getValue());
+        String msToken = OrLiveChatCookieUtil.getCookieByName(cookieMap, KEY_COOKIE_MS_TOKEN, () -> RandomUtil.randomString(MS_TOKEN_BASE_STRING, MS_TOKEN_LENGTH));
+        String __ac_nonce = OrLiveChatCookieUtil.getCookieByName(cookieMap, KEY_COOKIE_AC_NONCE, () -> RandomUtil.randomString(AC_NONCE_LENGTH));
+
+        @Cleanup
+        HttpResponse response2 = HttpUtil.createGet("https://live.douyin.com/" + roomId)
+                .cookie(StrUtil.emptyToDefault(cookie, KEY_COOKIE_TTWID + "=" + ttwid + "; " + KEY_COOKIE_MS_TOKEN + "=" + msToken + "; " + KEY_COOKIE_AC_NONCE + "=" + __ac_nonce))
+                .execute();
+        if (response2.getStatus() != HttpStatus.HTTP_OK) {
+            throw new BaseException("获取" + roomId + "真实房间ID失败");
+        }
+        String user_unique_id = StrUtil.emptyToDefault(ReUtil.getGroup1(PATTERN_USER_UNIQUE_ID, response2.body()), RandomUtil.randomNumbers(19));
+        long realRoomId;
+        String realRoomIdString = ReUtil.getGroup1(PATTERN_ROOM_ID, response2.body());
+        try {
+            realRoomId = NumberUtil.parseLong(realRoomIdString);
+        } catch (Exception e) {
+            throw new BaseException("获取" + roomId + "真实房间ID失败");
+        }
+
+
+        return RoomInitResult.builder()
+                .ttwid(ttwid)
+                .msToken(msToken)
+                .acNonce(__ac_nonce)
+                .realRoomId(realRoomId)
+                .userUniqueId(user_unique_id)
+                .build();
+    }
+
+    public static RoomInitResult roomInit(Object roomId) {
+        return roomInit(roomId, null);
+    }
+
+    @Getter
+    @Setter
+    @AllArgsConstructor
+    @NoArgsConstructor
+    @Builder
+    public static class RoomInitResult {
+        private String ttwid;
+        private String msToken;
+        private String acNonce;
+        private long realRoomId;
+        private String userUniqueId;
+    }
+}

+ 174 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-douyin/src/main/java/tech/ordinaryroad/live/chat/client/douyin/client/DouyinLiveChatClient.java

@@ -0,0 +1,174 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.douyin.client;
+
+import cn.hutool.core.date.DateUtil;
+import cn.hutool.core.util.RandomUtil;
+import cn.hutool.http.GlobalHeaders;
+import cn.hutool.http.Header;
+import cn.hutool.http.HttpUtil;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.handler.codec.http.DefaultHttpHeaders;
+import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory;
+import io.netty.handler.codec.http.websocketx.WebSocketVersion;
+import lombok.extern.slf4j.Slf4j;
+import tech.ordinaryroad.live.chat.client.commons.base.listener.IBaseConnectionListener;
+import tech.ordinaryroad.live.chat.client.douyin.api.DouyinApis;
+import tech.ordinaryroad.live.chat.client.douyin.config.DouyinLiveChatClientConfig;
+import tech.ordinaryroad.live.chat.client.douyin.constant.DouyinCmdEnum;
+import tech.ordinaryroad.live.chat.client.douyin.listener.IDouyinConnectionListener;
+import tech.ordinaryroad.live.chat.client.douyin.listener.IDouyinMsgListener;
+import tech.ordinaryroad.live.chat.client.douyin.msg.base.IDouyinMsg;
+import tech.ordinaryroad.live.chat.client.douyin.netty.handler.DouyinBinaryFrameHandler;
+import tech.ordinaryroad.live.chat.client.douyin.netty.handler.DouyinConnectionHandler;
+import tech.ordinaryroad.live.chat.client.servers.netty.client.base.BaseNettyClient;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+
+/**
+ * @author mjz
+ * @date 2024/1/2
+ */
+@Slf4j
+public class DouyinLiveChatClient extends BaseNettyClient<
+        DouyinLiveChatClientConfig,
+        DouyinCmdEnum,
+        IDouyinMsg,
+        IDouyinMsgListener,
+        DouyinConnectionHandler,
+        DouyinBinaryFrameHandler> {
+
+    private DouyinApis.RoomInitResult roomInitResult = new DouyinApis.RoomInitResult();
+
+    public DouyinLiveChatClient(DouyinLiveChatClientConfig config, List<IDouyinMsgListener> msgListeners, IDouyinConnectionListener connectionListener, EventLoopGroup workerGroup) {
+        super(config, workerGroup, connectionListener);
+        addMsgListeners(msgListeners);
+
+        // 初始化
+        this.init();
+    }
+
+    public DouyinLiveChatClient(DouyinLiveChatClientConfig config, IDouyinMsgListener msgListener, IDouyinConnectionListener connectionListener, EventLoopGroup workerGroup) {
+        super(config, workerGroup, connectionListener);
+        addMsgListener(msgListener);
+
+        // 初始化
+        this.init();
+    }
+
+    public DouyinLiveChatClient(DouyinLiveChatClientConfig config, IDouyinMsgListener msgListener, IDouyinConnectionListener connectionListener) {
+        this(config, msgListener, connectionListener, new NioEventLoopGroup());
+    }
+
+    public DouyinLiveChatClient(DouyinLiveChatClientConfig config, IDouyinMsgListener msgListener) {
+        this(config, msgListener, null, new NioEventLoopGroup());
+    }
+
+    public DouyinLiveChatClient(DouyinLiveChatClientConfig config) {
+        this(config, null);
+    }
+
+    @Override
+    public void init() {
+        roomInitResult = DouyinApis.roomInit(getConfig().getRoomId(), getConfig().getCookie());
+        super.init();
+    }
+
+    @Override
+    public DouyinConnectionHandler initConnectionHandler(IBaseConnectionListener<DouyinConnectionHandler> clientConnectionListener) {
+        DefaultHttpHeaders headers = new DefaultHttpHeaders();
+        headers.add(Header.COOKIE.name(), DouyinApis.KEY_COOKIE_TTWID + "=" + roomInitResult.getTtwid());
+        headers.add(Header.USER_AGENT.name(), GlobalHeaders.INSTANCE.header(Header.USER_AGENT));
+        return new DouyinConnectionHandler(
+                WebSocketClientHandshakerFactory.newHandshaker(getWebsocketUri(), WebSocketVersion.V13, null, true, headers, getConfig().getMaxFramePayloadLength()),
+                DouyinLiveChatClient.this, clientConnectionListener
+        );
+    }
+
+    @Override
+    public DouyinBinaryFrameHandler initBinaryFrameHandler() {
+        return new DouyinBinaryFrameHandler(super.msgListeners, DouyinLiveChatClient.this);
+    }
+
+    @Override
+    protected String getWebSocketUriString() {
+        long realRoomId = roomInitResult.getRealRoomId();
+        String userUniqueId = roomInitResult.getUserUniqueId();
+
+        String webSocketUriString = super.getWebSocketUriString();
+        Map<String, String> queryParams = new HashMap<>();
+        queryParams.put("app_name", "douyin_web");
+        queryParams.put("version_code", getConfig().getVersionCode());
+        queryParams.put("webcast_sdk_version", getConfig().getWebcastSdkVersion());
+        queryParams.put("update_version_code", getConfig().getUpdateVersionCode());
+        queryParams.put("compress", "gzip");
+        queryParams.put("device_platform", "web");
+        queryParams.put("cookie_enabled", "true");
+        queryParams.put("screen_width", "800");
+        queryParams.put("screen_height", "1280");
+        queryParams.put("browser_language", "zh-CN");
+        queryParams.put("browser_platform", "MacIntel");
+        queryParams.put("browser_name", "Mozilla");
+        queryParams.put("browser_version", "5.0%20(Macintosh;%20Intel%20Mac%20OS%20X%2010_15_7)%20AppleWebKit/537.36%20(KHTML,%20like%20Gecko)%20Chrome/116.0.0.0%20Safari/537.36");
+        queryParams.put("browser_online", "true");
+        queryParams.put("tz_name", "Asia/Shanghai");
+        queryParams.put("host", "https://live.douyin.com");
+        queryParams.put("im_path", "/webcast/im/fetch/");
+        queryParams.put("endpoint", "live_pc");
+        queryParams.put("identity", "audience");
+
+        queryParams.put("support_wrds", "1");
+        queryParams.put("heartbeatDuration ", "0");
+        queryParams.put("live_id", "1");
+        queryParams.put("did_rule", "3");
+        queryParams.put("aid", "6383");
+
+        queryParams.put("room_id", Long.toString(realRoomId));
+        queryParams.put("user_unique_id", userUniqueId);
+        // TODO 生成signature
+        queryParams.put("signature", "00000000");
+        queryParams.put("cursor", "t-" + System.currentTimeMillis() + "_r-1_d-1_u-1_h-1");
+        queryParams.put("internal_ext", "internal_src:dim|" +
+                "wss_push_room_id:" + realRoomId + "|" +
+                "wss_push_did:" + userUniqueId + "|" +
+                "dim_log_id:" + DateUtil.format(new Date(), "yyyy-MM-dd") + RandomUtil.randomNumbers(6) + RandomUtil.randomString("0123456789ABCDEF", 20) + "|" +
+                "first_req_ms:" + System.currentTimeMillis() + "|" +
+                "fetch_time:" + System.currentTimeMillis() + "|" +
+                "seq:1|" +
+                "wss_info:0-" + System.currentTimeMillis() + "-0-0|" +
+                "wrds_kvs:WebcastRoomStatsMessage-" + System.nanoTime() + "_WebcastRoomRankMessage-" + System.nanoTime() + "_LotteryInfoSyncData-" + System.nanoTime() + "_WebcastActivityEmojiGroupsMessage-" + System.nanoTime());
+        return webSocketUriString + "?" + HttpUtil.toParams(queryParams);
+    }
+
+    public void sendDanmu(Object danmu, Runnable success, Consumer<Throwable> failed) {
+        super.sendDanmu(danmu, success, failed);
+    }
+
+}

+ 93 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-douyin/src/main/java/tech/ordinaryroad/live/chat/client/douyin/config/DouyinLiveChatClientConfig.java

@@ -0,0 +1,93 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.douyin.config;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.SuperBuilder;
+import tech.ordinaryroad.live.chat.client.servers.netty.client.config.BaseNettyClientConfig;
+
+/**
+ * @author mjz
+ * @date 2024/1/2
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@SuperBuilder(toBuilder = true)
+public class DouyinLiveChatClientConfig extends BaseNettyClientConfig {
+
+    @Builder.Default
+    private int aggregatorMaxContentLength = 64 * 1024 * 1024;
+
+    @Builder.Default
+    private int maxFramePayloadLength = 64 * 1024 * 1024;
+
+    private String versionCode = "180800";
+
+    private String webcastSdkVersion = "1.0.12";
+
+    private String updateVersionCode = "1.0.12";
+
+    /**
+     * 示例
+     * wss://webcast5-ws-web-lf.douyin.com/webcast/im/push/v2/
+     * ?app_name=douyin_web
+     * &version_code=180800
+     * &webcast_sdk_version=1.0.12
+     * &update_version_code=1.0.12
+     * &compress=gzip
+     * &device_platform=web
+     * &cookie_enabled=true
+     * &screen_width=1512
+     * &screen_height=982
+     * &browser_language=zh-CN
+     * &browser_platform=MacIntel
+     * &browser_name=Mozilla
+     * &browser_version=5.0%20(Macintosh;%20Intel%20Mac%20OS%20X%2010_15_7)%20AppleWebKit/537.36%20(KHTML,%20like%20Gecko)%20Chrome/118.0.0.0%20Safari/537.36
+     * &browser_online=true
+     * &tz_name=Asia/Shanghai
+     * &cursor=u-1_h-1_t-1704202376885_r-1_d-1
+     * &internal_ext=internal_src:dim|wss_push_room_id:7319486720022301449|wss_push_did:7319492411867170356|dim_log_id:20240102213256AAA5B735ADBE992BEF6A|first_req_ms:1704202376757|fetch_time:1704202376885|seq:1|wss_info:0-1704202376885-0-0|wrds_kvs:WebcastActivityEmojiGroupsMessage-1704200830782138545_WebcastRoomRankMessage-1704202270876589607_WebcastRoomStatsMessage-1704202372842388781
+     * &host=https://live.douyin.com
+     * &aid=6383
+     * &live_id=1
+     * &did_rule=3
+     * &endpoint=live_pc
+     * &support_wrds=1
+     * &user_unique_id=7319492411867170356
+     * &im_path=/webcast/im/fetch/
+     * &identity=audience
+     * &need_persist_msg_count=15
+     * &room_id=7319486720022301449
+     * &heartbeatDuration=0
+     * &signature=Wk407jV1/WbnoIGk
+     */
+    @Builder.Default
+    private String websocketUri = "wss://webcast5-ws-web-lf.douyin.com:443/webcast/im/push/v2/";
+
+}

+ 73 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-douyin/src/main/java/tech/ordinaryroad/live/chat/client/douyin/constant/DouyinCmdEnum.java

@@ -0,0 +1,73 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.douyin.constant;
+
+import cn.hutool.core.util.StrUtil;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+/**
+ * @author mjz
+ * @date 2024/1/2
+ */
+@Getter
+@RequiredArgsConstructor
+public enum DouyinCmdEnum {
+
+    /**
+     * 弹幕
+     */
+    WebcastChatMessage,
+    /**
+     * 礼物
+     */
+    WebcastGiftMessage,
+    /**
+     * 点赞
+     */
+    WebcastLikeMessage,
+    /**
+     * 入房
+     */
+    WebcastMemberMessage,
+    WebcastSocialMessage,
+    WebcastRoomUserSeqMessage,
+    WebcastFansclubMessage,
+    WebcastControlMessage,
+    ;
+
+    public static DouyinCmdEnum getByName(String name) {
+        if (StrUtil.isBlank(name)) {
+            return null;
+        }
+
+        for (DouyinCmdEnum value : values()) {
+            if (value.name().equals(name)) {
+                return value;
+            }
+        }
+        return null;
+    }
+}

+ 35 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-douyin/src/main/java/tech/ordinaryroad/live/chat/client/douyin/listener/IDouyinConnectionListener.java

@@ -0,0 +1,35 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.douyin.listener;
+
+import tech.ordinaryroad.live.chat.client.commons.base.listener.IBaseConnectionListener;
+import tech.ordinaryroad.live.chat.client.douyin.netty.handler.DouyinConnectionHandler;
+
+/**
+ * @author mjz
+ * @date 2024/1/2
+ */
+public interface IDouyinConnectionListener extends IBaseConnectionListener<DouyinConnectionHandler> {
+}

+ 44 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-douyin/src/main/java/tech/ordinaryroad/live/chat/client/douyin/listener/IDouyinMsgListener.java

@@ -0,0 +1,44 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.douyin.listener;
+
+import tech.ordinaryroad.live.chat.client.commons.base.listener.*;
+import tech.ordinaryroad.live.chat.client.douyin.constant.DouyinCmdEnum;
+import tech.ordinaryroad.live.chat.client.douyin.msg.DouyinDanmuMsg;
+import tech.ordinaryroad.live.chat.client.douyin.msg.DouyinEnterRoomMsg;
+import tech.ordinaryroad.live.chat.client.douyin.msg.DouyinGiftMsg;
+import tech.ordinaryroad.live.chat.client.douyin.msg.DouyinLikeMsg;
+import tech.ordinaryroad.live.chat.client.douyin.netty.handler.DouyinBinaryFrameHandler;
+
+/**
+ * @author mjz
+ * @date 2024/1/2
+ */
+public interface IDouyinMsgListener extends IBaseMsgListener<DouyinBinaryFrameHandler, DouyinCmdEnum>,
+        IDanmuMsgListener<DouyinBinaryFrameHandler, DouyinDanmuMsg>,
+        IGiftMsgListener<DouyinBinaryFrameHandler, DouyinGiftMsg>,
+        IEnterRoomMsgListener<DouyinBinaryFrameHandler, DouyinEnterRoomMsg>,
+        ILikeMsgListener<DouyinBinaryFrameHandler, DouyinLikeMsg> {
+}

+ 77 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-douyin/src/main/java/tech/ordinaryroad/live/chat/client/douyin/msg/DouyinDanmuMsg.java

@@ -0,0 +1,77 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.douyin.msg;
+
+import cn.hutool.core.collection.CollUtil;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import tech.ordinaryroad.live.chat.client.commons.base.msg.IDanmuMsg;
+import tech.ordinaryroad.live.chat.client.douyin.msg.base.IDouyinMsg;
+import tech.ordinaryroad.live.chat.client.douyin.protobuf.douyin_webcast_chat_message_msg;
+
+/**
+ * @author mjz
+ * @date 2024/1/9
+ */
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+public class DouyinDanmuMsg implements IDouyinMsg, IDanmuMsg {
+
+    private douyin_webcast_chat_message_msg msg;
+
+    @Override
+    public String getBadgeName() {
+        return msg.getUser().getFansClub().getData().getClubName();
+    }
+
+    @Override
+    public byte getBadgeLevel() {
+        return (byte) msg.getUser().getFansClub().getData().getLevel();
+    }
+
+    @Override
+    public String getUid() {
+        return Long.toString(msg.getUser().getId());
+    }
+
+    @Override
+    public String getUsername() {
+        return msg.getUser().getNickname();
+    }
+
+    @Override
+    public String getUserAvatar() {
+        return CollUtil.getFirst(msg.getUser().getAvatarThumb().getUrlListListList());
+    }
+
+    @Override
+    public String getContent() {
+        return msg.getContent();
+    }
+}

+ 72 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-douyin/src/main/java/tech/ordinaryroad/live/chat/client/douyin/msg/DouyinEnterRoomMsg.java

@@ -0,0 +1,72 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.douyin.msg;
+
+import cn.hutool.core.collection.CollUtil;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import tech.ordinaryroad.live.chat.client.commons.base.msg.IEnterRoomMsg;
+import tech.ordinaryroad.live.chat.client.douyin.msg.base.IDouyinMsg;
+import tech.ordinaryroad.live.chat.client.douyin.protobuf.douyin_webcast_member_message_msg;
+
+/**
+ * @author mjz
+ * @date 2024/1/9
+ */
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+public class DouyinEnterRoomMsg implements IDouyinMsg, IEnterRoomMsg {
+
+    private douyin_webcast_member_message_msg msg;
+
+    @Override
+    public String getBadgeName() {
+        return msg.getUser().getFansClub().getData().getClubName();
+    }
+
+    @Override
+    public byte getBadgeLevel() {
+        return (byte) msg.getUser().getFansClub().getData().getLevel();
+    }
+
+    @Override
+    public String getUid() {
+        return Long.toString(msg.getUser().getId());
+    }
+
+    @Override
+    public String getUsername() {
+        return msg.getUser().getNickname();
+    }
+
+    @Override
+    public String getUserAvatar() {
+        return CollUtil.getFirst(msg.getUser().getAvatarThumb().getUrlListListList());
+    }
+}

+ 107 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-douyin/src/main/java/tech/ordinaryroad/live/chat/client/douyin/msg/DouyinGiftMsg.java

@@ -0,0 +1,107 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.douyin.msg;
+
+import cn.hutool.core.collection.CollUtil;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import tech.ordinaryroad.live.chat.client.commons.base.msg.IGiftMsg;
+import tech.ordinaryroad.live.chat.client.douyin.msg.base.IDouyinMsg;
+import tech.ordinaryroad.live.chat.client.douyin.protobuf.douyin_webcast_gift_message_msg;
+
+/**
+ * @author mjz
+ * @date 2024/1/9
+ */
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+public class DouyinGiftMsg implements IDouyinMsg, IGiftMsg {
+
+    private douyin_webcast_gift_message_msg msg;
+
+    @Override
+    public String getBadgeName() {
+        return msg.getUser().getFansClub().getData().getClubName();
+    }
+
+    @Override
+    public byte getBadgeLevel() {
+        return (byte) msg.getUser().getFansClub().getData().getLevel();
+    }
+
+    @Override
+    public String getUid() {
+        return Long.toString(msg.getUser().getId());
+    }
+
+    @Override
+    public String getUsername() {
+        return msg.getUser().getNickname();
+    }
+
+    @Override
+    public String getUserAvatar() {
+        return CollUtil.getFirst(msg.getUser().getAvatarThumb().getUrlListListList());
+    }
+
+    @Override
+    public String getGiftName() {
+        return msg.getGift().getName();
+    }
+
+    @Override
+    public String getGiftImg() {
+        return CollUtil.getFirst(msg.getGift().getImage().getUrlListListList());
+    }
+
+    @Override
+    public String getGiftId() {
+        return Long.toString(msg.getLongGiftId());
+    }
+
+    @Override
+    public int getGiftCount() {
+        return (int) msg.getTotalCount();
+    }
+
+    @Override
+    public int getGiftPrice() {
+        return msg.getGift().getDiamondCount();
+    }
+
+    @Override
+    public String getReceiveUid() {
+        return Long.toString(msg.getToUser().getId());
+    }
+
+    @Override
+    public String getReceiveUsername() {
+        return msg.getToUser().getNickname();
+    }
+}

+ 77 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-douyin/src/main/java/tech/ordinaryroad/live/chat/client/douyin/msg/DouyinLikeMsg.java

@@ -0,0 +1,77 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.douyin.msg;
+
+import cn.hutool.core.collection.CollUtil;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import tech.ordinaryroad.live.chat.client.commons.base.msg.ILikeMsg;
+import tech.ordinaryroad.live.chat.client.douyin.msg.base.IDouyinMsg;
+import tech.ordinaryroad.live.chat.client.douyin.protobuf.douyin_webcast_like_message_msg;
+
+/**
+ * @author mjz
+ * @date 2024/1/31
+ */
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+public class DouyinLikeMsg implements IDouyinMsg, ILikeMsg {
+
+    private douyin_webcast_like_message_msg msg;
+
+    @Override
+    public String getBadgeName() {
+        return msg.getUser().getFansClub().getData().getClubName();
+    }
+
+    @Override
+    public byte getBadgeLevel() {
+        return (byte) msg.getUser().getFansClub().getData().getLevel();
+    }
+
+    @Override
+    public String getUid() {
+        return Long.toString(msg.getUser().getId());
+    }
+
+    @Override
+    public String getUsername() {
+        return msg.getUser().getNickname();
+    }
+
+    @Override
+    public String getUserAvatar() {
+        return CollUtil.getFirst(msg.getUser().getAvatarThumb().getUrlListListList());
+    }
+
+    @Override
+    public int getClickCount() {
+        return (int) msg.getCount();
+    }
+}

+ 35 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-douyin/src/main/java/tech/ordinaryroad/live/chat/client/douyin/msg/base/IDouyinCmdMsg.java

@@ -0,0 +1,35 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.douyin.msg.base;
+
+import tech.ordinaryroad.live.chat.client.commons.base.msg.ICmdMsg;
+import tech.ordinaryroad.live.chat.client.douyin.constant.DouyinCmdEnum;
+
+/**
+ * @author mjz
+ * @date 2024/1/2
+ */
+public interface IDouyinCmdMsg extends IDouyinMsg, ICmdMsg<DouyinCmdEnum> {
+}

+ 34 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-douyin/src/main/java/tech/ordinaryroad/live/chat/client/douyin/msg/base/IDouyinMsg.java

@@ -0,0 +1,34 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.douyin.msg.base;
+
+import tech.ordinaryroad.live.chat.client.commons.base.msg.IMsg;
+
+/**
+ * @author mjz
+ * @date 2024/1/2
+ */
+public interface IDouyinMsg extends IMsg {
+}

+ 156 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-douyin/src/main/java/tech/ordinaryroad/live/chat/client/douyin/netty/handler/DouyinBinaryFrameHandler.java

@@ -0,0 +1,156 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.douyin.netty.handler;
+
+import cn.hutool.core.util.ZipUtil;
+import com.google.protobuf.ByteString;
+import com.google.protobuf.InvalidProtocolBufferException;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
+import lombok.extern.slf4j.Slf4j;
+import tech.ordinaryroad.live.chat.client.commons.base.exception.BaseException;
+import tech.ordinaryroad.live.chat.client.commons.base.msg.ICmdMsg;
+import tech.ordinaryroad.live.chat.client.douyin.client.DouyinLiveChatClient;
+import tech.ordinaryroad.live.chat.client.douyin.constant.DouyinCmdEnum;
+import tech.ordinaryroad.live.chat.client.douyin.listener.IDouyinMsgListener;
+import tech.ordinaryroad.live.chat.client.douyin.msg.DouyinDanmuMsg;
+import tech.ordinaryroad.live.chat.client.douyin.msg.DouyinEnterRoomMsg;
+import tech.ordinaryroad.live.chat.client.douyin.msg.DouyinGiftMsg;
+import tech.ordinaryroad.live.chat.client.douyin.msg.DouyinLikeMsg;
+import tech.ordinaryroad.live.chat.client.douyin.msg.base.IDouyinMsg;
+import tech.ordinaryroad.live.chat.client.douyin.protobuf.*;
+import tech.ordinaryroad.live.chat.client.servers.netty.client.handler.BaseNettyClientBinaryFrameHandler;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author mjz
+ * @date 2024/1/2
+ */
+@Slf4j
+@ChannelHandler.Sharable
+public class DouyinBinaryFrameHandler extends BaseNettyClientBinaryFrameHandler<DouyinLiveChatClient, DouyinBinaryFrameHandler, DouyinCmdEnum, IDouyinMsg, IDouyinMsgListener> {
+
+    private ChannelHandlerContext channelHandlerContext;
+
+    public DouyinBinaryFrameHandler(List<IDouyinMsgListener> iDouyinMsgListeners, DouyinLiveChatClient client) {
+        super(iDouyinMsgListeners, client);
+    }
+
+    public DouyinBinaryFrameHandler(List<IDouyinMsgListener> iDouyinMsgListeners, long roomId) {
+        super(iDouyinMsgListeners, roomId);
+    }
+
+    @Override
+    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
+        super.handlerAdded(ctx);
+        channelHandlerContext = ctx;
+    }
+
+    @Override
+    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
+        super.handlerRemoved(ctx);
+        channelHandlerContext = null;
+    }
+
+    @Override
+    public void onCmdMsg(DouyinCmdEnum cmd, ICmdMsg<DouyinCmdEnum> cmdMsg) {
+        if (super.msgListeners.isEmpty()) {
+            return;
+        }
+
+        ByteString payload = ((douyin_cmd_msg) cmdMsg).getPayload();
+        switch (cmd) {
+            case WebcastChatMessage: {
+                try {
+                    douyin_webcast_chat_message_msg douyinWebcastChatMessageMsg = douyin_webcast_chat_message_msg.parseFrom(payload);
+                    iteratorMsgListeners(msgListener -> msgListener.onDanmuMsg(DouyinBinaryFrameHandler.this, new DouyinDanmuMsg(douyinWebcastChatMessageMsg)));
+                } catch (IOException e) {
+                    throw new BaseException(e);
+                }
+                break;
+            }
+            case WebcastGiftMessage: {
+                try {
+                    douyin_webcast_gift_message_msg douyinWebcastGiftMessageMsg = douyin_webcast_gift_message_msg.parseFrom(payload);
+                    iteratorMsgListeners(msgListener -> msgListener.onGiftMsg(DouyinBinaryFrameHandler.this, new DouyinGiftMsg(douyinWebcastGiftMessageMsg)));
+                } catch (InvalidProtocolBufferException e) {
+                    throw new BaseException(e);
+                }
+                break;
+            }
+            case WebcastMemberMessage: {
+                try {
+                    douyin_webcast_member_message_msg douyinWebcastMemberMessageMsg = douyin_webcast_member_message_msg.parseFrom(payload);
+                    iteratorMsgListeners(msgListener -> msgListener.onEnterRoomMsg(DouyinBinaryFrameHandler.this, new DouyinEnterRoomMsg(douyinWebcastMemberMessageMsg)));
+                } catch (InvalidProtocolBufferException e) {
+                    throw new BaseException(e);
+                }
+                break;
+            }
+            case WebcastLikeMessage: {
+                try {
+                    douyin_webcast_like_message_msg douyinWebcastLikeMessageMsg = douyin_webcast_like_message_msg.parseFrom(payload);
+                    iteratorMsgListeners(msgListener -> msgListener.onLikeMsg(DouyinBinaryFrameHandler.this, new DouyinLikeMsg(douyinWebcastLikeMessageMsg)));
+                } catch (InvalidProtocolBufferException e) {
+                    throw new BaseException(e);
+                }
+                break;
+            }
+            default: {
+                iteratorMsgListeners(msgListener -> msgListener.onOtherCmdMsg(DouyinBinaryFrameHandler.this, cmd, cmdMsg));
+            }
+        }
+    }
+
+    @Override
+    protected List<IDouyinMsg> decode(ByteBuf byteBuf) {
+        try {
+            douyin_websocket_frame douyinWebsocketFrame = douyin_websocket_frame.parseFrom(byteBuf.nioBuffer());
+            ByteString payload = douyinWebsocketFrame.getPayload();
+            byte[] bytes = ZipUtil.unGzip(payload.newInput());
+            douyin_websocket_frame_msg douyinWebsocketFrameMsg = douyin_websocket_frame_msg.parseFrom(bytes);
+
+            // 抖音不是使用心跳,而是ACK
+            if (douyinWebsocketFrameMsg.getNeedAck()) {
+                douyin_websocket_frame ack = douyin_websocket_frame.newBuilder()
+                        .setLogId(douyinWebsocketFrame.getLogId())
+                        .setPayloadType("ack")
+                        .setPayload(douyinWebsocketFrameMsg.getInternalExtBytes())
+                        .build();
+                channelHandlerContext.writeAndFlush(new BinaryWebSocketFrame(Unpooled.wrappedBuffer(ack.toByteArray())));
+            }
+
+            return new ArrayList<>(douyinWebsocketFrameMsg.getMessagesListList());
+        } catch (InvalidProtocolBufferException e) {
+            throw new BaseException(e);
+        }
+    }
+}

+ 117 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-douyin/src/main/java/tech/ordinaryroad/live/chat/client/douyin/netty/handler/DouyinConnectionHandler.java

@@ -0,0 +1,117 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package tech.ordinaryroad.live.chat.client.douyin.netty.handler;
+
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker;
+import lombok.extern.slf4j.Slf4j;
+import tech.ordinaryroad.live.chat.client.commons.base.listener.IBaseConnectionListener;
+import tech.ordinaryroad.live.chat.client.douyin.client.DouyinLiveChatClient;
+import tech.ordinaryroad.live.chat.client.douyin.config.DouyinLiveChatClientConfig;
+import tech.ordinaryroad.live.chat.client.servers.netty.client.handler.BaseNettyClientConnectionHandler;
+
+/**
+ * @author mjz
+ * @date 2024/1/2
+ */
+@Slf4j
+@ChannelHandler.Sharable
+public class DouyinConnectionHandler extends BaseNettyClientConnectionHandler<DouyinLiveChatClient, DouyinConnectionHandler> {
+
+    /**
+     * 以ClientConfig为主
+     */
+    private final Object roomId;
+    /**
+     * 以ClientConfig为主
+     */
+    private String cookie;
+
+    public DouyinConnectionHandler(WebSocketClientHandshaker handshaker, DouyinLiveChatClient client, IBaseConnectionListener<DouyinConnectionHandler> listener) {
+        super(handshaker, client, listener);
+        this.roomId = client.getConfig().getRoomId();
+        this.cookie = client.getConfig().getCookie();
+    }
+
+    public DouyinConnectionHandler(WebSocketClientHandshaker handshaker, DouyinLiveChatClient client) {
+        this(handshaker, client, null);
+    }
+
+    public DouyinConnectionHandler(WebSocketClientHandshaker handshaker, long roomId, IBaseConnectionListener<DouyinConnectionHandler> listener, String cookie) {
+        super(handshaker, listener);
+        this.roomId = roomId;
+        this.cookie = cookie;
+    }
+
+    public DouyinConnectionHandler(WebSocketClientHandshaker handshaker, long roomId, IBaseConnectionListener<DouyinConnectionHandler> listener) {
+        this(handshaker, roomId, listener, null);
+    }
+
+    public DouyinConnectionHandler(WebSocketClientHandshaker handshaker, long roomId, String cookie) {
+        this(handshaker, roomId, null, cookie);
+    }
+
+    public DouyinConnectionHandler(WebSocketClientHandshaker handshaker, long roomId) {
+        this(handshaker, roomId, null, null);
+    }
+
+    @Override
+    protected void sendHeartbeat(ChannelHandlerContext ctx) {
+        // ignore
+    }
+
+    @Override
+    public void sendAuthRequest(Channel channel) {
+        // ignore
+    }
+
+    @Override
+    protected long getHeartbeatPeriod() {
+        if (client == null) {
+            return DouyinLiveChatClientConfig.DEFAULT_HEARTBEAT_PERIOD;
+        } else {
+            return client.getConfig().getHeartbeatPeriod();
+        }
+    }
+
+    @Override
+    protected long getHeartbeatInitialDelay() {
+        if (client == null) {
+            return DouyinLiveChatClientConfig.DEFAULT_HEARTBEAT_INITIAL_DELAY;
+        } else {
+            return client.getConfig().getHeartbeatInitialDelay();
+        }
+    }
+
+    public Object getRoomId() {
+        return client != null ? client.getConfig().getRoomId() : roomId;
+    }
+
+    private String getCookie() {
+        return client != null ? client.getConfig().getCookie() : cookie;
+    }
+}

+ 78 - 0
ruoyi-common/ruoyi-common-live/live-chat-clients/live-chat-client-douyin/src/main/java/tech/ordinaryroad/live/chat/client/douyin/protobuf/Douyin_cmd_msgProto.java

@@ -0,0 +1,78 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2023 OrdinaryRoad
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+// Generated by the protocol buffer compiler.  DO NOT EDIT!
+// source: douyin_cmd_msg.proto
+
+package tech.ordinaryroad.live.chat.client.douyin.protobuf;
+
+public final class Douyin_cmd_msgProto {
+  private Douyin_cmd_msgProto() {}
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistryLite registry) {
+  }
+
+  public static void registerAllExtensions(
+      com.google.protobuf.ExtensionRegistry registry) {
+    registerAllExtensions(
+        (com.google.protobuf.ExtensionRegistryLite) registry);
+  }
+  static final com.google.protobuf.Descriptors.Descriptor
+    internal_static_tech_ordinaryroad_live_chat_client_douyin_protobuf_douyin_cmd_msg_descriptor;
+  static final 
+    com.google.protobuf.GeneratedMessageV3.FieldAccessorTable
+      internal_static_tech_ordinaryroad_live_chat_client_douyin_protobuf_douyin_cmd_msg_fieldAccessorTable;
+
+  public static com.google.protobuf.Descriptors.FileDescriptor
+      getDescriptor() {
+    return descriptor;
+  }
+  private static  com.google.protobuf.Descriptors.FileDescriptor
+      descriptor;
+  static {
+    java.lang.String[] descriptorData = {
+      "\n\024douyin_cmd_msg.proto\0222tech.ordinaryroa" +
+      "d.live.chat.client.douyin.protobuf\"\250\001\n\016d" +
+      "ouyin_cmd_msg\022\016\n\006method\030\001 \001(\t\022\017\n\007payload" +
+      "\030\002 \001(\014\022\016\n\006msg_id\030\003 \001(\003\022\020\n\010msg_type\030\004 \001(\005" +
+      "\022\016\n\006offset\030\005 \001(\003\022\027\n\017need_wrds_store\030\006 \001(" +
+      "\010\022\024\n\014wrds_version\030\007 \001(\003\022\024\n\014wrds_sub_key\030" +
+      "\010 \001(\tBQ\n2tech.ordinaryroad.live.chat.cli" +
+      "ent.douyin.protobufB\023Douyin_cmd_msgProto" +
+      "P\001\242\002\003GPBb\006proto3"
+    };
+    descriptor = com.google.protobuf.Descriptors.FileDescriptor
+      .internalBuildGeneratedFileFrom(descriptorData,
+        new com.google.protobuf.Descriptors.FileDescriptor[] {
+        });
+    internal_static_tech_ordinaryroad_live_chat_client_douyin_protobuf_douyin_cmd_msg_descriptor =
+      getDescriptor().getMessageTypes().get(0);
+    internal_static_tech_ordinaryroad_live_chat_client_douyin_protobuf_douyin_cmd_msg_fieldAccessorTable = new
+      com.google.protobuf.GeneratedMessageV3.FieldAccessorTable(
+        internal_static_tech_ordinaryroad_live_chat_client_douyin_protobuf_douyin_cmd_msg_descriptor,
+        new java.lang.String[] { "Method", "Payload", "MsgId", "MsgType", "Offset", "NeedWrdsStore", "WrdsVersion", "WrdsSubKey", });
+  }
+
+  // @@protoc_insertion_point(outer_class_scope)
+}

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است