Quellcode durchsuchen

项目初始化

szw vor 1 Jahr
Ursprung
Commit
5694842eb6
100 geänderte Dateien mit 3033 neuen und 2 gelöschten Zeilen
  1. 12 0
      .gitignore
  2. 21 0
      LICENSE
  3. 262 2
      README.md
  4. 26 0
      im-client/pom.xml
  5. 13 0
      im-client/src/main/java/com/bx/imclient/IMAutoConfiguration.java
  6. 68 0
      im-client/src/main/java/com/bx/imclient/IMClient.java
  7. 18 0
      im-client/src/main/java/com/bx/imclient/annotation/IMListener.java
  8. 31 0
      im-client/src/main/java/com/bx/imclient/config/RedisConfig.java
  9. 12 0
      im-client/src/main/java/com/bx/imclient/listener/MessageListener.java
  10. 46 0
      im-client/src/main/java/com/bx/imclient/listener/MessageListenerMulticaster.java
  11. 201 0
      im-client/src/main/java/com/bx/imclient/sender/IMSender.java
  12. 44 0
      im-client/src/main/java/com/bx/imclient/task/AbstractMessageResultTask.java
  13. 59 0
      im-client/src/main/java/com/bx/imclient/task/GroupMessageResultResultTask.java
  14. 60 0
      im-client/src/main/java/com/bx/imclient/task/PrivateMessageResultResultTask.java
  15. 2 0
      im-client/src/main/resources/META-INF/spring.factories
  16. 2 0
      im-client/target/classes/META-INF/spring.factories
  17. BIN
      im-client/target/classes/com/bx/imclient/IMAutoConfiguration.class
  18. BIN
      im-client/target/classes/com/bx/imclient/IMClient.class
  19. BIN
      im-client/target/classes/com/bx/imclient/annotation/IMListener.class
  20. BIN
      im-client/target/classes/com/bx/imclient/config/RedisConfig.class
  21. BIN
      im-client/target/classes/com/bx/imclient/listener/MessageListener.class
  22. BIN
      im-client/target/classes/com/bx/imclient/listener/MessageListenerMulticaster.class
  23. BIN
      im-client/target/classes/com/bx/imclient/sender/IMSender.class
  24. BIN
      im-client/target/classes/com/bx/imclient/task/AbstractMessageResultTask$1.class
  25. BIN
      im-client/target/classes/com/bx/imclient/task/AbstractMessageResultTask.class
  26. BIN
      im-client/target/classes/com/bx/imclient/task/GroupMessageResultResultTask.class
  27. BIN
      im-client/target/classes/com/bx/imclient/task/PrivateMessageResultResultTask.class
  28. BIN
      im-client/target/im-client-2.0.0.jar
  29. 5 0
      im-client/target/maven-archiver/pom.properties
  30. 11 0
      im-client/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
  31. 10 0
      im-client/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
  32. 69 0
      im-commom/pom.xml
  33. 18 0
      im-commom/src/main/java/com/bx/imcommon/contant/IMConstant.java
  34. 32 0
      im-commom/src/main/java/com/bx/imcommon/contant/IMRedisKey.java
  35. 51 0
      im-commom/src/main/java/com/bx/imcommon/enums/IMCmdType.java
  36. 28 0
      im-commom/src/main/java/com/bx/imcommon/enums/IMListenerType.java
  37. 32 0
      im-commom/src/main/java/com/bx/imcommon/enums/IMSendCode.java
  38. 47 0
      im-commom/src/main/java/com/bx/imcommon/enums/IMTerminalType.java
  39. 44 0
      im-commom/src/main/java/com/bx/imcommon/model/IMGroupMessage.java
  40. 7 0
      im-commom/src/main/java/com/bx/imcommon/model/IMHeartbeatInfo.java
  41. 9 0
      im-commom/src/main/java/com/bx/imcommon/model/IMLoginInfo.java
  42. 44 0
      im-commom/src/main/java/com/bx/imcommon/model/IMPrivateMessage.java
  43. 40 0
      im-commom/src/main/java/com/bx/imcommon/model/IMRecvInfo.java
  44. 18 0
      im-commom/src/main/java/com/bx/imcommon/model/IMSendInfo.java
  45. 28 0
      im-commom/src/main/java/com/bx/imcommon/model/IMSendResult.java
  46. 17 0
      im-commom/src/main/java/com/bx/imcommon/model/IMSessionInfo.java
  47. 28 0
      im-commom/src/main/java/com/bx/imcommon/model/IMUserInfo.java
  48. 28 0
      im-commom/src/main/java/com/bx/imcommon/serializer/DateToLongSerializer.java
  49. 89 0
      im-commom/src/main/java/com/bx/imcommon/util/CommaTextUtils.java
  50. 89 0
      im-commom/src/main/java/com/bx/imcommon/util/JwtUtil.java
  51. 101 0
      im-commom/src/main/java/com/bx/imcommon/util/ThreadPoolExecutorFactory.java
  52. BIN
      im-commom/target/classes/com/bx/imcommon/contant/IMConstant.class
  53. BIN
      im-commom/target/classes/com/bx/imcommon/contant/IMRedisKey.class
  54. BIN
      im-commom/target/classes/com/bx/imcommon/enums/IMCmdType.class
  55. BIN
      im-commom/target/classes/com/bx/imcommon/enums/IMListenerType.class
  56. BIN
      im-commom/target/classes/com/bx/imcommon/enums/IMSendCode.class
  57. BIN
      im-commom/target/classes/com/bx/imcommon/enums/IMTerminalType.class
  58. BIN
      im-commom/target/classes/com/bx/imcommon/model/IMGroupMessage.class
  59. BIN
      im-commom/target/classes/com/bx/imcommon/model/IMHeartbeatInfo.class
  60. BIN
      im-commom/target/classes/com/bx/imcommon/model/IMLoginInfo.class
  61. BIN
      im-commom/target/classes/com/bx/imcommon/model/IMPrivateMessage.class
  62. BIN
      im-commom/target/classes/com/bx/imcommon/model/IMRecvInfo.class
  63. BIN
      im-commom/target/classes/com/bx/imcommon/model/IMSendInfo.class
  64. BIN
      im-commom/target/classes/com/bx/imcommon/model/IMSendResult.class
  65. BIN
      im-commom/target/classes/com/bx/imcommon/model/IMSessionInfo.class
  66. BIN
      im-commom/target/classes/com/bx/imcommon/model/IMUserInfo.class
  67. BIN
      im-commom/target/classes/com/bx/imcommon/serializer/DateToLongSerializer.class
  68. BIN
      im-commom/target/classes/com/bx/imcommon/util/CommaTextUtils.class
  69. BIN
      im-commom/target/classes/com/bx/imcommon/util/JwtUtil.class
  70. BIN
      im-commom/target/classes/com/bx/imcommon/util/ThreadPoolExecutorFactory.class
  71. BIN
      im-commom/target/im-commom-2.0.0.jar
  72. 5 0
      im-commom/target/maven-archiver/pom.properties
  73. 19 0
      im-commom/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
  74. 19 0
      im-commom/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
  75. 133 0
      im-platform/pom.xml
  76. 31 0
      im-platform/src/main/java/com/bx/implatform/IMPlatformApp.java
  77. 11 0
      im-platform/src/main/java/com/bx/implatform/config/ICEServer.java
  78. 17 0
      im-platform/src/main/java/com/bx/implatform/config/ICEServerConfig.java
  79. 22 0
      im-platform/src/main/java/com/bx/implatform/config/JwtProperties.java
  80. 27 0
      im-platform/src/main/java/com/bx/implatform/config/MinIoClientConfig.java
  81. 37 0
      im-platform/src/main/java/com/bx/implatform/config/MvcConfig.java
  82. 92 0
      im-platform/src/main/java/com/bx/implatform/config/RedisConfig.java
  83. 41 0
      im-platform/src/main/java/com/bx/implatform/config/SwaggerConfig.java
  84. 21 0
      im-platform/src/main/java/com/bx/implatform/contant/Constant.java
  85. 33 0
      im-platform/src/main/java/com/bx/implatform/contant/RedisKey.java
  86. 35 0
      im-platform/src/main/java/com/bx/implatform/controller/FileController.java
  87. 72 0
      im-platform/src/main/java/com/bx/implatform/controller/FriendController.java
  88. 86 0
      im-platform/src/main/java/com/bx/implatform/controller/GroupController.java
  89. 73 0
      im-platform/src/main/java/com/bx/implatform/controller/GroupMessageController.java
  90. 61 0
      im-platform/src/main/java/com/bx/implatform/controller/LoginController.java
  91. 75 0
      im-platform/src/main/java/com/bx/implatform/controller/PrivateMessageController.java
  92. 24 0
      im-platform/src/main/java/com/bx/implatform/controller/UserController.java
  93. 79 0
      im-platform/src/main/java/com/bx/implatform/controller/WebrtcController.java
  94. 36 0
      im-platform/src/main/java/com/bx/implatform/dto/GroupMessageDTO.java
  95. 30 0
      im-platform/src/main/java/com/bx/implatform/dto/LoginDTO.java
  96. 21 0
      im-platform/src/main/java/com/bx/implatform/dto/ModifyPwdDTO.java
  97. 29 0
      im-platform/src/main/java/com/bx/implatform/dto/PrivateMessageDTO.java
  98. 30 0
      im-platform/src/main/java/com/bx/implatform/dto/RegisterDTO.java
  99. 71 0
      im-platform/src/main/java/com/bx/implatform/entity/Friend.java
  100. 81 0
      im-platform/src/main/java/com/bx/implatform/entity/Group.java

+ 12 - 0
.gitignore

@@ -0,0 +1,12 @@
+/.idea/
+/box-im.iml
+/im-server/im-server.iml
+/im-platform/im-platform.iml
+/im-platform/src/main/resources/application-prod.yml
+/im-platform/src/main/resources/logback-prod.xml
+/im-server/src/main/resources/application-prod.yml
+/im-server/src/main/resources/logback-prod.xml
+/im-commom/im-commom.iml
+/im-uniapp/node_modules/
+/im-ui/jsconfig.json
+/package-lock.json

+ 21 - 0
LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 blue
+
+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.

+ 262 - 2
README.md

@@ -1,3 +1,263 @@
-# IMServer
 
-IM即时通讯
+###  **盒子IM** 
+![MIT协议](https://img.shields.io/badge/license-MIT-red)
+[![star](https://gitee.com/bluexsx/box-im/badge/star.svg)](https://gitee.com/bluexsx/box-im) 
+[![star](https://img.shields.io/github/stars/bluexsx/box-im.svg?style=flat&logo=GitHub)](https://github.com/bluexsx/box-im) 
+<a href="#加入交流群"><img src="https://img.shields.io/badge/QQ交流群-green.svg?style=plasticr"></a>
+
+1. 盒子IM是一个仿微信实现的网页版聊天软件,不依赖任何第三方收费组件。
+1. 支持私聊、群聊、离线消息、发送语音、图片、文件、emoji表情等功能
+1. 支持音视频通话(基于webrtc实现,需要ssl证书)
+1. 后端采用springboot+netty实现,网页端使用vue,移动端使用uniapp
+1. 服务器支持集群化部署,每个im-server仅处理自身连接用户的消息
+
+
+详细文档:https://www.yuque.com/u1475064/mufu2a
+
+
+#### 近期更新
+发布2.0版本,本次更新加入了uniapp移动端:
+
+- 支持移动端和web端同时在线,多端消息同步
+- 目前已兼容h5、微信小程序,安卓和IOS
+- 聊天窗口加入已读未读显示
+- 群聊加入@功能
+- 界面风格升级,表情包更新、生成文字头像等
+
+
+#### 在线体验
+
+账号:张三/123456 李四/123456,也可以在网页端自行注册账号
+
+网页端:https://www.boxim.online
+
+移动安卓端:https://www.boxim.online/download/boxim.apk
+
+移动H5端: https://www.boxim.online/h5/ ,或扫码:
+
+![输入图片说明](%E6%88%AA%E5%9B%BE/h5%E4%BA%8C%E7%BB%B4%E7%A0%81.png)
+
+微信小程序:
+
+![输入图片说明](%E6%88%AA%E5%9B%BE/wx%E5%B0%8F%E7%A8%8B%E5%BA%8F%E4%BA%8C%E7%BB%B4%E7%A0%81.jpg)
+
+注:由于每次发布小程序都需要经过严格且繁琐的审核,当前线上微信小程序并非最新版本,最后一次更新时间是2023年12月
+
+
+#### 相关项目
+
+一位网友的开源项目,基于盒子IM接口开发的仿QQ客户端,有兴趣的小伙伴可以也关注一下:
+
+https://gitee.com/zyzyteam/crim
+
+
+#### 项目结构
+|  模块  |     功能 |
+|-------------|------------|
+| im-platform | 与页面进行交互,处理业务请求 |
+| im-server   | 推送聊天消息|
+| im-client   | 消息推送sdk|
+| im-common   | 公共包  |
+| im-ui       | web页面  |
+| im-uniapp   | app页面  |
+
+#### 消息推送方案
+![输入图片说明](%E6%88%AA%E5%9B%BE/%E6%B6%88%E6%81%AF%E6%8E%A8%E9%80%81%E9%9B%86%E7%BE%A4%E5%8C%96.jpg)
+
+- 当消息的发送者和接收者连的不是同一个server时,消息是无法直接推送的,所以我们需要设计出能够支持跨节点推送的方案
+- 利用了redis的list数据实现消息推送,其中key为im:unread:${serverid},每个key的数据可以看做一个queue,每个im-server根据自身的id只消费属于自己的queue
+- redis记录了每个用户的websocket连接的是哪个im-server,当用户发送消息时,im-platform将根据所连接的im-server的id,决定将消息推向哪个queue
+
+
+#### 本地快速部署
+1.安装运行环境
+- 安装node:v14.16.0
+- 安装jdk:1.8
+- 安装maven:3.6.3
+- 安装mysql:5.7,密码分别为root/root,运行sql脚本(脚本在im-platfrom的resources/db目录)
+- 安装redis:5.0
+- 安装minio,命令端口使用9001,并创建一个名为"box-im"的bucket,并设置访问权限为公开
+
+2.启动后端服务
+```
+mvn clean package
+java -jar ./im-platform/target/im-platform.jar
+java -jar ./im-server/target/im-server.jar
+```
+
+3.启动前端web
+```
+cd im-ui
+npm install
+npm run serve
+```
+访问 http://localhost:8080
+
+
+4.启动uniapp-h5
+将im-uniapp目录导入HBuilderX,点击菜单"运行"->"开发环境-h5"
+访问 http://localhost:5173
+
+
+#### 快速接入
+消息推送的请求代码已经封装在im-client包中,对于需要接入im-server的小伙伴,可以按照下面的教程快速的将IM功能集成到自己的项目中。
+
+注意服务器端和前端都需要接入,服务器端发送消息,前端接收消息。
+
+4.1 服务器端接入
+
+引入pom文件
+```
+<dependency>
+    <groupId>com.bx</groupId>
+    <artifactId>im-client</artifactId>
+    <version>2.0.0</version>
+</dependency>
+```
+内容使用了redis进行通信,所以要配置redis地址:
+
+```
+spring:
+  redis:
+    host: 127.0.0.1
+    port: 6379
+```
+
+直接把IMClient通过@Autowire导进来就可以发送消息了,IMClient 只有2个接口:
+```
+public class IMClient {
+
+    /**
+     * 发送私聊消息
+     *
+     * @param message 私有消息
+     */
+    public<T> void sendPrivateMessage(IMPrivateMessage<T> message);
+
+    /**
+     * 发送群聊消息(发送结果通过MessageListener接收)
+     *
+     * @param message 群聊消息
+     */
+    public<T> void sendGroupMessage(IMGroupMessage<T> message);    
+}
+```
+
+发送私聊消息(群聊也是类似的方式):
+```
+ @Autowired
+ private IMClient imClient;
+
+ public void sendMessage(){
+        IMPrivateMessage<PrivateMessageVO> sendMessage = new IMPrivateMessage<>();
+        // 发送方的id和终端类型
+        sendMessage.setSender(new IMUserInfo(1L, IMTerminalType.APP.code()));
+        // 对方的id
+        sendMessage.setRecvId(2L);
+        // 推送给对方所有终端
+        sendMessage.setRecvTerminals(IMTerminalType.codes());
+        // 同时推送给自己的其他类型终端
+        sendMessage.setSendToSelf(true);
+        // 需要回推发送结果,将在IMListener接收发送结果
+        sendMessage.setSendResult(true);
+        // 推送的内容
+        sendMessage.setData(msgInfo);
+        // 推送消息
+        imClient.sendPrivateMessage(sendMessage);
+}
+
+```
+监听发送结果:
+1.编写消息监听类,实现MessageListener,并加上@IMListener
+2.发送消息时指定sendResult为true
+```
+@Slf4j
+@IMListener(type = IMListenerType.ALL)
+public class PrivateMessageListener implements MessageListener {
+    
+    @Override
+    public void process(IMSendResult<PrivateMessageVO> result){
+        PrivateMessageVO messageInfo = result.getData();
+        if(result.getCode().equals(IMSendCode.SUCCESS.code())){
+            log.info("消息发送成功,消息id:{},发送者:{},接收者:{},终端:{}",messageInfo.getId(),result.getSender().getId(),result.getReceiver().getId(),result.getReceiver().getTerminal());
+        }
+    }
+}
+```
+
+4.2 前端接入
+首先将im-ui/src/api/wssocket.js拷贝到自己的项目。
+
+接入代码如下:
+```
+import * as wsApi from './api/wssocket';
+
+let wsUrl = 'ws://localhost:8878/im'
+let token = "您的token";
+wsApi.init(wsUrl,token);
+wsApi.connect();
+wsApi.onOpen(() => {
+    // 连接打开
+    console.log("连接成功");
+});
+wsApi.onMessage((cmd,msgInfo) => {
+    if (cmd == 2) {
+    	// 异地登录,强制下线
+    	console.log("您已在其他地方登陆,将被强制下线");
+    } else if (cmd == 3) {
+    	// 私聊消息
+    	console.log(msgInfo);
+    } else if (cmd == 4) {
+    	// 群聊消息
+    	console.log(msgInfo);
+    }
+})
+wsApi.onClose((e) => {
+    console.log("连接关闭");
+});
+```
+
+
+#### 界面截图
+私聊:
+![输入图片说明](%E6%88%AA%E5%9B%BE/web/%E7%A7%81%E8%81%8A.jpg)
+
+群聊:
+![输入图片说明](%E6%88%AA%E5%9B%BE/web/%E7%BE%A4%E8%81%8A1.jpg)
+
+![输入图片说明](%E6%88%AA%E5%9B%BE/web/%E7%BE%A4%E8%81%8A2.jpg)
+
+
+好友列表:
+![输入图片说明](%E6%88%AA%E5%9B%BE/web/%E5%A5%BD%E5%8F%8B%E5%88%97%E8%A1%A8.jpg)
+
+群聊列表:
+![输入图片说明](%E6%88%AA%E5%9B%BE/web/%E7%BE%A4%E8%81%8A%E5%88%97%E8%A1%A8.jpg)
+
+微信小程序:
+![输入图片说明](%E6%88%AA%E5%9B%BE/wx-mp/%E8%81%8A%E5%A4%A9.jpg)
+
+![输入图片说明](%E6%88%AA%E5%9B%BE/wx-mp/%E5%85%B6%E4%BB%96.jpg)
+
+#### 加入交流群
+
+![输入图片说明](%E6%88%AA%E5%9B%BE/%E4%BA%A4%E6%B5%81%E7%BE%A4.png)
+
+欢迎进群与小伙们一起交流, **申请加群前请务必先star哦** 
+
+
+#### 嘿嘿
+![输入图片说明](%E6%88%AA%E5%9B%BE/%E5%BE%AE%E4%BF%A1%E6%94%B6%E6%AC%BE%E7%A0%81.png)
+
+悄悄放个二维码在这,宝子们..你懂我意思吧
+
+
+#### 点下star吧
+如果项目对您有帮助,请点亮右上方的star,支持一下作者吧!
+
+#### 说明几点
+
+1. 本系统允许用于商业用途,且不收费(自愿投币)。**但切记不要用于任何非法用途** ,本软件作者不会为此承担任何责任
+1. 基于本系统二次开发后再次开源的项目,请注明引用出处,以避免引发不必要的误会
+1. 如果您也想体验开源(bei bai piao)的快感,成为本项目的贡献者,欢迎提交PR。开发前最好提前联系作者,避免功能重复开发
+

+ 26 - 0
im-client/pom.xml

@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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">
+    <parent>
+        <artifactId>box-im</artifactId>
+        <groupId>com.bx</groupId>
+        <version>2.0.0</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>im-client</artifactId>
+
+    <dependencies>
+        <dependency>
+            <groupId>com.bx</groupId>
+            <artifactId>im-commom</artifactId>
+            <version>2.0.0</version>
+        </dependency>
+        <!-- 引入redis -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+        </dependency>
+    </dependencies>
+</project>

+ 13 - 0
im-client/src/main/java/com/bx/imclient/IMAutoConfiguration.java

@@ -0,0 +1,13 @@
+package com.bx.imclient;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+
+
+@Slf4j
+@Configuration
+@ComponentScan("com.bx.imclient")
+public class IMAutoConfiguration {
+
+}

+ 68 - 0
im-client/src/main/java/com/bx/imclient/IMClient.java

@@ -0,0 +1,68 @@
+package com.bx.imclient;
+
+import com.bx.imclient.sender.IMSender;
+import com.bx.imcommon.enums.IMTerminalType;
+import com.bx.imcommon.model.IMGroupMessage;
+import com.bx.imcommon.model.IMPrivateMessage;
+import lombok.AllArgsConstructor;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.List;
+import java.util.Map;
+
+@Configuration
+@AllArgsConstructor
+public class IMClient {
+
+    private final IMSender imSender;
+
+    /**
+     * 判断用户是否在线
+     *
+     * @param userId 用户id
+     */
+    public Boolean isOnline(Long userId){
+        return imSender.isOnline(userId);
+    }
+
+    /**
+     * 判断多个用户是否在线
+     *
+     * @param userIds 用户id列表
+     * @return 在线的用户列表
+     */
+    public List<Long> getOnlineUser(List<Long> userIds){
+        return imSender.getOnlineUser(userIds);
+    }
+
+
+    /**
+     * 判断多个用户是否在线
+     *
+     * @param userIds 用户id列表
+     * @return 在线的用户终端
+     */
+    public Map<Long,List<IMTerminalType>> getOnlineTerminal(List<Long> userIds){
+        return imSender.getOnlineTerminal(userIds);
+    }
+
+    /**
+     * 发送私聊消息(发送结果通过MessageListener接收)
+     *
+     * @param message 私有消息
+     */
+    public<T> void sendPrivateMessage(IMPrivateMessage<T> message){
+        imSender.sendPrivateMessage(message);
+    }
+
+    /**
+     * 发送群聊消息(发送结果通过MessageListener接收)
+     *
+     * @param message 群聊消息
+     */
+    public<T> void sendGroupMessage(IMGroupMessage<T> message){
+        imSender.sendGroupMessage(message);
+    }
+
+
+}

+ 18 - 0
im-client/src/main/java/com/bx/imclient/annotation/IMListener.java

@@ -0,0 +1,18 @@
+package com.bx.imclient.annotation;
+
+import com.bx.imcommon.enums.IMListenerType;
+import org.springframework.stereotype.Component;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target({ElementType.TYPE,ElementType.FIELD})
+@Retention(RetentionPolicy.RUNTIME)
+@Component
+public @interface IMListener {
+
+    IMListenerType type();
+
+}

+ 31 - 0
im-client/src/main/java/com/bx/imclient/config/RedisConfig.java

@@ -0,0 +1,31 @@
+package com.bx.imclient.config;
+
+import com.alibaba.fastjson.support.spring.FastJsonRedisSerializer;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+
+@Configuration("IMRedisConfig")
+public class RedisConfig {
+
+    @Bean("IMRedisTemplate")
+    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
+        RedisTemplate<String, Object> redisTemplate = new RedisTemplate();
+        redisTemplate.setConnectionFactory(redisConnectionFactory);
+        // 设置值(value)的序列化采用FastJsonRedisSerializer
+        redisTemplate.setValueSerializer(fastJsonRedisSerializer());
+        redisTemplate.setHashValueSerializer(fastJsonRedisSerializer());
+        // 设置键(key)的序列化采用StringRedisSerializer。
+        redisTemplate.setKeySerializer(new StringRedisSerializer());
+        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
+        redisTemplate.afterPropertiesSet();
+        return redisTemplate;
+    }
+
+    public FastJsonRedisSerializer fastJsonRedisSerializer(){
+        return new FastJsonRedisSerializer<>(Object.class);
+    }
+
+}

+ 12 - 0
im-client/src/main/java/com/bx/imclient/listener/MessageListener.java

@@ -0,0 +1,12 @@
+package com.bx.imclient.listener;
+
+
+import com.bx.imcommon.model.IMSendResult;
+
+import java.util.List;
+
+public interface MessageListener<T> {
+
+     void process(List<IMSendResult<T>> result);
+
+}

+ 46 - 0
im-client/src/main/java/com/bx/imclient/listener/MessageListenerMulticaster.java

@@ -0,0 +1,46 @@
+package com.bx.imclient.listener;
+
+
+import cn.hutool.core.collection.CollUtil;
+import com.alibaba.fastjson.JSONObject;
+import com.bx.imclient.annotation.IMListener;
+import com.bx.imcommon.enums.IMListenerType;
+import com.bx.imcommon.model.IMSendResult;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Collections;
+import java.util.List;
+
+@Component
+public class MessageListenerMulticaster {
+
+    @Autowired(required = false)
+    private List<MessageListener>  messageListeners  = Collections.emptyList();
+
+    public  void multicast(IMListenerType listenerType, List<IMSendResult> results){
+        if(CollUtil.isEmpty(results)){
+            return;
+        }
+        for(MessageListener listener:messageListeners){
+            IMListener annotation = listener.getClass().getAnnotation(IMListener.class);
+            if(annotation!=null && (annotation.type().equals(IMListenerType.ALL) || annotation.type().equals(listenerType))){
+                results.forEach(result->{
+                    // 将data转回对象类型
+                    if(result.getData() instanceof JSONObject){
+                        Type superClass = listener.getClass().getGenericInterfaces()[0];
+                        Type type = ((ParameterizedType) superClass).getActualTypeArguments()[0];
+                        JSONObject data = (JSONObject)result.getData();
+                        result.setData(data.toJavaObject(type));
+                    }
+                });
+                // 回调到调用方处理
+                listener.process(results);
+            }
+        }
+    }
+
+
+}

+ 201 - 0
im-client/src/main/java/com/bx/imclient/sender/IMSender.java

@@ -0,0 +1,201 @@
+package com.bx.imclient.sender;
+
+import cn.hutool.core.collection.CollUtil;
+import com.bx.imclient.listener.MessageListenerMulticaster;
+import com.bx.imcommon.contant.IMRedisKey;
+import com.bx.imcommon.enums.IMCmdType;
+import com.bx.imcommon.enums.IMListenerType;
+import com.bx.imcommon.enums.IMSendCode;
+import com.bx.imcommon.enums.IMTerminalType;
+import com.bx.imcommon.model.*;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.*;
+
+@Service
+@RequiredArgsConstructor
+public class IMSender {
+
+    @Resource(name="IMRedisTemplate")
+    private RedisTemplate<String, Object> redisTemplate;
+
+    @Value("${spring.application.name}")
+    private String appName;
+
+    private final MessageListenerMulticaster listenerMulticaster;
+
+    public<T> void sendPrivateMessage(IMPrivateMessage<T> message) {
+        List<IMSendResult> results = new LinkedList<>();
+        for (Integer terminal : message.getRecvTerminals()) {
+            // 获取对方连接的channelId
+            String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID, message.getRecvId().toString(), terminal.toString());
+            Integer serverId = (Integer)redisTemplate.opsForValue().get(key);
+            // 如果对方在线,将数据存储至redis,等待拉取推送
+            if (serverId != null) {
+                String sendKey = String.join(":", IMRedisKey.IM_MESSAGE_PRIVATE_QUEUE, serverId.toString());
+                IMRecvInfo recvInfo = new IMRecvInfo();
+                recvInfo.setCmd(IMCmdType.PRIVATE_MESSAGE.code());
+                recvInfo.setSendResult(message.getSendResult());
+                recvInfo.setServiceName(appName);
+                recvInfo.setSender(message.getSender());
+                recvInfo.setReceivers(Collections.singletonList(new IMUserInfo(message.getRecvId(), terminal)));
+                recvInfo.setData(message.getData());
+                redisTemplate.opsForList().rightPush(sendKey, recvInfo);
+            } else {
+                IMSendResult result = new IMSendResult();
+                result.setSender(message.getSender());
+                result.setReceiver(new IMUserInfo(message.getRecvId(), terminal));
+                result.setCode(IMSendCode.NOT_ONLINE.code());
+                result.setData(message.getData());
+                results.add(result);
+            }
+        }
+        // 推送给自己的其他终端
+        if(message.getSendToSelf()){
+            for (Integer terminal : IMTerminalType.codes()) {
+                if (message.getSender().getTerminal().equals(terminal)) {
+                    continue;
+                }
+                // 获取终端连接的channelId
+                String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID, message.getSender().getId().toString(), terminal.toString());
+                Integer serverId = (Integer)redisTemplate.opsForValue().get(key);
+                // 如果终端在线,将数据存储至redis,等待拉取推送
+                if (serverId != null) {
+                    String sendKey = String.join(":", IMRedisKey.IM_MESSAGE_PRIVATE_QUEUE, serverId.toString());
+                    IMRecvInfo recvInfo = new IMRecvInfo();
+                    // 自己的消息不需要回推消息结果
+                    recvInfo.setSendResult(false);
+                    recvInfo.setCmd(IMCmdType.PRIVATE_MESSAGE.code());
+                    recvInfo.setSender(message.getSender());
+                    recvInfo.setReceivers(Collections.singletonList(new IMUserInfo(message.getSender().getId(), terminal)));
+                    recvInfo.setData(message.getData());
+                    redisTemplate.opsForList().rightPush(sendKey, recvInfo);
+                }
+            }
+        }
+        // 对离线用户回复消息状态
+        if(message.getSendResult() && !results.isEmpty()){
+            listenerMulticaster.multicast(IMListenerType.PRIVATE_MESSAGE, results);
+        }
+    }
+
+    public<T> void sendGroupMessage(IMGroupMessage<T> message) {
+
+        // 根据群聊每个成员所连的IM-server,进行分组
+        Map<String, IMUserInfo> sendMap = new HashMap<>();
+        for (Integer terminal : message.getRecvTerminals()) {
+            message.getRecvIds().forEach(id -> {
+                String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID, id.toString(), terminal.toString());
+                sendMap.put(key,new IMUserInfo(id, terminal));
+            });
+        }
+        // 批量拉取
+        List<Object> serverIds = redisTemplate.opsForValue().multiGet(sendMap.keySet());
+        // 格式:map<服务器id,list<接收方>>
+        Map<Integer, List<IMUserInfo>> serverMap = new HashMap<>();
+        List<IMUserInfo> offLineUsers = new LinkedList<>();
+        int idx = 0;
+        for (Map.Entry<String,IMUserInfo> entry : sendMap.entrySet()) {
+            Integer serverId = (Integer)serverIds.get(idx++);
+            if (serverId != null) {
+                List<IMUserInfo> list = serverMap.computeIfAbsent(serverId, o -> new LinkedList<>());
+                list.add(entry.getValue());
+            } else {
+                // 加入离线列表
+                offLineUsers.add(entry.getValue());
+            }
+        }
+        // 逐个server发送
+        for (Map.Entry<Integer, List<IMUserInfo>> entry : serverMap.entrySet()) {
+            IMRecvInfo recvInfo = new IMRecvInfo();
+            recvInfo.setCmd(IMCmdType.GROUP_MESSAGE.code());
+            recvInfo.setReceivers(new LinkedList<>(entry.getValue()));
+            recvInfo.setSender(message.getSender());
+            recvInfo.setServiceName(appName);
+            recvInfo.setSendResult(message.getSendResult());
+            recvInfo.setData(message.getData());
+            // 推送至队列
+            String key = String.join(":", IMRedisKey.IM_MESSAGE_GROUP_QUEUE, entry.getKey().toString());
+            redisTemplate.opsForList().rightPush(key, recvInfo);
+        }
+
+        // 推送给自己的其他终端
+        if (message.getSendToSelf()) {
+            for (Integer terminal : IMTerminalType.codes()) {
+                if (terminal.equals(message.getSender().getTerminal())) {
+                    continue;
+                }
+                // 获取终端连接的channelId
+                String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID, message.getSender().getId().toString(), terminal.toString());
+                Integer serverId = (Integer)redisTemplate.opsForValue().get(key);
+                // 如果终端在线,将数据存储至redis,等待拉取推送
+                if (serverId != null) {
+                    IMRecvInfo recvInfo = new IMRecvInfo();
+                    recvInfo.setCmd(IMCmdType.GROUP_MESSAGE.code());
+                    recvInfo.setSender(message.getSender());
+                    recvInfo.setReceivers(Collections.singletonList(new IMUserInfo(message.getSender().getId(), terminal)));
+                    // 自己的消息不需要回推消息结果
+                    recvInfo.setSendResult(false);
+                    recvInfo.setData(message.getData());
+                    String sendKey = String.join(":", IMRedisKey.IM_MESSAGE_GROUP_QUEUE, serverId.toString());
+                    redisTemplate.opsForList().rightPush(sendKey, recvInfo);
+                }
+            }
+        }
+        // 对离线用户回复消息状态
+        if(message.getSendResult() && !offLineUsers.isEmpty()){
+            List<IMSendResult> results = new LinkedList<>();
+            for (IMUserInfo offLineUser : offLineUsers) {
+                IMSendResult result = new IMSendResult();
+                result.setSender(message.getSender());
+                result.setReceiver(offLineUser);
+                result.setCode(IMSendCode.NOT_ONLINE.code());
+                result.setData(message.getData());
+                results.add(result);
+            }
+            listenerMulticaster.multicast(IMListenerType.GROUP_MESSAGE, results);
+        }
+
+    }
+
+    public Map<Long,List<IMTerminalType>> getOnlineTerminal(List<Long> userIds){
+        if(CollUtil.isEmpty(userIds)){
+            return Collections.emptyMap();
+        }
+        // 把所有用户的key都存起来
+        Map<String,IMUserInfo> userMap = new HashMap<>();
+        for(Long id:userIds){
+            for (Integer terminal : IMTerminalType.codes()) {
+                String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID, id.toString(), terminal.toString());
+                userMap.put(key,new IMUserInfo(id,terminal));
+            }
+        }
+        // 批量拉取
+        List<Object> serverIds = redisTemplate.opsForValue().multiGet(userMap.keySet());
+        int idx = 0;
+        Map<Long,List<IMTerminalType>> onlineMap = new HashMap<>();
+        for (Map.Entry<String,IMUserInfo> entry : userMap.entrySet()) {
+            // serverid有值表示用户在线
+            if(serverIds.get(idx++) != null){
+                IMUserInfo userInfo = entry.getValue();
+                List<IMTerminalType> terminals = onlineMap.computeIfAbsent(userInfo.getId(), o -> new LinkedList<>());
+                terminals.add(IMTerminalType.fromCode(userInfo.getTerminal()));
+            }
+        }
+        // 去重并返回
+        return onlineMap;
+    }
+
+    public Boolean isOnline(Long userId) {
+        String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID, userId.toString(), "*");
+        return !Objects.requireNonNull(redisTemplate.keys(key)).isEmpty();
+    }
+
+    public List<Long> getOnlineUser(List<Long> userIds){
+        return new LinkedList<>(getOnlineTerminal(userIds).keySet());
+    }
+}

+ 44 - 0
im-client/src/main/java/com/bx/imclient/task/AbstractMessageResultTask.java

@@ -0,0 +1,44 @@
+package com.bx.imclient.task;
+
+import com.bx.imcommon.util.ThreadPoolExecutorFactory;
+import lombok.SneakyThrows;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.boot.CommandLineRunner;
+
+import javax.annotation.PreDestroy;
+import java.util.concurrent.ExecutorService;
+
+@Slf4j
+public abstract class AbstractMessageResultTask implements CommandLineRunner {
+
+    private static final ExecutorService EXECUTOR_SERVICE = ThreadPoolExecutorFactory.getThreadPoolExecutor();
+
+    @Override
+    public void run(String... args) {
+        // 初始化定时器
+        EXECUTOR_SERVICE.execute(new Runnable() {
+            @SneakyThrows
+            @Override
+            public void run() {
+                try {
+                    pullMessage();
+                } catch (Exception e) {
+                    log.error("任务调度异常", e);
+                }
+                if (!EXECUTOR_SERVICE.isShutdown()) {
+                    Thread.sleep(100);
+                    EXECUTOR_SERVICE.execute(this);
+                }
+            }
+        });
+    }
+
+
+    @PreDestroy
+    public void destroy() {
+        log.info("{}线程任务关闭", this.getClass().getSimpleName());
+        EXECUTOR_SERVICE.shutdown();
+    }
+
+    public abstract void pullMessage() throws InterruptedException;
+}

+ 59 - 0
im-client/src/main/java/com/bx/imclient/task/GroupMessageResultResultTask.java

@@ -0,0 +1,59 @@
+package com.bx.imclient.task;
+
+import cn.hutool.core.util.StrUtil;
+import com.alibaba.fastjson.JSONObject;
+import com.bx.imclient.listener.MessageListenerMulticaster;
+import com.bx.imcommon.contant.IMRedisKey;
+import com.bx.imcommon.enums.IMListenerType;
+import com.bx.imcommon.model.IMSendResult;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+
+
+@Component
+@RequiredArgsConstructor
+public class GroupMessageResultResultTask extends AbstractMessageResultTask {
+
+    @Resource(name = "IMRedisTemplate")
+    private RedisTemplate<String,Object> redisTemplate;
+
+    @Value("${spring.application.name}")
+    private String appName;
+
+    @Value("${im.result.batch:100}")
+    private int batchSize;
+
+    private final MessageListenerMulticaster listenerMulticaster;
+
+    @Override
+    public void pullMessage() {
+        List<IMSendResult> results;
+        do {
+            results = loadBatch();
+            if(!results.isEmpty()){
+                listenerMulticaster.multicast(IMListenerType.GROUP_MESSAGE, results);
+            }
+        } while (results.size() >= batchSize);
+    }
+
+    List<IMSendResult> loadBatch() {
+        String key = StrUtil.join(":", IMRedisKey.IM_RESULT_GROUP_QUEUE, appName);
+        //这个接口redis6.2以上才支持
+        //List<Object> list = redisTemplate.opsForList().leftPop(key, batchSize);
+        List<IMSendResult> results = new LinkedList<>();
+        JSONObject jsonObject = (JSONObject) redisTemplate.opsForList().leftPop(key);
+        while (!Objects.isNull(jsonObject) && results.size() < batchSize) {
+            results.add(jsonObject.toJavaObject(IMSendResult.class));
+            jsonObject = (JSONObject) redisTemplate.opsForList().leftPop(key);
+        }
+        return results;
+    }
+
+}

+ 60 - 0
im-client/src/main/java/com/bx/imclient/task/PrivateMessageResultResultTask.java

@@ -0,0 +1,60 @@
+package com.bx.imclient.task;
+
+import cn.hutool.core.util.StrUtil;
+import com.alibaba.fastjson.JSONObject;
+import com.bx.imclient.listener.MessageListenerMulticaster;
+import com.bx.imcommon.contant.IMRedisKey;
+import com.bx.imcommon.enums.IMListenerType;
+import com.bx.imcommon.model.IMSendResult;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class PrivateMessageResultResultTask extends AbstractMessageResultTask {
+
+    @Resource(name = "IMRedisTemplate")
+    private RedisTemplate<String, Object> redisTemplate;
+
+    @Value("${spring.application.name}")
+    private String appName;
+
+    @Value("${im.result.batch:100}")
+    private int batchSize;
+
+    private final MessageListenerMulticaster listenerMulticaster;
+
+    @Override
+    public void pullMessage() {
+        List<IMSendResult> results;
+        do {
+            results = loadBatch();
+            if(!results.isEmpty()){
+                listenerMulticaster.multicast(IMListenerType.PRIVATE_MESSAGE, results);
+            }
+        } while (results.size() >= batchSize);
+    }
+
+    List<IMSendResult> loadBatch() {
+        String key = StrUtil.join(":", IMRedisKey.IM_RESULT_PRIVATE_QUEUE, appName);
+        //这个接口redis6.2以上才支持
+        //List<Object> list = redisTemplate.opsForList().leftPop(key, batchSize);
+        List<IMSendResult> results = new LinkedList<>();
+        JSONObject jsonObject = (JSONObject) redisTemplate.opsForList().leftPop(key);
+        while (!Objects.isNull(jsonObject) && results.size() < batchSize) {
+            results.add(jsonObject.toJavaObject(IMSendResult.class));
+            jsonObject = (JSONObject) redisTemplate.opsForList().leftPop(key);
+        }
+        return results;
+    }
+
+}

+ 2 - 0
im-client/src/main/resources/META-INF/spring.factories

@@ -0,0 +1,2 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+com.bx.imclient.IMAutoConfiguration

+ 2 - 0
im-client/target/classes/META-INF/spring.factories

@@ -0,0 +1,2 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+com.bx.imclient.IMAutoConfiguration

BIN
im-client/target/classes/com/bx/imclient/IMAutoConfiguration.class


BIN
im-client/target/classes/com/bx/imclient/IMClient.class


BIN
im-client/target/classes/com/bx/imclient/annotation/IMListener.class


BIN
im-client/target/classes/com/bx/imclient/config/RedisConfig.class


BIN
im-client/target/classes/com/bx/imclient/listener/MessageListener.class


BIN
im-client/target/classes/com/bx/imclient/listener/MessageListenerMulticaster.class


BIN
im-client/target/classes/com/bx/imclient/sender/IMSender.class


BIN
im-client/target/classes/com/bx/imclient/task/AbstractMessageResultTask$1.class


BIN
im-client/target/classes/com/bx/imclient/task/AbstractMessageResultTask.class


BIN
im-client/target/classes/com/bx/imclient/task/GroupMessageResultResultTask.class


BIN
im-client/target/classes/com/bx/imclient/task/PrivateMessageResultResultTask.class


BIN
im-client/target/im-client-2.0.0.jar


+ 5 - 0
im-client/target/maven-archiver/pom.properties

@@ -0,0 +1,5 @@
+#Generated by Maven
+#Tue May 21 14:19:36 GMT+08:00 2024
+groupId=com.bx
+artifactId=im-client
+version=2.0.0

+ 11 - 0
im-client/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst

@@ -0,0 +1,11 @@
+com\bx\imclient\IMAutoConfiguration.class
+com\bx\imclient\listener\MessageListenerMulticaster.class
+com\bx\imclient\task\AbstractMessageResultTask.class
+com\bx\imclient\config\RedisConfig.class
+com\bx\imclient\task\PrivateMessageResultResultTask.class
+com\bx\imclient\annotation\IMListener.class
+com\bx\imclient\sender\IMSender.class
+com\bx\imclient\IMClient.class
+com\bx\imclient\task\AbstractMessageResultTask$1.class
+com\bx\imclient\task\GroupMessageResultResultTask.class
+com\bx\imclient\listener\MessageListener.class

+ 10 - 0
im-client/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst

@@ -0,0 +1,10 @@
+D:\szwWorkSpace\IMServer\im-client\src\main\java\com\bx\imclient\IMClient.java
+D:\szwWorkSpace\IMServer\im-client\src\main\java\com\bx\imclient\task\AbstractMessageResultTask.java
+D:\szwWorkSpace\IMServer\im-client\src\main\java\com\bx\imclient\task\GroupMessageResultResultTask.java
+D:\szwWorkSpace\IMServer\im-client\src\main\java\com\bx\imclient\listener\MessageListenerMulticaster.java
+D:\szwWorkSpace\IMServer\im-client\src\main\java\com\bx\imclient\IMAutoConfiguration.java
+D:\szwWorkSpace\IMServer\im-client\src\main\java\com\bx\imclient\annotation\IMListener.java
+D:\szwWorkSpace\IMServer\im-client\src\main\java\com\bx\imclient\sender\IMSender.java
+D:\szwWorkSpace\IMServer\im-client\src\main\java\com\bx\imclient\task\PrivateMessageResultResultTask.java
+D:\szwWorkSpace\IMServer\im-client\src\main\java\com\bx\imclient\config\RedisConfig.java
+D:\szwWorkSpace\IMServer\im-client\src\main\java\com\bx\imclient\listener\MessageListener.java

+ 69 - 0
im-commom/pom.xml

@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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">
+    <parent>
+        <artifactId>box-im</artifactId>
+        <groupId>com.bx</groupId>
+        <version>2.0.0</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>im-commom</artifactId>
+
+    <properties>
+        <maven.compiler.source>8</maven.compiler.source>
+        <maven.compiler.target>8</maven.compiler.target>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <version>${lombok.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>cn.hutool</groupId>
+            <artifactId>hutool-all</artifactId>
+        </dependency>
+        <!--FastJson-->
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-beans</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.velocity</groupId>
+            <artifactId>velocity</artifactId>
+            <version>${velocity.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.datatype</groupId>
+            <artifactId>jackson-datatype-joda</artifactId>
+            <version>2.9.10</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework</groupId>
+            <artifactId>spring-context</artifactId>
+        </dependency>
+        <!-- 引入操作JWT的相关依赖 -->
+        <dependency>
+            <groupId>com.auth0</groupId>
+            <artifactId>java-jwt</artifactId>
+            <version>3.11.0</version>
+        </dependency>
+        <!-- slf4j -->
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-api</artifactId>
+            <version>1.7.36</version>
+        </dependency>
+    </dependencies>
+</project>

+ 18 - 0
im-commom/src/main/java/com/bx/imcommon/contant/IMConstant.java

@@ -0,0 +1,18 @@
+package com.bx.imcommon.contant;
+
+public final class IMConstant {
+
+    private IMConstant() {
+    }
+
+    /**
+     * 在线状态过期时间 600s
+     */
+    public static final long ONLINE_TIMEOUT_SECOND = 600;
+    /**
+     * 消息允许撤回时间 300s
+     */
+    public static final long ALLOW_RECALL_SECOND = 300;
+
+
+}

+ 32 - 0
im-commom/src/main/java/com/bx/imcommon/contant/IMRedisKey.java

@@ -0,0 +1,32 @@
+package com.bx.imcommon.contant;
+
+public final class IMRedisKey {
+
+    private IMRedisKey() {}
+
+    /**
+     * im-server最大id,从0开始递增
+     */
+    public static final String  IM_MAX_SERVER_ID = "im:max_server_id";
+    /**
+     * 用户ID所连接的IM-server的ID
+     */
+    public static final String  IM_USER_SERVER_ID = "im:user:server_id";
+    /**
+     * 未读私聊消息队列
+     */
+    public static final String IM_MESSAGE_PRIVATE_QUEUE = "im:message:private";
+    /**
+     * 未读群聊消息队列
+     */
+    public static final String IM_MESSAGE_GROUP_QUEUE = "im:message:group";
+    /**
+     * 私聊消息发送结果队列
+     */
+    public static final String IM_RESULT_PRIVATE_QUEUE = "im:result:private";
+    /**
+     * 群聊消息发送结果队列
+     */
+    public static final String IM_RESULT_GROUP_QUEUE = "im:result:group";
+
+}

+ 51 - 0
im-commom/src/main/java/com/bx/imcommon/enums/IMCmdType.java

@@ -0,0 +1,51 @@
+package com.bx.imcommon.enums;
+
+import lombok.AllArgsConstructor;
+
+@AllArgsConstructor
+public enum IMCmdType {
+
+    /**
+     * 登陆
+     */
+    LOGIN(0, "登陆"),
+    /**
+     * 心跳
+     */
+    HEART_BEAT(1, "心跳"),
+    /**
+     * 强制下线
+     */
+    FORCE_LOGUT(2, "强制下线"),
+    /**
+     * 私聊消息
+     */
+    PRIVATE_MESSAGE(3, "私聊消息"),
+    /**
+     * 群发消息
+     */
+    GROUP_MESSAGE(4, "群发消息");
+
+
+    private final Integer code;
+
+    private final String desc;
+
+
+    public static IMCmdType fromCode(Integer code) {
+        for (IMCmdType typeEnum : values()) {
+            if (typeEnum.code.equals(code)) {
+                return typeEnum;
+            }
+        }
+        return null;
+    }
+
+
+    public Integer code() {
+        return this.code;
+    }
+
+
+}
+

+ 28 - 0
im-commom/src/main/java/com/bx/imcommon/enums/IMListenerType.java

@@ -0,0 +1,28 @@
+package com.bx.imcommon.enums;
+
+import lombok.AllArgsConstructor;
+
+@AllArgsConstructor
+public enum IMListenerType {
+    /**
+     * 全部消息
+     */
+    ALL(0, "全部消息"),
+    /**
+     * 私聊消息
+     */
+    PRIVATE_MESSAGE(1, "私聊消息"),
+    /**
+     * 群聊消息
+     */
+    GROUP_MESSAGE(2, "群聊消息");
+
+    private final Integer code;
+
+    private final String desc;
+
+    public Integer code() {
+        return this.code;
+    }
+
+}

+ 32 - 0
im-commom/src/main/java/com/bx/imcommon/enums/IMSendCode.java

@@ -0,0 +1,32 @@
+package com.bx.imcommon.enums;
+
+import lombok.AllArgsConstructor;
+
+@AllArgsConstructor
+public enum IMSendCode {
+
+    /**
+     * 发送成功
+     */
+    SUCCESS(0, "发送成功"),
+    /**
+     * 对方当前不在线
+     */
+    NOT_ONLINE(1, "对方当前不在线"),
+    /**
+     * 未找到对方的channel
+     */
+    NOT_FIND_CHANNEL(2, "未找到对方的channel"),
+    /**
+     * 未知异常
+     */
+    UNKONW_ERROR(9999, "未知异常");
+
+    private final Integer code;
+    private final String desc;
+
+    public Integer code() {
+        return this.code;
+    }
+
+}

+ 47 - 0
im-commom/src/main/java/com/bx/imcommon/enums/IMTerminalType.java

@@ -0,0 +1,47 @@
+package com.bx.imcommon.enums;
+
+import lombok.AllArgsConstructor;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@AllArgsConstructor
+public enum IMTerminalType {
+
+    /**
+     * web
+     */
+    WEB(0, "web"),
+    /**
+     * app
+     */
+    APP(1, "app"),
+    /**
+     * pc
+     */
+    PC(2, "pc");
+
+    private final Integer code;
+
+    private final String desc;
+
+
+    public static IMTerminalType fromCode(Integer code) {
+        for (IMTerminalType typeEnum : values()) {
+            if (typeEnum.code.equals(code)) {
+                return typeEnum;
+            }
+        }
+        return null;
+    }
+
+    public static List<Integer> codes() {
+        return Arrays.stream(values()).map(IMTerminalType::code).collect(Collectors.toList());
+    }
+
+    public Integer code() {
+        return this.code;
+    }
+
+}

+ 44 - 0
im-commom/src/main/java/com/bx/imcommon/model/IMGroupMessage.java

@@ -0,0 +1,44 @@
+package com.bx.imcommon.model;
+
+import com.bx.imcommon.enums.IMTerminalType;
+import lombok.Data;
+
+import java.util.LinkedList;
+import java.util.List;
+
+@Data
+public class IMGroupMessage<T> {
+
+    /**
+     * 发送方
+     */
+    private IMUserInfo sender;
+
+    /**
+     * 接收者id列表(群成员列表,为空则不会推送)
+     */
+    private List<Long> recvIds = new LinkedList<>();
+
+
+    /**
+     * 接收者终端类型,默认全部
+     */
+    private List<Integer> recvTerminals = IMTerminalType.codes();
+
+    /**
+     * 是否发送给自己的其他终端,默认true
+     */
+    private Boolean sendToSelf = true;
+
+    /**
+     * 是否需要回推发送结果,默认true
+     */
+    private Boolean sendResult = true;
+
+    /**
+     * 消息内容
+     */
+    private T data;
+
+
+}

+ 7 - 0
im-commom/src/main/java/com/bx/imcommon/model/IMHeartbeatInfo.java

@@ -0,0 +1,7 @@
+package com.bx.imcommon.model;
+
+import lombok.Data;
+
+@Data
+public class IMHeartbeatInfo {
+}

+ 9 - 0
im-commom/src/main/java/com/bx/imcommon/model/IMLoginInfo.java

@@ -0,0 +1,9 @@
+package com.bx.imcommon.model;
+
+import lombok.Data;
+
+@Data
+public class IMLoginInfo {
+
+    private String accessToken;
+}

+ 44 - 0
im-commom/src/main/java/com/bx/imcommon/model/IMPrivateMessage.java

@@ -0,0 +1,44 @@
+package com.bx.imcommon.model;
+
+import com.bx.imcommon.enums.IMTerminalType;
+import lombok.Data;
+
+import java.util.List;
+
+
+@Data
+public class IMPrivateMessage<T> {
+
+    /**
+     * 发送方
+     */
+    private IMUserInfo sender;
+
+    /**
+     * 接收者id
+     */
+    private Long recvId;
+
+
+    /**
+     * 接收者终端类型,默认全部
+     */
+    private List<Integer> recvTerminals = IMTerminalType.codes();
+
+    /**
+     * 是否发送给自己的其他终端,默认true
+     */
+    private Boolean sendToSelf = true;
+
+    /**
+     * 是否需要回推发送结果,默认true
+     */
+    private Boolean sendResult = true;
+
+    /**
+     * 消息内容
+     */
+    private T data;
+
+
+}

+ 40 - 0
im-commom/src/main/java/com/bx/imcommon/model/IMRecvInfo.java

@@ -0,0 +1,40 @@
+package com.bx.imcommon.model;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class IMRecvInfo {
+
+    /**
+     * 命令类型 IMCmdType
+     */
+    private Integer cmd;
+
+    /**
+     * 发送方
+     */
+    private IMUserInfo sender;
+
+    /**
+     * 接收方用户列表
+     */
+    List<IMUserInfo> receivers;
+
+    /**
+     * 是否需要回调发送结果
+     */
+    private Boolean sendResult;
+
+    /**
+     * 当前服务名(回调发送结果使用)
+     */
+    private String serviceName;
+    /**
+     * 推送消息体
+     */
+    private Object data;
+}
+
+

+ 18 - 0
im-commom/src/main/java/com/bx/imcommon/model/IMSendInfo.java

@@ -0,0 +1,18 @@
+package com.bx.imcommon.model;
+
+import lombok.Data;
+
+@Data
+public class IMSendInfo<T> {
+
+    /**
+     * 命令
+     */
+    private Integer cmd;
+
+    /**
+     * 推送消息体
+     */
+    private T data;
+
+}

+ 28 - 0
im-commom/src/main/java/com/bx/imcommon/model/IMSendResult.java

@@ -0,0 +1,28 @@
+package com.bx.imcommon.model;
+
+import lombok.Data;
+
+@Data
+public class IMSendResult<T> {
+
+    /**
+     * 发送方
+     */
+    private IMUserInfo sender;
+
+    /**
+     * 接收方
+     */
+    private IMUserInfo receiver;
+
+    /**
+     * 发送状态 IMCmdType
+     */
+    private Integer code;
+
+    /**
+     * 消息内容
+     */
+    private T data;
+
+}

+ 17 - 0
im-commom/src/main/java/com/bx/imcommon/model/IMSessionInfo.java

@@ -0,0 +1,17 @@
+package com.bx.imcommon.model;
+
+import lombok.Data;
+
+@Data
+public class IMSessionInfo {
+    /**
+     * 用户id
+     */
+    private Long userId;
+
+    /**
+     * 终端类型
+     */
+    private Integer terminal;
+
+}

+ 28 - 0
im-commom/src/main/java/com/bx/imcommon/model/IMUserInfo.java

@@ -0,0 +1,28 @@
+package com.bx.imcommon.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * @author: 谢绍许
+ * @date: 2023-09-24 09:23:11
+ * @version: 1.0
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class IMUserInfo {
+
+    /**
+     * 用户id
+     */
+    private Long id;
+
+    /**
+     * 用户终端类型 IMTerminalType
+     */
+    private Integer terminal;
+
+
+}

+ 28 - 0
im-commom/src/main/java/com/bx/imcommon/serializer/DateToLongSerializer.java

@@ -0,0 +1,28 @@
+package com.bx.imcommon.serializer;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonToken;
+import com.fasterxml.jackson.core.type.WritableTypeId;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.jsontype.TypeSerializer;
+
+import java.io.IOException;
+import java.util.Date;
+
+public class DateToLongSerializer extends JsonSerializer<Date> {
+
+    @Override
+    public void serialize(Date date, JsonGenerator jsonGenerator,
+                          SerializerProvider serializerProvider) throws IOException {
+        jsonGenerator.writeNumber(date.getTime());
+    }
+
+    @Override
+    public void serializeWithType(Date value, JsonGenerator gen, SerializerProvider serializers, TypeSerializer typeSer) throws IOException {
+        WritableTypeId typeIdDef = typeSer.writeTypePrefix(gen,
+                typeSer.typeId(value, JsonToken.VALUE_STRING));
+        serialize(value, gen, serializers);
+        typeSer.writeTypeSuffix(gen, typeIdDef);
+    }
+}

+ 89 - 0
im-commom/src/main/java/com/bx/imcommon/util/CommaTextUtils.java

@@ -0,0 +1,89 @@
+package com.bx.imcommon.util;
+
+
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 逗号分格文本处理工具类
+ *
+ * @author: blue
+ * @date: 2023-11-09 09:52:49
+ * @version: 1.0
+ */
+public class CommaTextUtils {
+
+    /**
+     * 文本转列表
+     *
+     * @param strText 文件
+     * @return 列表
+     */
+    public static List<String> asList(String strText) {
+        if (StrUtil.isEmpty(strText)) {
+            return new LinkedList<>();
+        }
+        return new LinkedList<>(Arrays.asList(strText.split(",")));
+
+    }
+
+    /**
+     * 列表转字符串,并且自动清空、去重、排序
+     *
+     * @param texts 列表
+     * @return 文本
+     */
+    public static <T> String asText(Collection<T> texts) {
+        if (CollUtil.isEmpty(texts)) {
+            return StrUtil.EMPTY;
+        }
+        return texts.stream().map(text -> StrUtil.toString(text)).filter(StrUtil::isNotEmpty).distinct().sorted().collect(Collectors.joining(","));
+    }
+
+    /**
+     * 追加一个单词
+     *
+     * @param strText 文本
+     * @param word    单词
+     * @return 文本
+     */
+    public static <T> String appendWord(String strText, T word) {
+        List<String> texts = asList(strText);
+        texts.add(StrUtil.toString(word));
+        return asText(texts);
+    }
+
+    /**
+     * 删除一个单词
+     *
+     * @param strText 文本
+     * @param word    单词
+     * @return 文本
+     */
+    public static <T> String removeWord(String strText, T word) {
+        List<String> texts = asList(strText);
+        texts.remove(StrUtil.toString(word));
+        return asText(texts);
+    }
+
+    /**
+     * 合并
+     *
+     * @param strText1 文本1
+     * @param strText2 文本2
+     * @return 文本
+     */
+    public static String merge(String strText1, String strText2) {
+        List<String> texts = asList(strText1);
+        texts.addAll(asList(strText2));
+        return asText(texts);
+    }
+
+}

+ 89 - 0
im-commom/src/main/java/com/bx/imcommon/util/JwtUtil.java

@@ -0,0 +1,89 @@
+package com.bx.imcommon.util;
+
+import com.auth0.jwt.JWT;
+import com.auth0.jwt.JWTVerifier;
+import com.auth0.jwt.algorithms.Algorithm;
+import com.auth0.jwt.exceptions.JWTDecodeException;
+import com.auth0.jwt.exceptions.JWTVerificationException;
+
+import java.util.Date;
+
+public final class JwtUtil {
+
+    private JwtUtil() {
+    }
+
+    /**
+     * 生成jwt字符串  JWT(json web token)
+     *
+     * @param userId   用户id
+     * @param info     用户细腻系
+     * @param expireIn 过期时间
+     * @param secret   秘钥
+     * @return token
+     */
+    public static String sign(Long userId, String info, long expireIn, String secret) {
+        try {
+            Date date = new Date(System.currentTimeMillis() + expireIn * 1000);
+            Algorithm algorithm = Algorithm.HMAC256(secret);
+            return JWT.create()
+                    //将userId保存到token里面
+                    .withAudience(userId.toString())
+                    //存放自定义数据
+                    .withClaim("info", info)
+                    //过期时间
+                    .withExpiresAt(date)
+                    //token的密钥
+                    .sign(algorithm);
+        } catch (Exception e) {
+            return null;
+        }
+    }
+
+    /**
+     * 根据token获取userId
+     *
+     * @param token 登录token
+     * @return 用户id
+     */
+    public static Long getUserId(String token) {
+        try {
+            String userId = JWT.decode(token).getAudience().get(0);
+            return Long.parseLong(userId);
+        } catch (JWTDecodeException e) {
+            return null;
+        }
+    }
+
+    /**
+     * 根据token获取用户数据
+     *
+     * @param token 用户登录token
+     * @return 用户数据
+     */
+    public static String getInfo(String token) {
+        try {
+            return JWT.decode(token).getClaim("info").asString();
+        } catch (JWTDecodeException e) {
+            return null;
+        }
+    }
+
+    /**
+     * 校验token
+     *
+     * @param token  用户登录token
+     * @param secret 秘钥
+     * @return true/false
+     */
+    public static Boolean checkSign(String token, String secret) {
+        try {
+            Algorithm algorithm = Algorithm.HMAC256(secret);
+            JWTVerifier verifier = JWT.require(algorithm).build();
+            verifier.verify(token);
+            return true;
+        } catch (JWTVerificationException e) {
+            return false;
+        }
+    }
+}

+ 101 - 0
im-commom/src/main/java/com/bx/imcommon/util/ThreadPoolExecutorFactory.java

@@ -0,0 +1,101 @@
+package com.bx.imcommon.util;
+
+import lombok.extern.slf4j.Slf4j;
+
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 创建单例线程池
+ * @author Andrews
+ * @date 2023/11/30 11:12
+ */
+@Slf4j
+public final class ThreadPoolExecutorFactory {
+    /**
+     * 机器的CPU核数:Runtime.getRuntime().availableProcessors()
+     * corePoolSize 池中所保存的线程数,包括空闲线程。
+     * CPU 密集型:核心线程数 = CPU核数 + 1
+     * IO 密集型:核心线程数 = CPU核数 * 2
+     */
+    private static final int CORE_POOL_SIZE = Runtime.getRuntime().availableProcessors() * 2;
+    /**
+     * maximumPoolSize - 池中允许的最大线程数(采用LinkedBlockingQueue时没有作用)。
+     */
+    private static final int MAX_IMUM_POOL_SIZE = 100;
+    /**
+     * keepAliveTime -当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间,线程池维护线程所允许的空闲时间
+     */
+    private static final int KEEP_ALIVE_TIME = 1000;
+    /**
+     * 等待队列的大小。默认是无界的,性能损耗的关键
+     */
+    private static final int QUEUE_SIZE = 200;
+
+    /**
+     * 线程池对象
+     */
+    private static volatile ThreadPoolExecutor threadPoolExecutor = null;
+
+    /**
+     * 构造方法私有化
+     */
+    private ThreadPoolExecutorFactory() {
+        if (null == threadPoolExecutor) {
+            threadPoolExecutor = ThreadPoolExecutorFactory.getThreadPoolExecutor();
+        }
+    }
+
+    /**
+     * 重写readResolve方法
+     */
+    private Object readResolve() {
+        //重写readResolve方法,防止序列化破坏单例
+        return ThreadPoolExecutorFactory.getThreadPoolExecutor();
+    }
+
+    /**
+     * 双检锁创建线程安全的单例
+     */
+    public static ThreadPoolExecutor getThreadPoolExecutor() {
+        if (null == threadPoolExecutor) {
+            synchronized (ThreadPoolExecutorFactory.class) {
+                if (null == threadPoolExecutor) {
+                    threadPoolExecutor = new ThreadPoolExecutor(
+                            //核心线程数
+                            CORE_POOL_SIZE,
+                            //最大线程数,包含临时线程
+                            MAX_IMUM_POOL_SIZE,
+                            //临时线程的存活时间
+                            KEEP_ALIVE_TIME,
+                            //时间单位(毫秒)
+                            TimeUnit.MILLISECONDS,
+                            //等待队列
+                            new LinkedBlockingQueue<>(QUEUE_SIZE),
+                            //拒绝策略
+                            new ThreadPoolExecutor.CallerRunsPolicy()
+                    );
+                }
+            }
+        }
+        return threadPoolExecutor;
+    }
+
+    /**
+     * 关闭线程池
+     */
+    public void shutDown() {
+        if (threadPoolExecutor != null) {
+            threadPoolExecutor.shutdown();
+        }
+    }
+
+    public void execute(Runnable runnable) {
+        if (runnable == null) {
+            return;
+        }
+        threadPoolExecutor.execute(runnable);
+    }
+
+}

BIN
im-commom/target/classes/com/bx/imcommon/contant/IMConstant.class


BIN
im-commom/target/classes/com/bx/imcommon/contant/IMRedisKey.class


BIN
im-commom/target/classes/com/bx/imcommon/enums/IMCmdType.class


BIN
im-commom/target/classes/com/bx/imcommon/enums/IMListenerType.class


BIN
im-commom/target/classes/com/bx/imcommon/enums/IMSendCode.class


BIN
im-commom/target/classes/com/bx/imcommon/enums/IMTerminalType.class


BIN
im-commom/target/classes/com/bx/imcommon/model/IMGroupMessage.class


BIN
im-commom/target/classes/com/bx/imcommon/model/IMHeartbeatInfo.class


BIN
im-commom/target/classes/com/bx/imcommon/model/IMLoginInfo.class


BIN
im-commom/target/classes/com/bx/imcommon/model/IMPrivateMessage.class


BIN
im-commom/target/classes/com/bx/imcommon/model/IMRecvInfo.class


BIN
im-commom/target/classes/com/bx/imcommon/model/IMSendInfo.class


BIN
im-commom/target/classes/com/bx/imcommon/model/IMSendResult.class


BIN
im-commom/target/classes/com/bx/imcommon/model/IMSessionInfo.class


BIN
im-commom/target/classes/com/bx/imcommon/model/IMUserInfo.class


BIN
im-commom/target/classes/com/bx/imcommon/serializer/DateToLongSerializer.class


BIN
im-commom/target/classes/com/bx/imcommon/util/CommaTextUtils.class


BIN
im-commom/target/classes/com/bx/imcommon/util/JwtUtil.class


BIN
im-commom/target/classes/com/bx/imcommon/util/ThreadPoolExecutorFactory.class


BIN
im-commom/target/im-commom-2.0.0.jar


+ 5 - 0
im-commom/target/maven-archiver/pom.properties

@@ -0,0 +1,5 @@
+#Generated by Maven
+#Tue May 21 14:19:35 GMT+08:00 2024
+groupId=com.bx
+artifactId=im-commom
+version=2.0.0

+ 19 - 0
im-commom/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst

@@ -0,0 +1,19 @@
+com\bx\imcommon\enums\IMSendCode.class
+com\bx\imcommon\util\ThreadPoolExecutorFactory.class
+com\bx\imcommon\model\IMSendInfo.class
+com\bx\imcommon\enums\IMCmdType.class
+com\bx\imcommon\enums\IMListenerType.class
+com\bx\imcommon\model\IMHeartbeatInfo.class
+com\bx\imcommon\model\IMSendResult.class
+com\bx\imcommon\util\CommaTextUtils.class
+com\bx\imcommon\contant\IMRedisKey.class
+com\bx\imcommon\enums\IMTerminalType.class
+com\bx\imcommon\model\IMGroupMessage.class
+com\bx\imcommon\model\IMLoginInfo.class
+com\bx\imcommon\util\JwtUtil.class
+com\bx\imcommon\contant\IMConstant.class
+com\bx\imcommon\serializer\DateToLongSerializer.class
+com\bx\imcommon\model\IMRecvInfo.class
+com\bx\imcommon\model\IMSessionInfo.class
+com\bx\imcommon\model\IMPrivateMessage.class
+com\bx\imcommon\model\IMUserInfo.class

+ 19 - 0
im-commom/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst

@@ -0,0 +1,19 @@
+D:\szwWorkSpace\IMServer\im-commom\src\main\java\com\bx\imcommon\util\CommaTextUtils.java
+D:\szwWorkSpace\IMServer\im-commom\src\main\java\com\bx\imcommon\contant\IMConstant.java
+D:\szwWorkSpace\IMServer\im-commom\src\main\java\com\bx\imcommon\model\IMLoginInfo.java
+D:\szwWorkSpace\IMServer\im-commom\src\main\java\com\bx\imcommon\enums\IMSendCode.java
+D:\szwWorkSpace\IMServer\im-commom\src\main\java\com\bx\imcommon\model\IMSendInfo.java
+D:\szwWorkSpace\IMServer\im-commom\src\main\java\com\bx\imcommon\enums\IMListenerType.java
+D:\szwWorkSpace\IMServer\im-commom\src\main\java\com\bx\imcommon\model\IMGroupMessage.java
+D:\szwWorkSpace\IMServer\im-commom\src\main\java\com\bx\imcommon\serializer\DateToLongSerializer.java
+D:\szwWorkSpace\IMServer\im-commom\src\main\java\com\bx\imcommon\util\ThreadPoolExecutorFactory.java
+D:\szwWorkSpace\IMServer\im-commom\src\main\java\com\bx\imcommon\contant\IMRedisKey.java
+D:\szwWorkSpace\IMServer\im-commom\src\main\java\com\bx\imcommon\model\IMHeartbeatInfo.java
+D:\szwWorkSpace\IMServer\im-commom\src\main\java\com\bx\imcommon\enums\IMCmdType.java
+D:\szwWorkSpace\IMServer\im-commom\src\main\java\com\bx\imcommon\model\IMRecvInfo.java
+D:\szwWorkSpace\IMServer\im-commom\src\main\java\com\bx\imcommon\enums\IMTerminalType.java
+D:\szwWorkSpace\IMServer\im-commom\src\main\java\com\bx\imcommon\model\IMPrivateMessage.java
+D:\szwWorkSpace\IMServer\im-commom\src\main\java\com\bx\imcommon\util\JwtUtil.java
+D:\szwWorkSpace\IMServer\im-commom\src\main\java\com\bx\imcommon\model\IMUserInfo.java
+D:\szwWorkSpace\IMServer\im-commom\src\main\java\com\bx\imcommon\model\IMSessionInfo.java
+D:\szwWorkSpace\IMServer\im-commom\src\main\java\com\bx\imcommon\model\IMSendResult.java

+ 133 - 0
im-platform/pom.xml

@@ -0,0 +1,133 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<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">
+    <parent>
+        <artifactId>box-im</artifactId>
+        <groupId>com.bx</groupId>
+        <version>2.0.0</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>im-platform</artifactId>
+
+
+    <dependencies>
+        <dependency>
+            <groupId>com.bx</groupId>
+            <artifactId>im-client</artifactId>
+            <version>2.0.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-boot-starter</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>druid</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>mysql</groupId>
+            <artifactId>mysql-connector-java</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-jdbc</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger2</artifactId>
+            <version>${swagger.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>io.springfox</groupId>
+            <artifactId>springfox-swagger-ui</artifactId>
+            <version>${swagger.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.aspectj</groupId>
+            <artifactId>aspectjweaver</artifactId>
+        </dependency>
+        <!-- 引入redis -->
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-data-redis</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-security</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.session</groupId>
+            <artifactId>spring-session-data-redis</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-validation</artifactId>
+        </dependency>
+        <!--minio-->
+        <dependency>
+            <groupId>io.minio</groupId>
+            <artifactId>minio</artifactId>
+            <version>8.4.3</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>com.squareup.okhttp3</groupId>
+                    <artifactId>okhttp</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.jetbrains.kotlin</groupId>
+                    <artifactId>kotlin-stdlib</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>com.squareup.okhttp3</groupId>
+            <artifactId>okhttp</artifactId>
+            <version>4.9.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.jetbrains.kotlin</groupId>
+            <artifactId>kotlin-stdlib</artifactId>
+            <version>1.3.70</version>
+        </dependency>
+        <!--thumbnailator图片处理-->
+        <dependency>
+            <groupId>net.coobird</groupId>
+            <artifactId>thumbnailator</artifactId>
+            <version>0.4.8</version>
+        </dependency>
+        <dependency>
+            <groupId>com.baomidou</groupId>
+            <artifactId>mybatis-plus-generator</artifactId>
+            <version>3.3.2</version>
+        </dependency>
+
+    </dependencies>
+
+    <build>
+        <finalName>${project.artifactId}</finalName>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <version>2.0.3.RELEASE</version>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>repackage</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
+</project>

+ 31 - 0
im-platform/src/main/java/com/bx/implatform/IMPlatformApp.java

@@ -0,0 +1,31 @@
+package com.bx.implatform;
+
+import cn.hutool.core.util.StrUtil;
+import com.bx.implatform.contant.RedisKey;
+import lombok.extern.slf4j.Slf4j;
+import org.mybatis.spring.annotation.MapperScan;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.ApplicationArguments;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
+import org.springframework.context.annotation.EnableAspectJAutoProxy;
+import org.springframework.data.redis.core.RedisTemplate;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+@Slf4j
+@EnableAspectJAutoProxy(exposeProxy = true)
+@MapperScan(basePackages = {"com.bx.implatform.mapper"})
+@SpringBootApplication(exclude = {SecurityAutoConfiguration.class})// 禁用secrity
+public class IMPlatformApp {
+
+    public static void main(String[] args) {
+        SpringApplication.run(IMPlatformApp.class, args);
+    }
+
+}

+ 11 - 0
im-platform/src/main/java/com/bx/implatform/config/ICEServer.java

@@ -0,0 +1,11 @@
+package com.bx.implatform.config;
+
+import lombok.Data;
+
+@Data
+public class ICEServer {
+    private String urls;
+    private String username;
+    private String credential;
+
+}

+ 17 - 0
im-platform/src/main/java/com/bx/implatform/config/ICEServerConfig.java

@@ -0,0 +1,17 @@
+package com.bx.implatform.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Data
+@Component
+@ConfigurationProperties(prefix = "webrtc")
+public class ICEServerConfig {
+
+    private List<ICEServer> iceServers = new ArrayList<>();
+
+}

+ 22 - 0
im-platform/src/main/java/com/bx/implatform/config/JwtProperties.java

@@ -0,0 +1,22 @@
+package com.bx.implatform.config;
+
+import lombok.Data;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+@Data
+@Component
+public class JwtProperties {
+
+    @Value("${jwt.accessToken.expireIn}")
+    private Integer accessTokenExpireIn;
+
+    @Value("${jwt.accessToken.secret}")
+    private String accessTokenSecret;
+
+    @Value("${jwt.refreshToken.expireIn}")
+    private Integer refreshTokenExpireIn;
+
+    @Value("${jwt.refreshToken.secret}")
+    private String refreshTokenSecret;
+}

+ 27 - 0
im-platform/src/main/java/com/bx/implatform/config/MinIoClientConfig.java

@@ -0,0 +1,27 @@
+package com.bx.implatform.config;
+
+import io.minio.MinioClient;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class MinIoClientConfig {
+
+    @Value("${minio.endpoint}")
+    private String endpoint;
+    @Value("${minio.accessKey}")
+    private String accessKey;
+    @Value("${minio.secretKey}")
+    private String secretKey;
+
+
+    @Bean
+    public MinioClient minioClient() {
+        // 注入minio 客户端
+        return MinioClient.builder()
+                .endpoint(endpoint)
+                .credentials(accessKey, secretKey)
+                .build();
+    }
+}

+ 37 - 0
im-platform/src/main/java/com/bx/implatform/config/MvcConfig.java

@@ -0,0 +1,37 @@
+package com.bx.implatform.config;
+
+import com.bx.implatform.interceptor.AuthInterceptor;
+import com.bx.implatform.interceptor.XssInterceptor;
+import lombok.AllArgsConstructor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+@AllArgsConstructor
+public class MvcConfig implements WebMvcConfigurer {
+
+    private final AuthInterceptor authInterceptor;
+    private final XssInterceptor xssInterceptor;
+
+    @Override
+    public void addInterceptors(InterceptorRegistry registry) {
+        registry.addInterceptor(xssInterceptor)
+                .addPathPatterns("/**")
+                .excludePathPatterns("/error");
+        registry.addInterceptor(authInterceptor)
+                .addPathPatterns("/**")
+                .excludePathPatterns("/login", "/logout", "/register", "/refreshToken", "/checkUser",
+                        "/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**");
+    }
+
+    @Bean
+    public PasswordEncoder passwordEncoder() {
+        // 使用BCrypt加密密码
+        return new BCryptPasswordEncoder();
+    }
+
+}

+ 92 - 0
im-platform/src/main/java/com/bx/implatform/config/RedisConfig.java

@@ -0,0 +1,92 @@
+package com.bx.implatform.config;
+
+import com.fasterxml.jackson.annotation.JsonAutoDetect;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.annotation.PropertyAccessor;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import org.springframework.cache.CacheManager;
+import org.springframework.cache.annotation.CachingConfigurerSupport;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.cache.interceptor.CacheErrorHandler;
+import org.springframework.cache.interceptor.CacheResolver;
+import org.springframework.cache.interceptor.SimpleCacheErrorHandler;
+import org.springframework.cache.interceptor.SimpleCacheResolver;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+import org.springframework.data.redis.cache.RedisCacheConfiguration;
+import org.springframework.data.redis.cache.RedisCacheManager;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
+import org.springframework.data.redis.serializer.RedisSerializationContext;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+
+import javax.annotation.Resource;
+import java.time.Duration;
+import java.util.Objects;
+
+@EnableCaching
+@Configuration
+public class RedisConfig extends CachingConfigurerSupport {
+
+    @Resource
+    private RedisConnectionFactory factory;
+
+    @Primary
+    @Bean
+    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
+        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
+        redisTemplate.setConnectionFactory(redisConnectionFactory);
+
+        // 设置值(value)的序列化采用jackson2JsonRedisSerializer
+        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer());
+        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer());
+        // 设置键(key)的序列化采用StringRedisSerializer。
+        redisTemplate.setKeySerializer(new StringRedisSerializer());
+        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
+        redisTemplate.afterPropertiesSet();
+        return redisTemplate;
+    }
+
+    @Bean
+    public Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer() {
+        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
+        ObjectMapper om = new ObjectMapper();
+        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
+        // 解决jackson2无法反序列化LocalDateTime的问题
+        om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
+        om.registerModule(new JavaTimeModule());
+        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
+        jackson2JsonRedisSerializer.setObjectMapper(om);
+        return jackson2JsonRedisSerializer;
+    }
+
+
+    @Bean
+    @Override
+    public CacheResolver cacheResolver() {
+        return new SimpleCacheResolver(Objects.requireNonNull(cacheManager()));
+    }
+
+    @Bean
+    @Override
+    public CacheErrorHandler errorHandler() {
+        // 用于捕获从Cache中进行CRUD时的异常的回调处理器。
+        return new SimpleCacheErrorHandler();
+    }
+
+    @Bean
+    @Override
+    public CacheManager cacheManager() {
+        // 设置redis缓存管理器
+        RedisCacheConfiguration cacheConfiguration =
+                RedisCacheConfiguration.defaultCacheConfig()
+                        .disableCachingNullValues()
+                        .entryTtl(Duration.ofMinutes(10))
+                        .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer()));
+        return RedisCacheManager.builder(factory).cacheDefaults(cacheConfiguration).build();
+    }
+}

+ 41 - 0
im-platform/src/main/java/com/bx/implatform/config/SwaggerConfig.java

@@ -0,0 +1,41 @@
+package com.bx.implatform.config;
+
+import io.swagger.annotations.ApiOperation;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import springfox.documentation.builders.ApiInfoBuilder;
+import springfox.documentation.builders.PathSelectors;
+import springfox.documentation.builders.RequestHandlerSelectors;
+import springfox.documentation.service.ApiInfo;
+import springfox.documentation.spi.DocumentationType;
+import springfox.documentation.spring.web.plugins.Docket;
+import springfox.documentation.swagger2.annotations.EnableSwagger2;
+
+@Configuration
+@EnableSwagger2
+public class SwaggerConfig {
+
+    @Bean
+    public Docket createRestApi() {
+
+        return new Docket(DocumentationType.SWAGGER_2)
+                .apiInfo(apiInfo())
+                .select()
+                //这里采用包含注解的方式来确定要显示的接口
+                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
+                //这里采用包扫描的方式来确定要显示的接口
+                .paths(PathSelectors.any())
+                .build();
+
+    }
+
+    private ApiInfo apiInfo() {
+        return new ApiInfoBuilder()
+                .title("IM Platform doc")
+                .description("盒子IM API文档")
+                .termsOfServiceUrl("http://8.134.92.70/")
+                .version("1.0")
+                .build();
+    }
+
+}

+ 21 - 0
im-platform/src/main/java/com/bx/implatform/contant/Constant.java

@@ -0,0 +1,21 @@
+package com.bx.implatform.contant;
+
+public final class Constant {
+
+    private Constant() {
+    }
+
+    /**
+     * 最大图片上传大小
+     */
+    public static final long MAX_IMAGE_SIZE = 5 * 1024 * 1024;
+    /**
+     * 最大上传文件大小
+     */
+    public static final long MAX_FILE_SIZE = 10 * 1024 * 1024;
+    /**
+     * 群聊最大人数
+     */
+    public static final long MAX_GROUP_MEMBER = 500;
+
+}

+ 33 - 0
im-platform/src/main/java/com/bx/implatform/contant/RedisKey.java

@@ -0,0 +1,33 @@
+package com.bx.implatform.contant;
+
+public final class RedisKey {
+
+    private RedisKey() {
+    }
+
+    /**
+     * 已读群聊消息位置(已读最大id)
+     */
+    public static final String IM_GROUP_READED_POSITION = "im:readed:group:position";
+    /**
+     * webrtc 会话信息
+     */
+    public static final String IM_WEBRTC_SESSION = "im:webrtc:session";
+    /**
+     * 缓存前缀
+     */
+    public static final String IM_CACHE = "im:cache:";
+    /**
+     * 缓存是否好友:bool
+     */
+    public static final String IM_CACHE_FRIEND = IM_CACHE + "friend";
+    /**
+     * 缓存群聊信息
+     */
+    public static final String IM_CACHE_GROUP = IM_CACHE + "group";
+    /**
+     * 缓存群聊成员id
+     */
+    public static final String IM_CACHE_GROUP_MEMBER_ID = IM_CACHE + "group_member_ids";
+
+}

+ 35 - 0
im-platform/src/main/java/com/bx/implatform/controller/FileController.java

@@ -0,0 +1,35 @@
+package com.bx.implatform.controller;
+
+import com.bx.implatform.result.Result;
+import com.bx.implatform.result.ResultUtils;
+import com.bx.implatform.service.thirdparty.FileService;
+import com.bx.implatform.vo.UploadImageVO;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+@Slf4j
+@RestController
+@Api(tags = "文件上传")
+@RequiredArgsConstructor
+public class FileController {
+
+    private final FileService fileService;
+
+    @ApiOperation(value = "上传图片", notes = "上传图片,上传后返回原图和缩略图的url")
+    @PostMapping("/image/upload")
+    public Result<UploadImageVO> uploadImage(MultipartFile file) {
+        return ResultUtils.success(fileService.uploadImage(file));
+    }
+
+    @ApiOperation(value = "上传文件", notes = "上传文件,上传后返回文件url")
+    @PostMapping("/file/upload")
+    public Result<String> uploadFile(MultipartFile file) {
+        return ResultUtils.success(fileService.uploadFile(file), "");
+    }
+
+}

+ 72 - 0
im-platform/src/main/java/com/bx/implatform/controller/FriendController.java

@@ -0,0 +1,72 @@
+package com.bx.implatform.controller;
+
+import com.bx.implatform.entity.Friend;
+import com.bx.implatform.result.Result;
+import com.bx.implatform.result.ResultUtils;
+import com.bx.implatform.service.IFriendService;
+import com.bx.implatform.session.SessionContext;
+import com.bx.implatform.vo.FriendVO;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotEmpty;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Api(tags = "好友")
+@RestController
+@RequestMapping("/friend")
+@RequiredArgsConstructor
+public class FriendController {
+
+    private final IFriendService friendService;
+
+    @GetMapping("/list")
+    @ApiOperation(value = "好友列表", notes = "获取好友列表")
+    public Result<List<FriendVO>> findFriends() {
+        List<Friend> friends = friendService.findFriendByUserId(SessionContext.getSession().getUserId());
+        List<FriendVO> vos = friends.stream().map(f -> {
+            FriendVO vo = new FriendVO();
+            vo.setId(f.getFriendId());
+            vo.setHeadImage(f.getFriendHeadImage());
+            vo.setNickName(f.getFriendNickName());
+            return vo;
+        }).collect(Collectors.toList());
+        return ResultUtils.success(vos);
+    }
+
+
+    @PostMapping("/add")
+    @ApiOperation(value = "添加好友", notes = "双方建立好友关系")
+    public Result addFriend(@NotEmpty(message = "好友id不可为空") @RequestParam("friendId") Long friendId) {
+        friendService.addFriend(friendId);
+        return ResultUtils.success();
+    }
+
+    @GetMapping("/find/{friendId}")
+    @ApiOperation(value = "查找好友信息", notes = "查找好友信息")
+    public Result<FriendVO> findFriend(@NotEmpty(message = "好友id不可为空") @PathVariable("friendId") Long friendId) {
+        return ResultUtils.success(friendService.findFriend(friendId));
+    }
+
+
+    @DeleteMapping("/delete/{friendId}")
+    @ApiOperation(value = "删除好友", notes = "解除好友关系")
+    public Result delFriend(@NotEmpty(message = "好友id不可为空") @PathVariable("friendId") Long friendId) {
+        friendService.delFriend(friendId);
+        return ResultUtils.success();
+    }
+
+    @PutMapping("/update")
+    @ApiOperation(value = "更新好友信息", notes = "更新好友头像或昵称")
+    public Result modifyFriend(@Valid @RequestBody FriendVO vo) {
+        friendService.update(vo);
+        return ResultUtils.success();
+    }
+
+
+}
+

+ 86 - 0
im-platform/src/main/java/com/bx/implatform/controller/GroupController.java

@@ -0,0 +1,86 @@
+package com.bx.implatform.controller;
+
+import com.bx.implatform.result.Result;
+import com.bx.implatform.result.ResultUtils;
+import com.bx.implatform.service.IGroupService;
+import com.bx.implatform.vo.GroupInviteVO;
+import com.bx.implatform.vo.GroupMemberVO;
+import com.bx.implatform.vo.GroupVO;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+@Api(tags = "群聊")
+@RestController
+@RequestMapping("/group")
+@RequiredArgsConstructor
+public class GroupController {
+
+    private final IGroupService groupService;
+
+    @ApiOperation(value = "创建群聊", notes = "创建群聊")
+    @PostMapping("/create")
+    public Result<GroupVO> createGroup(@Valid @RequestBody GroupVO vo) {
+        return ResultUtils.success(groupService.createGroup(vo));
+    }
+
+    @ApiOperation(value = "修改群聊信息", notes = "修改群聊信息")
+    @PutMapping("/modify")
+    public Result<GroupVO> modifyGroup(@Valid @RequestBody GroupVO vo) {
+        return ResultUtils.success(groupService.modifyGroup(vo));
+    }
+
+    @ApiOperation(value = "解散群聊", notes = "解散群聊")
+    @DeleteMapping("/delete/{groupId}")
+    public Result deleteGroup(@NotNull(message = "群聊id不能为空") @PathVariable Long groupId) {
+        groupService.deleteGroup(groupId);
+        return ResultUtils.success();
+    }
+
+    @ApiOperation(value = "查询群聊", notes = "查询单个群聊信息")
+    @GetMapping("/find/{groupId}")
+    public Result<GroupVO> findGroup(@NotNull(message = "群聊id不能为空") @PathVariable Long groupId) {
+        return ResultUtils.success(groupService.findById(groupId));
+    }
+
+    @ApiOperation(value = "查询群聊列表", notes = "查询群聊列表")
+    @GetMapping("/list")
+    public Result<List<GroupVO>> findGroups() {
+        return ResultUtils.success(groupService.findGroups());
+    }
+
+    @ApiOperation(value = "邀请进群", notes = "邀请好友进群")
+    @PostMapping("/invite")
+    public Result invite(@Valid @RequestBody GroupInviteVO vo) {
+        groupService.invite(vo);
+        return ResultUtils.success();
+    }
+
+    @ApiOperation(value = "查询群聊成员", notes = "查询群聊成员")
+    @GetMapping("/members/{groupId}")
+    public Result<List<GroupMemberVO>> findGroupMembers(@NotNull(message = "群聊id不能为空") @PathVariable Long groupId) {
+        return ResultUtils.success(groupService.findGroupMembers(groupId));
+    }
+
+    @ApiOperation(value = "退出群聊", notes = "退出群聊")
+    @DeleteMapping("/quit/{groupId}")
+    public Result quitGroup(@NotNull(message = "群聊id不能为空") @PathVariable Long groupId) {
+        groupService.quitGroup(groupId);
+        return ResultUtils.success();
+    }
+
+    @ApiOperation(value = "踢出群聊", notes = "将用户踢出群聊")
+    @DeleteMapping("/kick/{groupId}")
+    public Result kickGroup(@NotNull(message = "群聊id不能为空") @PathVariable Long groupId,
+                            @NotNull(message = "用户id不能为空") @RequestParam Long userId) {
+        groupService.kickGroup(groupId, userId);
+        return ResultUtils.success();
+    }
+
+}
+

+ 73 - 0
im-platform/src/main/java/com/bx/implatform/controller/GroupMessageController.java

@@ -0,0 +1,73 @@
+package com.bx.implatform.controller;
+
+import com.bx.implatform.dto.GroupMessageDTO;
+import com.bx.implatform.result.Result;
+import com.bx.implatform.result.ResultUtils;
+import com.bx.implatform.service.IGroupMessageService;
+import com.bx.implatform.vo.GroupMessageVO;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+@Api(tags = "群聊消息")
+@RestController
+@RequestMapping("/message/group")
+@RequiredArgsConstructor
+public class GroupMessageController {
+
+    private final IGroupMessageService groupMessageService;
+
+    @PostMapping("/send")
+    @ApiOperation(value = "发送群聊消息", notes = "发送群聊消息")
+    public Result<Long> sendMessage(@Valid @RequestBody GroupMessageDTO vo) {
+        return ResultUtils.success(groupMessageService.sendMessage(vo));
+    }
+
+    @DeleteMapping("/recall/{id}")
+    @ApiOperation(value = "撤回消息", notes = "撤回群聊消息")
+    public Result<Long> recallMessage(@NotNull(message = "消息id不能为空") @PathVariable Long id) {
+        groupMessageService.recallMessage(id);
+        return ResultUtils.success();
+    }
+
+
+    @GetMapping("/loadMessage")
+    @ApiOperation(value = "拉取消息", notes = "拉取消息,一次最多拉取100条")
+    public Result<List<GroupMessageVO>> loadMessage(@RequestParam Long minId) {
+        return ResultUtils.success(groupMessageService.loadMessage(minId));
+    }
+
+    @GetMapping("/pullOfflineMessage")
+    @ApiOperation(value = "拉取离线消息", notes = "拉取离线消息,消息将通过webscoket异步推送")
+    public Result pullOfflineMessage(@RequestParam Long minId) {
+        groupMessageService.pullOfflineMessage(minId);
+        return ResultUtils.success();
+    }
+
+    @PutMapping("/readed")
+    @ApiOperation(value = "消息已读", notes = "将群聊中的消息状态置为已读")
+    public Result readedMessage(@RequestParam Long groupId) {
+        groupMessageService.readedMessage(groupId);
+        return ResultUtils.success();
+    }
+
+    @GetMapping("/findReadedUsers")
+    @ApiOperation(value = "获取已读用户id", notes = "获取消息已读用户列表")
+    public Result<List<Long>> findReadedUsers(@RequestParam Long groupId,@RequestParam Long messageId) {
+        return ResultUtils.success(groupMessageService.findReadedUsers(groupId,messageId));
+    }
+
+    @GetMapping("/history")
+    @ApiOperation(value = "查询聊天记录", notes = "查询聊天记录")
+    public Result<List<GroupMessageVO>> recallMessage(@NotNull(message = "群聊id不能为空") @RequestParam Long groupId,
+                                                      @NotNull(message = "页码不能为空") @RequestParam Long page,
+                                                      @NotNull(message = "size不能为空") @RequestParam Long size) {
+        return ResultUtils.success(groupMessageService.findHistoryMessage(groupId, page, size));
+    }
+}
+

+ 61 - 0
im-platform/src/main/java/com/bx/implatform/controller/LoginController.java

@@ -0,0 +1,61 @@
+package com.bx.implatform.controller;
+
+import com.bx.implatform.dto.LoginDTO;
+import com.bx.implatform.dto.ModifyPwdDTO;
+import com.bx.implatform.dto.RegisterDTO;
+import com.bx.implatform.result.Result;
+import com.bx.implatform.result.ResultUtils;
+import com.bx.implatform.service.IUserService;
+import com.bx.implatform.vo.LoginVO;
+import com.bx.implatform.vo.UserVO;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+
+@Api(tags = "用户登录和注册")
+@RestController
+@RequiredArgsConstructor
+public class LoginController {
+
+    private final IUserService userService;
+
+    @PostMapping("/login")
+    @ApiOperation(value = "用户注册", notes = "用户注册")
+    public Result register(@Valid @RequestBody LoginDTO dto) {
+        LoginVO vo = userService.login(dto);
+        return ResultUtils.success(vo);
+    }
+
+    @PostMapping("/checkUser")
+    @ApiOperation(value = "用户注册", notes = "用户注册")
+    public Result checkUser( @RequestBody UserVO user) {
+        Boolean res =  userService.checkUser(user.getUserName());
+        return ResultUtils.success(res);
+    }
+
+    @PutMapping("/refreshToken")
+    @ApiOperation(value = "刷新token", notes = "用refreshtoken换取新的token")
+    public Result refreshToken(@RequestHeader("refreshToken") String refreshToken) {
+        LoginVO vo = userService.refreshToken(refreshToken);
+        return ResultUtils.success(vo);
+    }
+
+
+    @PostMapping("/register")
+    @ApiOperation(value = "用户注册", notes = "用户注册")
+    public Result register(@Valid @RequestBody RegisterDTO dto) {
+        userService.register(dto);
+        return ResultUtils.success();
+    }
+
+    @PutMapping("/modifyPwd")
+    @ApiOperation(value = "修改密码", notes = "修改用户密码")
+    public Result update(@Valid @RequestBody ModifyPwdDTO dto) {
+        userService.modifyPassword(dto);
+        return ResultUtils.success();
+    }
+
+}

+ 75 - 0
im-platform/src/main/java/com/bx/implatform/controller/PrivateMessageController.java

@@ -0,0 +1,75 @@
+package com.bx.implatform.controller;
+
+import com.bx.implatform.dto.PrivateMessageDTO;
+import com.bx.implatform.result.Result;
+import com.bx.implatform.result.ResultUtils;
+import com.bx.implatform.service.IPrivateMessageService;
+import com.bx.implatform.vo.PrivateMessageVO;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import javax.validation.Valid;
+import javax.validation.constraints.NotNull;
+import java.util.List;
+
+@Api(tags = "私聊消息")
+@RestController
+@RequestMapping("/message/private")
+@RequiredArgsConstructor
+public class PrivateMessageController {
+
+    private final IPrivateMessageService privateMessageService;
+
+    @PostMapping("/send")
+    @ApiOperation(value = "发送消息", notes = "发送私聊消息")
+    public Result<Long> sendMessage(@Valid @RequestBody PrivateMessageDTO vo) {
+        return ResultUtils.success(privateMessageService.sendMessage(vo));
+    }
+
+
+    @DeleteMapping("/recall/{id}")
+    @ApiOperation(value = "撤回消息", notes = "撤回私聊消息")
+    public Result<Long> recallMessage(@NotNull(message = "消息id不能为空") @PathVariable Long id) {
+        privateMessageService.recallMessage(id);
+        return ResultUtils.success();
+    }
+
+
+    @GetMapping("/loadMessage")
+    @ApiOperation(value = "拉取消息", notes = "拉取消息,一次最多拉取100条")
+    public Result<List<PrivateMessageVO>> loadMessage(@RequestParam Long minId) {
+        return ResultUtils.success(privateMessageService.loadMessage(minId));
+    }
+
+    @GetMapping("/pullOfflineMessage")
+    @ApiOperation(value = "拉取离线消息", notes = "拉取离线消息,消息将通过webscoket异步推送")
+    public Result pullOfflineMessage(@RequestParam Long minId) {
+        privateMessageService.pullOfflineMessage(minId);
+        return ResultUtils.success();
+    }
+
+    @PutMapping("/readed")
+    @ApiOperation(value = "消息已读", notes = "将会话中接收的消息状态置为已读")
+    public Result readedMessage(@RequestParam Long friendId) {
+        privateMessageService.readedMessage(friendId);
+        return ResultUtils.success();
+    }
+
+    @GetMapping("/maxReadedId")
+    @ApiOperation(value = "获取最大已读消息的id",notes="获取某个会话中已读消息的最大id")
+    public Result<Long> getMaxReadedId(@RequestParam Long friendId){
+        return ResultUtils.success(privateMessageService.getMaxReadedId(friendId));
+    }
+
+    @GetMapping("/history")
+    @ApiOperation(value = "查询聊天记录", notes = "查询聊天记录")
+    public Result<List<PrivateMessageVO>> recallMessage(@NotNull(message = "好友id不能为空") @RequestParam Long friendId,
+                                                        @NotNull(message = "页码不能为空") @RequestParam Long page,
+                                                        @NotNull(message = "size不能为空") @RequestParam Long size) {
+        return ResultUtils.success(privateMessageService.findHistoryMessage(friendId, page, size));
+    }
+
+}
+

Datei-Diff unterdrückt, da er zu groß ist
+ 24 - 0
im-platform/src/main/java/com/bx/implatform/controller/UserController.java


+ 79 - 0
im-platform/src/main/java/com/bx/implatform/controller/WebrtcController.java

@@ -0,0 +1,79 @@
+package com.bx.implatform.controller;
+
+import com.bx.implatform.config.ICEServer;
+import com.bx.implatform.result.Result;
+import com.bx.implatform.result.ResultUtils;
+import com.bx.implatform.service.IWebrtcService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+@Api(tags = "webrtc视频单人通话")
+@RestController
+@RequestMapping("/webrtc/private")
+@RequiredArgsConstructor
+public class WebrtcController {
+
+    private final IWebrtcService webrtcService;
+
+    @ApiOperation(httpMethod = "POST", value = "呼叫视频通话")
+    @PostMapping("/call")
+    public Result call(@RequestParam Long uid, @RequestParam(defaultValue = "video") String mode, @RequestBody String offer) {
+        webrtcService.call(uid, mode, offer);
+        return ResultUtils.success();
+    }
+
+    @ApiOperation(httpMethod = "POST", value = "接受视频通话")
+    @PostMapping("/accept")
+    public Result accept(@RequestParam Long uid, @RequestBody String answer) {
+        webrtcService.accept(uid, answer);
+        return ResultUtils.success();
+    }
+
+
+    @ApiOperation(httpMethod = "POST", value = "拒绝视频通话")
+    @PostMapping("/reject")
+    public Result reject(@RequestParam Long uid) {
+        webrtcService.reject(uid);
+        return ResultUtils.success();
+    }
+
+    @ApiOperation(httpMethod = "POST", value = "取消呼叫")
+    @PostMapping("/cancel")
+    public Result cancel(@RequestParam Long uid) {
+        webrtcService.cancel(uid);
+        return ResultUtils.success();
+    }
+
+    @ApiOperation(httpMethod = "POST", value = "呼叫失败")
+    @PostMapping("/failed")
+    public Result failed(@RequestParam Long uid, @RequestParam String reason) {
+        webrtcService.failed(uid, reason);
+        return ResultUtils.success();
+    }
+
+    @ApiOperation(httpMethod = "POST", value = "挂断")
+    @PostMapping("/handup")
+    public Result handup(@RequestParam Long uid) {
+        webrtcService.handup(uid);
+        return ResultUtils.success();
+    }
+
+
+    @PostMapping("/candidate")
+    @ApiOperation(httpMethod = "POST", value = "同步candidate")
+    public Result candidate(@RequestParam Long uid, @RequestBody String candidate) {
+        webrtcService.candidate(uid, candidate);
+        return ResultUtils.success();
+    }
+
+
+    @GetMapping("/iceservers")
+    @ApiOperation(httpMethod = "GET", value = "获取iceservers")
+    public Result<List<ICEServer>> iceservers() {
+        return ResultUtils.success(webrtcService.getIceServers());
+    }
+}

+ 36 - 0
im-platform/src/main/java/com/bx/implatform/dto/GroupMessageDTO.java

@@ -0,0 +1,36 @@
+package com.bx.implatform.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.hibernate.validator.constraints.Length;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import java.util.List;
+
+@Data
+@ApiModel("群聊消息DTO")
+public class GroupMessageDTO {
+
+    @NotNull(message = "群聊id不可为空")
+    @ApiModelProperty(value = "群聊id")
+    private Long groupId;
+
+    @Length(max = 1024, message = "发送内容长度不得大于1024")
+    @NotEmpty(message = "发送内容不可为空")
+    @ApiModelProperty(value = "发送内容")
+    private String content;
+
+    @NotNull(message = "消息类型不可为空")
+    @ApiModelProperty(value = "消息类型")
+    private Integer type;
+
+    @ApiModelProperty(value = "是否回执消息")
+    private Boolean receipt = false;
+
+    @Size(max = 20, message = "一次最多只能@20个小伙伴哦")
+    @ApiModelProperty(value = "被@用户列表")
+    private List<Long> atUserIds;
+}

+ 30 - 0
im-platform/src/main/java/com/bx/implatform/dto/LoginDTO.java

@@ -0,0 +1,30 @@
+package com.bx.implatform.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.Max;
+import javax.validation.constraints.Min;
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+
+@Data
+@ApiModel("用户登录DTO")
+public class LoginDTO {
+
+    @Max(value = 2, message = "登录终端类型取值范围:0,2")
+    @Min(value = 0, message = "登录终端类型取值范围:0,2")
+    @NotNull(message = "登录终端类型不可为空")
+    @ApiModelProperty(value = "登录终端 0:web 1:app 2:pc")
+    private Integer terminal;
+
+    @NotEmpty(message = "用户名不可为空")
+    @ApiModelProperty(value = "用户名")
+    private String userName;
+
+    @NotEmpty(message = "用户密码不可为空")
+    @ApiModelProperty(value = "用户密码")
+    private String password;
+
+}

+ 21 - 0
im-platform/src/main/java/com/bx/implatform/dto/ModifyPwdDTO.java

@@ -0,0 +1,21 @@
+package com.bx.implatform.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+
+import javax.validation.constraints.NotEmpty;
+
+@Data
+@ApiModel("修改密码DTO")
+public class ModifyPwdDTO {
+
+    @NotEmpty(message = "旧用户密码不可为空")
+    @ApiModelProperty(value = "旧用户密码")
+    private String oldPassword;
+
+    @NotEmpty(message = "新用户密码不可为空")
+    @ApiModelProperty(value = "新用户密码")
+    private String newPassword;
+
+}

+ 29 - 0
im-platform/src/main/java/com/bx/implatform/dto/PrivateMessageDTO.java

@@ -0,0 +1,29 @@
+package com.bx.implatform.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.hibernate.validator.constraints.Length;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+
+@Data
+@ApiModel("私聊消息DTO")
+public class PrivateMessageDTO {
+
+    @NotNull(message = "接收用户id不可为空")
+    @ApiModelProperty(value = "接收用户id")
+    private Long recvId;
+
+
+    @Length(max = 1024, message = "内容长度不得大于1024")
+    @NotEmpty(message = "发送内容不可为空")
+    @ApiModelProperty(value = "发送内容")
+    private String content;
+
+    @NotNull(message = "消息类型不可为空")
+    @ApiModelProperty(value = "消息类型")
+    private Integer type;
+
+}

+ 30 - 0
im-platform/src/main/java/com/bx/implatform/dto/RegisterDTO.java

@@ -0,0 +1,30 @@
+package com.bx.implatform.dto;
+
+import io.swagger.annotations.ApiModel;
+import io.swagger.annotations.ApiModelProperty;
+import lombok.Data;
+import org.hibernate.validator.constraints.Length;
+
+import javax.validation.constraints.NotEmpty;
+
+@Data
+@ApiModel("用户注册DTO")
+public class RegisterDTO {
+
+    @Length(max = 64, message = "用户名不能大于64字符")
+    @NotEmpty(message = "用户名不可为空")
+    @ApiModelProperty(value = "用户名")
+    private String userName;
+
+    @Length(min = 5, max = 20, message = "密码长度必须在5-20个字符之间")
+    @NotEmpty(message = "用户密码不可为空")
+    @ApiModelProperty(value = "用户密码")
+    private String password;
+
+    @Length(max = 64, message = "昵称不能大于64字符")
+    @NotEmpty(message = "用户昵称不可为空")
+    @ApiModelProperty(value = "用户昵称")
+    private String nickName;
+
+
+}

+ 71 - 0
im-platform/src/main/java/com/bx/implatform/entity/Friend.java

@@ -0,0 +1,71 @@
+package com.bx.implatform.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.extension.activerecord.Model;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * <p>
+ * 好友
+ * </p>
+ *
+ * @author blue
+ * @since 2022-10-22
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("im_friend")
+public class Friend extends Model<Friend> {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * id
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 用户id
+     */
+    @TableField("user_id")
+    private Long userId;
+
+    /**
+     * 好友id
+     */
+    @TableField("friend_id")
+    private Long friendId;
+
+    /**
+     * 用户昵称
+     */
+    @TableField("friend_nick_name")
+    private String friendNickName;
+
+    /**
+     * 用户头像
+     */
+    @TableField("friend_head_image")
+    private String friendHeadImage;
+
+    /**
+     * 创建时间
+     */
+    @TableField("created_time")
+    private Date createdTime;
+
+
+    @Override
+    protected Serializable pkVal() {
+        return this.id;
+    }
+
+}

+ 81 - 0
im-platform/src/main/java/com/bx/implatform/entity/Group.java

@@ -0,0 +1,81 @@
+package com.bx.implatform.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.extension.activerecord.Model;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 群
+ *
+ * @author blue
+ * @since 2022-10-31
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@TableName("im_group")
+public class Group extends Model<Group> {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * id
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 群名字
+     */
+    @TableField("name")
+    private String name;
+
+    /**
+     * 群主id
+     */
+    @TableField("owner_id")
+    private Long ownerId;
+
+    /**
+     * 头像
+     */
+    @TableField("head_image")
+    private String headImage;
+
+    /**
+     * 头像缩略图
+     */
+    @TableField("head_image_thumb")
+    private String headImageThumb;
+
+    /**
+     * 群公告
+     */
+    @TableField("notice")
+    private String notice;
+
+    /**
+     * 是否已删除
+     */
+    @TableField("deleted")
+    private Boolean deleted;
+
+    /**
+     * 创建时间
+     */
+    @TableField("created_time")
+    private Date createdTime;
+
+
+    @Override
+    protected Serializable pkVal() {
+        return this.id;
+    }
+
+}

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.