Browse Source

!79 3.0版本上线

Merge pull request !79 from blue/v_3.0.0
master
blue 2 years ago
committed by Gitee
parent
commit
80b1bfeac7
No known key found for this signature in database GPG Key ID: 173E9B9CA92EEF8F
  1. 8
      .gitignore
  2. 200
      README.md
  3. 41
      db/im-platform.sql
  4. 9
      im-client/pom.xml
  5. 2
      im-client/src/main/java/com/bx/imclient/IMAutoConfiguration.java
  6. 13
      im-client/src/main/java/com/bx/imclient/IMClient.java
  7. 31
      im-client/src/main/java/com/bx/imclient/config/RedisConfig.java
  8. 87
      im-client/src/main/java/com/bx/imclient/sender/IMSender.java
  9. 39
      im-client/src/main/java/com/bx/imclient/task/AbstractMessageResultTask.java
  10. 42
      im-client/src/main/java/com/bx/imclient/task/GroupMessageResultResultTask.java
  11. 46
      im-client/src/main/java/com/bx/imclient/task/PrivateMessageResultResultTask.java
  12. 25
      im-client/src/main/java/com/bx/imclient/task/SystemMessageResultResultTask.java
  13. 2
      im-client/src/main/resources/META-INF/spring.factories
  14. 1
      im-client/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
  15. 21
      im-commom/pom.xml
  16. 14
      im-commom/src/main/java/com/bx/imcommon/contant/IMRedisKey.java
  17. 6
      im-commom/src/main/java/com/bx/imcommon/enums/IMCmdType.java
  18. 8
      im-commom/src/main/java/com/bx/imcommon/enums/IMListenerType.java
  19. 2
      im-commom/src/main/java/com/bx/imcommon/model/IMPrivateMessage.java
  20. 2
      im-commom/src/main/java/com/bx/imcommon/model/IMSendResult.java
  21. 34
      im-commom/src/main/java/com/bx/imcommon/model/IMSystemMessage.java
  22. 32
      im-commom/src/main/java/com/bx/imcommon/mq/RedisMQConfig.java
  23. 35
      im-commom/src/main/java/com/bx/imcommon/mq/RedisMQConsumer.java
  24. 29
      im-commom/src/main/java/com/bx/imcommon/mq/RedisMQListener.java
  25. 114
      im-commom/src/main/java/com/bx/imcommon/mq/RedisMQPullTask.java
  26. 52
      im-commom/src/main/java/com/bx/imcommon/mq/RedisMQTemplate.java
  27. 37
      im-commom/src/main/java/com/bx/imcommon/util/ThreadPoolExecutorFactory.java
  28. 26
      im-docker-compose/README.md
  29. 27
      im-docker-compose/im-coturn/docker-compose.yml
  30. 99
      im-docker-compose/im-service/docker-compose.yml
  31. 28
      im-docker-compose/im-service/im-platform.env
  32. 4
      im-docker-compose/im-service/im-server.env
  33. 78
      im-platform/pom.xml
  34. 15
      im-platform/src/main/java/com/bx/implatform/IMPlatformApp.java
  35. 1
      im-platform/src/main/java/com/bx/implatform/annotation/OnlineCheck.java
  36. 7
      im-platform/src/main/java/com/bx/implatform/aspect/OnlineCheckAspect.java
  37. 2
      im-platform/src/main/java/com/bx/implatform/aspect/RedisLockAspect.java
  38. 11
      im-platform/src/main/java/com/bx/implatform/config/MvcConfig.java
  39. 54
      im-platform/src/main/java/com/bx/implatform/config/RedisConfig.java
  40. 9
      im-platform/src/main/java/com/bx/implatform/config/RedissonConfig.java
  41. 51
      im-platform/src/main/java/com/bx/implatform/config/SwaggerConfig.java
  42. 13
      im-platform/src/main/java/com/bx/implatform/contant/Constant.java
  43. 31
      im-platform/src/main/java/com/bx/implatform/contant/RedisKey.java
  44. 15
      im-platform/src/main/java/com/bx/implatform/controller/FileController.java
  45. 30
      im-platform/src/main/java/com/bx/implatform/controller/FriendController.java
  46. 37
      im-platform/src/main/java/com/bx/implatform/controller/GroupController.java
  47. 37
      im-platform/src/main/java/com/bx/implatform/controller/GroupMessageController.java
  48. 25
      im-platform/src/main/java/com/bx/implatform/controller/LoginController.java
  49. 38
      im-platform/src/main/java/com/bx/implatform/controller/PrivateMessageController.java
  50. 13
      im-platform/src/main/java/com/bx/implatform/controller/SystemController.java
  51. 30
      im-platform/src/main/java/com/bx/implatform/controller/UserController.java
  52. 58
      im-platform/src/main/java/com/bx/implatform/controller/WebrtcGroupController.java
  53. 34
      im-platform/src/main/java/com/bx/implatform/controller/WebrtcPrivateController.java
  54. 21
      im-platform/src/main/java/com/bx/implatform/dto/GroupBanDTO.java
  55. 21
      im-platform/src/main/java/com/bx/implatform/dto/GroupMessageDTO.java
  56. 18
      im-platform/src/main/java/com/bx/implatform/dto/GroupUnbanDTO.java
  57. 20
      im-platform/src/main/java/com/bx/implatform/dto/LoginDTO.java
  58. 12
      im-platform/src/main/java/com/bx/implatform/dto/ModifyPwdDTO.java
  59. 16
      im-platform/src/main/java/com/bx/implatform/dto/PrivateMessageDTO.java
  60. 14
      im-platform/src/main/java/com/bx/implatform/dto/RegisterDTO.java
  61. 21
      im-platform/src/main/java/com/bx/implatform/dto/UserBanDTO.java
  62. 16
      im-platform/src/main/java/com/bx/implatform/dto/WebrtcGroupAnswerDTO.java
  63. 17
      im-platform/src/main/java/com/bx/implatform/dto/WebrtcGroupCandidateDTO.java
  64. 15
      im-platform/src/main/java/com/bx/implatform/dto/WebrtcGroupDeviceDTO.java
  65. 12
      im-platform/src/main/java/com/bx/implatform/dto/WebrtcGroupFailedDTO.java
  66. 13
      im-platform/src/main/java/com/bx/implatform/dto/WebrtcGroupInviteDTO.java
  67. 11
      im-platform/src/main/java/com/bx/implatform/dto/WebrtcGroupJoinDTO.java
  68. 16
      im-platform/src/main/java/com/bx/implatform/dto/WebrtcGroupOfferDTO.java
  69. 13
      im-platform/src/main/java/com/bx/implatform/dto/WebrtcGroupSetupDTO.java
  70. 22
      im-platform/src/main/java/com/bx/implatform/entity/Friend.java
  71. 41
      im-platform/src/main/java/com/bx/implatform/entity/Group.java
  72. 46
      im-platform/src/main/java/com/bx/implatform/entity/GroupMember.java
  73. 30
      im-platform/src/main/java/com/bx/implatform/entity/GroupMessage.java
  74. 25
      im-platform/src/main/java/com/bx/implatform/entity/PrivateMessage.java
  75. 50
      im-platform/src/main/java/com/bx/implatform/entity/SensitiveWord.java
  76. 61
      im-platform/src/main/java/com/bx/implatform/entity/User.java
  77. 59
      im-platform/src/main/java/com/bx/implatform/enums/MessageType.java
  78. 6
      im-platform/src/main/java/com/bx/implatform/filter/CacheFilter.java
  79. 8
      im-platform/src/main/java/com/bx/implatform/filter/CacheHttpServletRequestWrapper.java
  80. 102
      im-platform/src/main/java/com/bx/implatform/generator/CodeGenerator.java
  81. 7
      im-platform/src/main/java/com/bx/implatform/interceptor/AuthInterceptor.java
  82. 4
      im-platform/src/main/java/com/bx/implatform/interceptor/XssInterceptor.java
  83. 2
      im-platform/src/main/java/com/bx/implatform/listener/GroupMessageListener.java
  84. 4
      im-platform/src/main/java/com/bx/implatform/listener/PrivateMessageListener.java
  85. 26
      im-platform/src/main/java/com/bx/implatform/listener/SystemMessageListener.java
  86. 16
      im-platform/src/main/java/com/bx/implatform/mapper/SensitiveWordMapper.java
  87. 2
      im-platform/src/main/java/com/bx/implatform/service/FriendService.java
  88. 2
      im-platform/src/main/java/com/bx/implatform/service/GroupMemberService.java
  89. 4
      im-platform/src/main/java/com/bx/implatform/service/GroupMessageService.java
  90. 4
      im-platform/src/main/java/com/bx/implatform/service/GroupService.java
  91. 4
      im-platform/src/main/java/com/bx/implatform/service/PrivateMessageService.java
  92. 22
      im-platform/src/main/java/com/bx/implatform/service/SensitiveWordService.java
  93. 2
      im-platform/src/main/java/com/bx/implatform/service/UserService.java
  94. 3
      im-platform/src/main/java/com/bx/implatform/service/WebrtcGroupService.java
  95. 6
      im-platform/src/main/java/com/bx/implatform/service/WebrtcPrivateService.java
  96. 18
      im-platform/src/main/java/com/bx/implatform/service/impl/FriendServiceImpl.java
  97. 4
      im-platform/src/main/java/com/bx/implatform/service/impl/GroupMemberServiceImpl.java
  98. 61
      im-platform/src/main/java/com/bx/implatform/service/impl/GroupMessageServiceImpl.java
  99. 143
      im-platform/src/main/java/com/bx/implatform/service/impl/GroupServiceImpl.java
  100. 37
      im-platform/src/main/java/com/bx/implatform/service/impl/PrivateMessageServiceImpl.java

8
.gitignore

@ -9,10 +9,10 @@
/im-server/src/main/resources/logback-prod.xml /im-server/src/main/resources/logback-prod.xml
/im-commom/im-commom.iml /im-commom/im-commom.iml
/im-uniapp/node_modules/ /im-uniapp/node_modules/
/im-ui/package-lock.json /im-web/package-lock.json
/im-uniapp/unpackage/ /im-uniapp/unpackage/
/im-uniapp/hybrid/ /im-uniapp/hybrid/
/im-uniapp/package-lock.json /im-uniapp/package-lock.json
/im-ui/src/components/rtc/LocalVideo.vue /im-web/src/components/rtc/LocalVideo.vue
/im-ui/src/components/rtc/RemoteVideo.vue /im-web/src/components/rtc/RemoteVideo.vue
/im-ui/src/components/rtc/RtcGroupAcceptor.vue /im-web/src/components/rtc/RtcGroupAcceptor.vue

200
README.md

@ -6,8 +6,9 @@
<a href="#加入交流群"><img src="https://img.shields.io/badge/QQ交流群-green.svg?style=plasticr"></a> <a href="#加入交流群"><img src="https://img.shields.io/badge/QQ交流群-green.svg?style=plasticr"></a>
1. 盒子IM是一个仿微信实现的网页版聊天软件,不依赖任何第三方收费组件。 1. 盒子IM是一个仿微信实现的网页版聊天软件,不依赖任何第三方收费组件。
1. 支持私聊、群聊、离线消息、发送语音、图片、文件、emoji表情等功能 1. 支持私聊、群聊、离线消息、发送语音、图片、文件、已读未读、群@等功能
1. 支持音视频通话(基于webrtc实现,需要ssl证书) 1. 支持单人、多人音视频通话(基于原生webrtc实现,需要ssl证书)
1. uniapp端兼容app、h5、微信小程序,可与web端同时在线,并保持消息同步
1. 后端采用springboot+netty实现,网页端使用vue,移动端使用uniapp 1. 后端采用springboot+netty实现,网页端使用vue,移动端使用uniapp
1. 服务器支持集群化部署,每个im-server仅处理自身连接用户的消息 1. 服务器支持集群化部署,每个im-server仅处理自身连接用户的消息
@ -16,13 +17,11 @@
#### 近期更新 #### 近期更新
发布2.0版本,本次更新加入了uniapp移动端: 发布3.0版本:
- 支持移动端和web端同时在线,多端消息同步 - 后台管理端上线,后台管理代码仓库地址:https://gitee.com/bluexsx/box-im-admin
- 目前已兼容h5、微信小程序,安卓和IOS - 框架和组件版本全面升级: jdk17、springboot3.3、node18等
- 聊天窗口加入已读未读显示 - 部分界面,功能、性能优化
- 群聊加入@功能
- 界面风格升级,表情包更新、生成文字头像等
#### 在线体验 #### 在线体验
@ -37,27 +36,18 @@
![输入图片说明](%E6%88%AA%E5%9B%BE/h5%E4%BA%8C%E7%BB%B4%E7%A0%81.png) ![输入图片说明](%E6%88%AA%E5%9B%BE/h5%E4%BA%8C%E7%BB%B4%E7%A0%81.png)
微信小程序: 由于微信小程序每次发布审核过于严苛和繁琐,暂时不再提供体验环境,但uniapp端依然会继续兼容小程序
![输入图片说明](%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)
#### 相关项目
一位网友的开源项目,基于盒子IM接口开发的仿QQ客户端,有兴趣的小伙伴可以也关注一下:
https://gitee.com/zyzyteam/crim
#### 项目结构 #### 项目结构
| 模块 | 功能 | | 模块 | 功能 |
|-------------|------------| |-------------|----------------------------------|
| im-platform | 与页面进行交互,处理业务请求 | | im-platform | 业务平台服务,负责处理来自用户的业务请求(http) |
| im-server | 推送聊天消息| | im-server | 消息推送服务,不依赖业务,负责将消息推送给用户(ws) |
| im-client | 消息推送sdk| | im-client | 消息推送sdk, 任何服务均可集成此sdk与im-server通信 |
| im-common | 公共包 | | im-common | 公共包 |
| im-ui | web页面 | | im-ui | web页面 |
| im-uniapp | app页面 | | im-uniapp | uniapp页面 |
#### 消息推送方案 #### 消息推送方案
![输入图片说明](%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) ![输入图片说明](%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)
@ -69,12 +59,12 @@ https://gitee.com/zyzyteam/crim
#### 本地快速部署 #### 本地快速部署
1.安装运行环境 1.安装运行环境
- 安装node:v14.16.0 - 安装node:v18.19.0
- 安装jdk:1.8 - 安装jdk:17
- 安装maven:3.6.3 - 安装maven:3.9.6
- 安装mysql:5.7,密码分别为root/root,运行sql脚本(脚本在im-platfrom的resources/db目录) - 安装mysql:8.0,账号密码分别为root/root,创建名为im_platform的数据库,运行db/im_platfrom.sql脚本
- 安装redis:5.0 - 安装redis:6.2
- 安装minio,命令端口使用9001,并创建一个名为"box-im"的bucket,并设置访问权限为公开 - 安装minio:RELEASE.2024-xx,使用默认账号、密码、端口
2.启动后端服务 2.启动后端服务
``` ```
@ -85,161 +75,37 @@ java -jar ./im-server/target/im-server.jar
3.启动前端web 3.启动前端web
``` ```
cd im-ui cd im-web
npm install npm install
npm run serve npm run serve
``` ```
访问 http://localhost:8080 访问 http://localhost:8080
4.启动uniapp-h5 4.启动uniapp-h5
将im-uniapp目录导入HBuilderX,点击菜单"运行"->"开发环境-h5" 将im-uniapp目录导入HBuilderX,点击菜单"运行"->"开发环境-h5"
访问 http://localhost:5173 访问 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.connect(wsUrl,token);
wsApi.onConnect(() => {
// 连接打开
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) => {
if (e.code != 3000) {
console.log("意外断开,进行重连");
wsApi.reconnect(wsUrl,token);
}else{
console.log("主动断开");
}
});
```
#### 界面截图 #### 界面截图
私聊: 私聊:
![输入图片说明](%E6%88%AA%E5%9B%BE/web/%E7%A7%81%E8%81%8A.jpg) ![输入图片说明](%E6%88%AA%E5%9B%BE/web/%E7%A7%81%E8%81%8A.png)
群聊: 群聊:
![输入图片说明](%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%8A.png)
![输入图片说明](%E6%88%AA%E5%9B%BE/web/%E7%BE%A4%E8%81%8A2.jpg)
群通话:
![输入图片说明](%E6%88%AA%E5%9B%BE/web/%E7%BE%A4%E8%A7%86%E9%A2%91.png)
好友列表: 好友列表:
![输入图片说明](%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/%E5%A5%BD%E5%8F%8B%E5%88%97%E8%A1%A8.png)
列表: 群列表:
![输入图片说明](%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/web/%E7%BE%A4%E5%88%97%E8%A1%A8.png)
微信小程序: 移动端APP:
![输入图片说明](%E6%88%AA%E5%9B%BE/wx-mp/%E8%81%8A%E5%A4%A9.jpg) ![输入图片说明](%E6%88%AA%E5%9B%BE/app/1.jpg)
![输入图片说明](%E6%88%AA%E5%9B%BE/wx-mp/%E5%85%B6%E4%BB%96.jpg) ![输入图片说明](%E6%88%AA%E5%9B%BE/app/2.jpg)
#### 加入交流群 #### 加入交流群
1群目前已满员,扫码进入2群: 1群目前已满员,扫码进入2群:

41
im-platform/src/main/resources/db/db.sql → db/im-platform.sql

@ -1,19 +1,20 @@
use `box-im`;
create table `im_user`( create table `im_user`(
`id` bigint not null auto_increment primary key comment 'id', `id` bigint not null auto_increment primary key comment 'id',
`user_name` varchar(255) not null comment '用户名', `user_name` varchar(255) not null comment '用户名',
`nick_name` varchar(255) not null comment '用户昵称', `nick_name` varchar(255) not null comment '用户昵称',
`head_image` varchar(255) default '' comment '用户头像', `head_image` varchar(255) default '' comment '用户头像',
`head_image_thumb` varchar(255) default '' comment '用户头像缩略图', `head_image_thumb` varchar(255) default '' comment '用户头像缩略图',
`password` varchar(255) not null comment '密码(明文)', `password` varchar(255) not null comment '密码',
`sex` tinyint(1) default 0 comment '性别 0:男 1:女', `sex` tinyint(1) default 0 comment '性别 0:男 1:女',
`is_banned` tinyint(1) default 0 comment '是否被封禁 0:否 1:是',
`reason` varchar(255) default '' comment '被封禁原因',
`type` smallint default 1 comment '用户类型 1:普通用户 2:审核账户', `type` smallint default 1 comment '用户类型 1:普通用户 2:审核账户',
`signature` varchar(1024) default '' comment '个性签名', `signature` varchar(1024) default '' comment '个性签名',
`last_login_time` datetime DEFAULT null comment '最后登录时间', `last_login_time` datetime DEFAULT null comment '最后登录时间',
`created_time` datetime DEFAULT CURRENT_TIMESTAMP comment '创建时间', `created_time` datetime DEFAULT CURRENT_TIMESTAMP comment '创建时间',
unique key `idx_user_name`(user_name), unique key `idx_user_name`(user_name),
key `idx_nick_name`(nick_name) key `idx_nick_name`(nick_name)
) ENGINE=InnoDB CHARSET=utf8mb3 comment '用户'; ) ENGINE=InnoDB CHARSET=utf8mb4 comment '用户';
create table `im_friend`( create table `im_friend`(
`id` bigint not null auto_increment primary key comment 'id', `id` bigint not null auto_increment primary key comment 'id',
@ -24,18 +25,18 @@ create table `im_friend`(
`created_time` datetime DEFAULT CURRENT_TIMESTAMP comment '创建时间', `created_time` datetime DEFAULT CURRENT_TIMESTAMP comment '创建时间',
key `idx_user_id` (`user_id`), key `idx_user_id` (`user_id`),
key `idx_friend_id` (`friend_id`) key `idx_friend_id` (`friend_id`)
) ENGINE=InnoDB CHARSET=utf8mb3 comment '好友'; ) ENGINE=InnoDB CHARSET=utf8mb4 comment '好友';
create table `im_private_message`( create table `im_private_message`(
`id` bigint not null auto_increment primary key comment 'id', `id` bigint not null auto_increment primary key comment 'id',
`send_id` bigint not null comment '发送用户id', `send_id` bigint not null comment '发送用户id',
`recv_id` bigint not null comment '接收用户id', `recv_id` bigint not null comment '接收用户id',
`content` text comment '发送内容', `content` text comment '发送内容',
`type` tinyint(1) NOT NULL comment '消息类型 0:文字 1:图片 2:文件 3:语音 10:系统提示', `type` tinyint(1) NOT NULL comment '消息类型 0:文字 1:图片 2:文件 3:语音 4:视频 21:提示',
`status` tinyint(1) NOT NULL comment '状态 0:未读 1:已读 2:撤回', `status` tinyint(1) NOT NULL comment '状态 0:未读 1:已读 2:撤回 3:已读',
`send_time` datetime DEFAULT CURRENT_TIMESTAMP comment '发送时间', `send_time` datetime DEFAULT CURRENT_TIMESTAMP comment '发送时间',
key `idx_send_recv_id` (`send_id`,`recv_id`) key `idx_send_recv_id` (`send_id`,`recv_id`)
)ENGINE=InnoDB CHARSET=utf8mb3 comment '私聊消息'; )ENGINE=InnoDB CHARSET=utf8mb4 comment '私聊消息';
create table `im_group`( create table `im_group`(
@ -45,24 +46,26 @@ create table `im_group`(
`head_image` varchar(255) default '' comment '群头像', `head_image` varchar(255) default '' comment '群头像',
`head_image_thumb` varchar(255) default '' comment '群头像缩略图', `head_image_thumb` varchar(255) default '' comment '群头像缩略图',
`notice` varchar(1024) default '' comment '群公告', `notice` varchar(1024) default '' comment '群公告',
`remark` varchar(255) default '' comment '群备注', `is_banned` tinyint(1) default 0 comment '是否被封禁 0:否 1:是',
`reason` varchar(255) default '' comment '被封禁原因',
`deleted` tinyint(1) default 0 comment '是否已删除', `deleted` tinyint(1) default 0 comment '是否已删除',
`created_time` datetime default CURRENT_TIMESTAMP comment '创建时间' `created_time` datetime default CURRENT_TIMESTAMP comment '创建时间'
)ENGINE=InnoDB CHARSET=utf8mb3 comment ''; )ENGINE=InnoDB CHARSET=utf8mb4 comment '';
create table `im_group_member`( create table `im_group_member`(
`id` bigint not null auto_increment primary key comment 'id', `id` bigint not null auto_increment primary key comment 'id',
`group_id` bigint not null comment '群id', `group_id` bigint not null comment '群id',
`user_id` bigint not null comment '用户id', `user_id` bigint not null comment '用户id',
`alias_name` varchar(255) DEFAULT '' comment '组内显示名称', `user_nick_name` varchar(255) DEFAULT '' comment '用户昵称',
`remark_nick_name` varchar(255) DEFAULT '' comment '显示昵称备注',
`head_image` varchar(255) DEFAULT '' comment '用户头像', `head_image` varchar(255) DEFAULT '' comment '用户头像',
`remark` varchar(255) DEFAULT '' comment '备注', `remark_group_name` varchar(255) DEFAULT '' comment '显示群名备注',
`quit` tinyint(1) DEFAULT 0 comment '是否已退出', `quit` tinyint(1) DEFAULT 0 comment '是否已退出',
`quit_time` datetime DEFAULT NULL comment '退出时间', `quit_time` datetime DEFAULT NULL comment '退出时间',
`created_time` datetime DEFAULT CURRENT_TIMESTAMP comment '创建时间', `created_time` datetime DEFAULT CURRENT_TIMESTAMP comment '创建时间',
key `idx_group_id`(`group_id`), key `idx_group_id`(`group_id`),
key `idx_user_id`(`user_id`) key `idx_user_id`(`user_id`)
)ENGINE=InnoDB CHARSET=utf8mb3 comment '群成员'; )ENGINE=InnoDB CHARSET=utf8mb4 comment '群成员';
create table `im_group_message`( create table `im_group_message`(
`id` bigint not null auto_increment primary key comment 'id', `id` bigint not null auto_increment primary key comment 'id',
@ -74,8 +77,16 @@ create table `im_group_message`(
`at_user_ids` varchar(1024) comment '被@的用户id列表,逗号分隔', `at_user_ids` varchar(1024) comment '被@的用户id列表,逗号分隔',
`receipt` tinyint DEFAULT 0 comment '是否回执消息', `receipt` tinyint DEFAULT 0 comment '是否回执消息',
`receipt_ok` tinyint DEFAULT 0 comment '回执消息是否完成', `receipt_ok` tinyint DEFAULT 0 comment '回执消息是否完成',
`type` tinyint(1) NOT NULL comment '消息类型 0:文字 1:图片 2:文件 3:语音 4:视频 10:系统提示' , `type` tinyint(1) NOT NULL comment '消息类型 0:文字 1:图片 2:文件 3:语音 4:视频 21:提示' ,
`status` tinyint(1) DEFAULT 0 comment '状态 0:未发出 1:已送达 2:撤回 3:已读', `status` tinyint(1) DEFAULT 0 comment '状态 0:未发出 2:撤回 ',
`send_time` datetime DEFAULT CURRENT_TIMESTAMP comment '发送时间', `send_time` datetime DEFAULT CURRENT_TIMESTAMP comment '发送时间',
key `idx_group_id` (group_id) key `idx_group_id` (group_id)
)ENGINE=InnoDB CHARSET=utf8mb3 comment '群消息'; )ENGINE=InnoDB CHARSET=utf8mb4 comment '群消息';
create table `im_sensitive_word`(
`id` bigint not null auto_increment primary key comment 'id',
`content` varchar(64) not null comment '敏感词内容',
`enabled` tinyint DEFAULT 0 COMMENT '是否启用 0:未启用 1:启用',
`creator` bigint DEFAULT NULL COMMENT '创建者',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'
)ENGINE=InnoDB CHARSET=utf8mb4 comment '敏感词';

9
im-client/pom.xml

@ -5,7 +5,7 @@
<parent> <parent>
<artifactId>box-im</artifactId> <artifactId>box-im</artifactId>
<groupId>com.bx</groupId> <groupId>com.bx</groupId>
<version>2.0.0</version> <version>3.0.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
@ -15,12 +15,7 @@
<dependency> <dependency>
<groupId>com.bx</groupId> <groupId>com.bx</groupId>
<artifactId>im-commom</artifactId> <artifactId>im-commom</artifactId>
<version>2.0.0</version> <version>3.0.0</version>
</dependency>
<!-- 引入redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency> </dependency>
</dependencies> </dependencies>
</project> </project>

2
im-client/src/main/java/com/bx/imclient/IMAutoConfiguration.java

@ -7,7 +7,7 @@ import org.springframework.context.annotation.Configuration;
@Slf4j @Slf4j
@Configuration @Configuration
@ComponentScan("com.bx.imclient") @ComponentScan(basePackages = "com.bx.imclient,com.bx.imcommon")
public class IMAutoConfiguration { public class IMAutoConfiguration {
} }

13
im-client/src/main/java/com/bx/imclient/IMClient.java

@ -4,6 +4,7 @@ import com.bx.imclient.sender.IMSender;
import com.bx.imcommon.enums.IMTerminalType; import com.bx.imcommon.enums.IMTerminalType;
import com.bx.imcommon.model.IMGroupMessage; import com.bx.imcommon.model.IMGroupMessage;
import com.bx.imcommon.model.IMPrivateMessage; import com.bx.imcommon.model.IMPrivateMessage;
import com.bx.imcommon.model.IMSystemMessage;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@ -46,6 +47,16 @@ public class IMClient {
return imSender.getOnlineTerminal(userIds); return imSender.getOnlineTerminal(userIds);
} }
/**
* 发送系统消息发送结果通过MessageListener接收
*
* @param message 私有消息
*/
public<T> void sendSystemMessage(IMSystemMessage<T> message){
imSender.sendSystemMessage(message);
}
/** /**
* 发送私聊消息发送结果通过MessageListener接收 * 发送私聊消息发送结果通过MessageListener接收
* *
@ -65,4 +76,6 @@ public class IMClient {
} }
} }

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

@ -1,31 +0,0 @@
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);
}
}

87
im-client/src/main/java/com/bx/imclient/sender/IMSender.java

@ -8,33 +8,86 @@ import com.bx.imcommon.enums.IMListenerType;
import com.bx.imcommon.enums.IMSendCode; import com.bx.imcommon.enums.IMSendCode;
import com.bx.imcommon.enums.IMTerminalType; import com.bx.imcommon.enums.IMTerminalType;
import com.bx.imcommon.model.*; import com.bx.imcommon.model.*;
import com.bx.imcommon.mq.RedisMQTemplate;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.*; import java.util.*;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class IMSender { public class IMSender {
@Resource(name="IMRedisTemplate") @Autowired
private RedisTemplate<String, Object> redisTemplate; private RedisMQTemplate redisMQTemplate;
@Value("${spring.application.name}") @Value("${spring.application.name}")
private String appName; private String appName;
private final MessageListenerMulticaster listenerMulticaster; private final MessageListenerMulticaster listenerMulticaster;
public<T> void sendSystemMessage(IMSystemMessage<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 = redisMQTemplate.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 (!Objects.isNull(serverId)) {
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.SYSTEM_MESSAGE.code());
recvInfo.setReceivers(new LinkedList<>(entry.getValue()));
recvInfo.setServiceName(appName);
recvInfo.setSendResult(message.getSendResult());
recvInfo.setData(message.getData());
// 推送至队列
String key = String.join(":", IMRedisKey.IM_MESSAGE_SYSTEM_QUEUE, entry.getKey().toString());
redisMQTemplate.opsForList().rightPush(key, recvInfo);
}
// 对离线用户回复消息状态
if(message.getSendResult() && !offLineUsers.isEmpty()){
List<IMSendResult> results = new LinkedList<>();
for (IMUserInfo offLineUser : offLineUsers) {
IMSendResult result = new IMSendResult();
result.setReceiver(offLineUser);
result.setCode(IMSendCode.NOT_ONLINE.code());
result.setData(message.getData());
results.add(result);
}
listenerMulticaster.multicast(IMListenerType.SYSTEM_MESSAGE, results);
}
}
public<T> void sendPrivateMessage(IMPrivateMessage<T> message) { public<T> void sendPrivateMessage(IMPrivateMessage<T> message) {
List<IMSendResult> results = new LinkedList<>(); List<IMSendResult> results = new LinkedList<>();
if(!Objects.isNull(message.getRecvId())){ if(!Objects.isNull(message.getRecvId())){
for (Integer terminal : message.getRecvTerminals()) { for (Integer terminal : message.getRecvTerminals()) {
// 获取对方连接的channelId // 获取对方连接的channelId
String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID, message.getRecvId().toString(), terminal.toString()); String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID, message.getRecvId().toString(), terminal.toString());
Integer serverId = (Integer)redisTemplate.opsForValue().get(key); Integer serverId = (Integer)redisMQTemplate.opsForValue().get(key);
// 如果对方在线,将数据存储至redis,等待拉取推送 // 如果对方在线,将数据存储至redis,等待拉取推送
if (serverId != null) { if (serverId != null) {
String sendKey = String.join(":", IMRedisKey.IM_MESSAGE_PRIVATE_QUEUE, serverId.toString()); String sendKey = String.join(":", IMRedisKey.IM_MESSAGE_PRIVATE_QUEUE, serverId.toString());
@ -45,7 +98,7 @@ public class IMSender {
recvInfo.setSender(message.getSender()); recvInfo.setSender(message.getSender());
recvInfo.setReceivers(Collections.singletonList(new IMUserInfo(message.getRecvId(), terminal))); recvInfo.setReceivers(Collections.singletonList(new IMUserInfo(message.getRecvId(), terminal)));
recvInfo.setData(message.getData()); recvInfo.setData(message.getData());
redisTemplate.opsForList().rightPush(sendKey, recvInfo); redisMQTemplate.opsForList().rightPush(sendKey, recvInfo);
} else { } else {
IMSendResult result = new IMSendResult(); IMSendResult result = new IMSendResult();
result.setSender(message.getSender()); result.setSender(message.getSender());
@ -65,7 +118,7 @@ public class IMSender {
} }
// 获取终端连接的channelId // 获取终端连接的channelId
String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID, message.getSender().getId().toString(), terminal.toString()); String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID, message.getSender().getId().toString(), terminal.toString());
Integer serverId = (Integer)redisTemplate.opsForValue().get(key); Integer serverId = (Integer)redisMQTemplate.opsForValue().get(key);
// 如果终端在线,将数据存储至redis,等待拉取推送 // 如果终端在线,将数据存储至redis,等待拉取推送
if (serverId != null) { if (serverId != null) {
String sendKey = String.join(":", IMRedisKey.IM_MESSAGE_PRIVATE_QUEUE, serverId.toString()); String sendKey = String.join(":", IMRedisKey.IM_MESSAGE_PRIVATE_QUEUE, serverId.toString());
@ -76,7 +129,7 @@ public class IMSender {
recvInfo.setSender(message.getSender()); recvInfo.setSender(message.getSender());
recvInfo.setReceivers(Collections.singletonList(new IMUserInfo(message.getSender().getId(), terminal))); recvInfo.setReceivers(Collections.singletonList(new IMUserInfo(message.getSender().getId(), terminal)));
recvInfo.setData(message.getData()); recvInfo.setData(message.getData());
redisTemplate.opsForList().rightPush(sendKey, recvInfo); redisMQTemplate.opsForList().rightPush(sendKey, recvInfo);
} }
} }
} }
@ -84,10 +137,10 @@ public class IMSender {
if(message.getSendResult() && !results.isEmpty()){ if(message.getSendResult() && !results.isEmpty()){
listenerMulticaster.multicast(IMListenerType.PRIVATE_MESSAGE, results); listenerMulticaster.multicast(IMListenerType.PRIVATE_MESSAGE, results);
} }
} }
public<T> void sendGroupMessage(IMGroupMessage<T> message) { public<T> void sendGroupMessage(IMGroupMessage<T> message) {
// 根据群聊每个成员所连的IM-server,进行分组 // 根据群聊每个成员所连的IM-server,进行分组
Map<String, IMUserInfo> sendMap = new HashMap<>(); Map<String, IMUserInfo> sendMap = new HashMap<>();
for (Integer terminal : message.getRecvTerminals()) { for (Integer terminal : message.getRecvTerminals()) {
@ -97,14 +150,14 @@ public class IMSender {
}); });
} }
// 批量拉取 // 批量拉取
List<Object> serverIds = redisTemplate.opsForValue().multiGet(sendMap.keySet()); List<Object> serverIds = redisMQTemplate.opsForValue().multiGet(sendMap.keySet());
// 格式:map<服务器id,list<接收方>> // 格式:map<服务器id,list<接收方>>
Map<Integer, List<IMUserInfo>> serverMap = new HashMap<>(); Map<Integer, List<IMUserInfo>> serverMap = new HashMap<>();
List<IMUserInfo> offLineUsers = new LinkedList<>(); List<IMUserInfo> offLineUsers = new LinkedList<>();
int idx = 0; int idx = 0;
for (Map.Entry<String,IMUserInfo> entry : sendMap.entrySet()) { for (Map.Entry<String,IMUserInfo> entry : sendMap.entrySet()) {
Integer serverId = (Integer)serverIds.get(idx++); Integer serverId = (Integer)serverIds.get(idx++);
if (serverId != null) { if (!Objects.isNull(serverId)) {
List<IMUserInfo> list = serverMap.computeIfAbsent(serverId, o -> new LinkedList<>()); List<IMUserInfo> list = serverMap.computeIfAbsent(serverId, o -> new LinkedList<>());
list.add(entry.getValue()); list.add(entry.getValue());
} else { } else {
@ -123,7 +176,7 @@ public class IMSender {
recvInfo.setData(message.getData()); recvInfo.setData(message.getData());
// 推送至队列 // 推送至队列
String key = String.join(":", IMRedisKey.IM_MESSAGE_GROUP_QUEUE, entry.getKey().toString()); String key = String.join(":", IMRedisKey.IM_MESSAGE_GROUP_QUEUE, entry.getKey().toString());
redisTemplate.opsForList().rightPush(key, recvInfo); redisMQTemplate.opsForList().rightPush(key, recvInfo);
} }
// 推送给自己的其他终端 // 推送给自己的其他终端
@ -134,9 +187,9 @@ public class IMSender {
} }
// 获取终端连接的channelId // 获取终端连接的channelId
String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID, message.getSender().getId().toString(), terminal.toString()); String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID, message.getSender().getId().toString(), terminal.toString());
Integer serverId = (Integer)redisTemplate.opsForValue().get(key); Integer serverId = (Integer)redisMQTemplate.opsForValue().get(key);
// 如果终端在线,将数据存储至redis,等待拉取推送 // 如果终端在线,将数据存储至redis,等待拉取推送
if (serverId != null) { if (!Objects.isNull(serverId)) {
IMRecvInfo recvInfo = new IMRecvInfo(); IMRecvInfo recvInfo = new IMRecvInfo();
recvInfo.setCmd(IMCmdType.GROUP_MESSAGE.code()); recvInfo.setCmd(IMCmdType.GROUP_MESSAGE.code());
recvInfo.setSender(message.getSender()); recvInfo.setSender(message.getSender());
@ -145,7 +198,7 @@ public class IMSender {
recvInfo.setSendResult(false); recvInfo.setSendResult(false);
recvInfo.setData(message.getData()); recvInfo.setData(message.getData());
String sendKey = String.join(":", IMRedisKey.IM_MESSAGE_GROUP_QUEUE, serverId.toString()); String sendKey = String.join(":", IMRedisKey.IM_MESSAGE_GROUP_QUEUE, serverId.toString());
redisTemplate.opsForList().rightPush(sendKey, recvInfo); redisMQTemplate.opsForList().rightPush(sendKey, recvInfo);
} }
} }
} }
@ -178,7 +231,7 @@ public class IMSender {
} }
} }
// 批量拉取 // 批量拉取
List<Object> serverIds = redisTemplate.opsForValue().multiGet(userMap.keySet()); List<Object> serverIds = redisMQTemplate.opsForValue().multiGet(userMap.keySet());
int idx = 0; int idx = 0;
Map<Long,List<IMTerminalType>> onlineMap = new HashMap<>(); Map<Long,List<IMTerminalType>> onlineMap = new HashMap<>();
for (Map.Entry<String,IMUserInfo> entry : userMap.entrySet()) { for (Map.Entry<String,IMUserInfo> entry : userMap.entrySet()) {
@ -195,7 +248,7 @@ public class IMSender {
public Boolean isOnline(Long userId) { public Boolean isOnline(Long userId) {
String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID, userId.toString(), "*"); String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID, userId.toString(), "*");
return !Objects.requireNonNull(redisTemplate.keys(key)).isEmpty(); return !Objects.requireNonNull(redisMQTemplate.keys(key)).isEmpty();
} }
public List<Long> getOnlineUser(List<Long> userIds){ public List<Long> getOnlineUser(List<Long> userIds){

39
im-client/src/main/java/com/bx/imclient/task/AbstractMessageResultTask.java

@ -1,44 +1,21 @@
package com.bx.imclient.task; package com.bx.imclient.task;
import com.bx.imcommon.util.ThreadPoolExecutorFactory; import cn.hutool.core.util.StrUtil;
import lombok.SneakyThrows; import com.bx.imcommon.mq.RedisMQConsumer;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner; import org.springframework.beans.factory.annotation.Value;
import javax.annotation.PreDestroy;
import java.util.concurrent.ExecutorService;
@Slf4j @Slf4j
public abstract class AbstractMessageResultTask implements CommandLineRunner { public abstract class AbstractMessageResultTask<T> extends RedisMQConsumer<T> {
private static final ExecutorService EXECUTOR_SERVICE = ThreadPoolExecutorFactory.getThreadPoolExecutor(); @Value("${spring.application.name}")
private String appName;
@Override @Override
public void run(String... args) { public String generateKey() {
// 初始化定时器 return StrUtil.join(":", super.generateKey(), appName);
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;
} }

42
im-client/src/main/java/com/bx/imclient/task/GroupMessageResultResultTask.java

@ -1,59 +1,27 @@
package com.bx.imclient.task; 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.imclient.listener.MessageListenerMulticaster;
import com.bx.imcommon.contant.IMRedisKey; import com.bx.imcommon.contant.IMRedisKey;
import com.bx.imcommon.enums.IMListenerType; import com.bx.imcommon.enums.IMListenerType;
import com.bx.imcommon.model.IMSendResult; import com.bx.imcommon.model.IMSendResult;
import com.bx.imcommon.mq.RedisMQListener;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Objects;
@Component @Component
@RequiredArgsConstructor @RequiredArgsConstructor
public class GroupMessageResultResultTask extends AbstractMessageResultTask { @RedisMQListener(queue = IMRedisKey.IM_RESULT_GROUP_QUEUE, batchSize = 100)
public class GroupMessageResultResultTask extends AbstractMessageResultTask<IMSendResult> {
@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; private final MessageListenerMulticaster listenerMulticaster;
@Override @Override
public void pullMessage() { public void onMessage(List<IMSendResult> results) {
List<IMSendResult> results; listenerMulticaster.multicast(IMListenerType.GROUP_MESSAGE, 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;
}
} }

46
im-client/src/main/java/com/bx/imclient/task/PrivateMessageResultResultTask.java

@ -1,60 +1,26 @@
package com.bx.imclient.task; 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.imclient.listener.MessageListenerMulticaster;
import com.bx.imcommon.contant.IMRedisKey; import com.bx.imcommon.contant.IMRedisKey;
import com.bx.imcommon.enums.IMListenerType; import com.bx.imcommon.enums.IMListenerType;
import com.bx.imcommon.model.IMSendResult; import com.bx.imcommon.model.IMSendResult;
import com.bx.imcommon.mq.RedisMQListener;
import lombok.RequiredArgsConstructor; 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 org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Objects;
@Slf4j
@Component @Component
@RequiredArgsConstructor @RequiredArgsConstructor
public class PrivateMessageResultResultTask extends AbstractMessageResultTask { @RedisMQListener(queue = IMRedisKey.IM_RESULT_PRIVATE_QUEUE, batchSize = 100)
public class PrivateMessageResultResultTask extends AbstractMessageResultTask<IMSendResult> {
@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; private final MessageListenerMulticaster listenerMulticaster;
@Override @Override
public void pullMessage() { public void onMessage(List<IMSendResult> results) {
List<IMSendResult> results; listenerMulticaster.multicast(IMListenerType.PRIVATE_MESSAGE, 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;
} }
} }

25
im-client/src/main/java/com/bx/imclient/task/SystemMessageResultResultTask.java

@ -0,0 +1,25 @@
package com.bx.imclient.task;
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 com.bx.imcommon.mq.RedisMQListener;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
@RequiredArgsConstructor
@RedisMQListener(queue = IMRedisKey.IM_RESULT_SYSTEM_QUEUE, batchSize = 100)
public class SystemMessageResultResultTask extends AbstractMessageResultTask<IMSendResult> {
private final MessageListenerMulticaster listenerMulticaster;
@Override
public void onMessage(List<IMSendResult> results) {
listenerMulticaster.multicast(IMListenerType.SYSTEM_MESSAGE, results);
}
}

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

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

1
im-client/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

@ -0,0 +1 @@
com.bx.imclient.IMAutoConfiguration

21
im-commom/pom.xml

@ -5,16 +5,11 @@
<parent> <parent>
<artifactId>box-im</artifactId> <artifactId>box-im</artifactId>
<groupId>com.bx</groupId> <groupId>com.bx</groupId>
<version>2.0.0</version> <version>3.0.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>im-commom</artifactId> <artifactId>im-commom</artifactId>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
@ -39,15 +34,9 @@
<groupId>org.springframework</groupId> <groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId> <artifactId>spring-beans</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity</artifactId>
<version>${velocity.version}</version>
</dependency>
<dependency> <dependency>
<groupId>com.fasterxml.jackson.datatype</groupId> <groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-joda</artifactId> <artifactId>jackson-datatype-joda</artifactId>
<version>2.9.10</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>org.springframework</groupId>
@ -63,7 +52,11 @@
<dependency> <dependency>
<groupId>org.slf4j</groupId> <groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId> <artifactId>slf4j-api</artifactId>
<version>1.7.36</version> </dependency>
<!-- 引入redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency> </dependency>
</dependencies> </dependencies>
</project> </project>

14
im-commom/src/main/java/com/bx/imcommon/contant/IMRedisKey.java

@ -2,7 +2,6 @@ package com.bx.imcommon.contant;
public final class IMRedisKey { public final class IMRedisKey {
private IMRedisKey() {}
/** /**
* im-server最大id,从0开始递增 * im-server最大id,从0开始递增
@ -13,13 +12,22 @@ public final class IMRedisKey {
*/ */
public static final String IM_USER_SERVER_ID = "im:user:server_id"; public static final String IM_USER_SERVER_ID = "im:user:server_id";
/** /**
* 未读私聊消息队列 * 系统消息队列
*/
public static final String IM_MESSAGE_SYSTEM_QUEUE = "im:message:system";
/**
* 私聊消息队列
*/ */
public static final String IM_MESSAGE_PRIVATE_QUEUE = "im:message:private"; 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_MESSAGE_GROUP_QUEUE = "im:message:group";
/**
* 系统消息发送结果队列
*/
public static final String IM_RESULT_SYSTEM_QUEUE = "im:result:system";
/** /**
* 私聊消息发送结果队列 * 私聊消息发送结果队列
*/ */

6
im-commom/src/main/java/com/bx/imcommon/enums/IMCmdType.java

@ -24,7 +24,11 @@ public enum IMCmdType {
/** /**
* 群发消息 * 群发消息
*/ */
GROUP_MESSAGE(4, "群发消息"); GROUP_MESSAGE(4, "群发消息"),
/**
* 系统消息
*/
SYSTEM_MESSAGE(5,"系统消息");
private final Integer code; private final Integer code;

8
im-commom/src/main/java/com/bx/imcommon/enums/IMListenerType.java

@ -15,7 +15,13 @@ public enum IMListenerType {
/** /**
* 群聊消息 * 群聊消息
*/ */
GROUP_MESSAGE(2, "群聊消息"); GROUP_MESSAGE(2, "群聊消息"),
/**
* 系统消息
*/
SYSTEM_MESSAGE(3, "群聊消息");
private final Integer code; private final Integer code;

2
im-commom/src/main/java/com/bx/imcommon/model/IMPrivateMessage.java

@ -26,7 +26,7 @@ public class IMPrivateMessage<T> {
private List<Integer> recvTerminals = IMTerminalType.codes(); private List<Integer> recvTerminals = IMTerminalType.codes();
/** /**
* 是否发送给自己的其他终端,默认true * 是否同步消息给自己的其他终端,默认true
*/ */
private Boolean sendToSelf = true; private Boolean sendToSelf = true;

2
im-commom/src/main/java/com/bx/imcommon/model/IMSendResult.java

@ -16,7 +16,7 @@ public class IMSendResult<T> {
private IMUserInfo receiver; private IMUserInfo receiver;
/** /**
* 发送状态 IMCmdType * 发送状态编码 IMSendCode
*/ */
private Integer code; private Integer code;

34
im-commom/src/main/java/com/bx/imcommon/model/IMSystemMessage.java

@ -0,0 +1,34 @@
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 IMSystemMessage<T> {
/**
* 接收者id列表为空表示向所有在线用户广播
*/
private List<Long> recvIds = new LinkedList<>();
/**
* 接收者终端类型,默认全部
*/
private List<Integer> recvTerminals = IMTerminalType.codes();
/**
* 是否需要回推发送结果,默认true
*/
private Boolean sendResult = true;
/**
* 消息内容
*/
private T data;
}

32
im-commom/src/main/java/com/bx/imcommon/mq/RedisMQConfig.java

@ -0,0 +1,32 @@
package com.bx.imcommon.mq;
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.serializer.StringRedisSerializer;
@Configuration
public class RedisMQConfig {
@Bean
public RedisMQTemplate redisMQTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisMQTemplate redisMQTemplate = new RedisMQTemplate();
redisMQTemplate.setConnectionFactory(redisConnectionFactory);
// 设置值(value)的序列化采用FastJsonRedisSerializer
redisMQTemplate.setValueSerializer(fastJsonRedisSerializer());
redisMQTemplate.setHashValueSerializer(fastJsonRedisSerializer());
// 设置键(key)的序列化采用StringRedisSerializer。
redisMQTemplate.setKeySerializer(new StringRedisSerializer());
redisMQTemplate.setHashKeySerializer(new StringRedisSerializer());
redisMQTemplate.afterPropertiesSet();
return redisMQTemplate;
}
@Bean
public FastJsonRedisSerializer fastJsonRedisSerializer(){
return new FastJsonRedisSerializer<>(Object.class);
}
}

35
im-commom/src/main/java/com/bx/imcommon/mq/RedisMQConsumer.java

@ -0,0 +1,35 @@
package com.bx.imcommon.mq;
import java.util.List;
/**
* redis 队列消费者抽象类
*/
public abstract class RedisMQConsumer<T> {
/**
* 消费消息回调(单条)
*/
public void onMessage(T data){}
/**
* 消费消息回调(批量)
*/
public void onMessage(List<T> datas){}
/**
* 生成redis队列完整key
*/
public String generateKey(){
// 默认队列名就是redis的key
RedisMQListener annotation = this.getClass().getAnnotation(RedisMQListener.class);
return annotation.queue();
}
/**
* 队列是否就绪返回true才会开始消费
*/
public Boolean isReady(){
return true;
}
}

29
im-commom/src/main/java/com/bx/imcommon/mq/RedisMQListener.java

@ -0,0 +1,29 @@
package com.bx.imcommon.mq;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* redis 队列消费监听注解
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface RedisMQListener {
/**
* 队列也是redis的key
*/
String queue();
/**
* 一次性拉取的数据数量
*/
int batchSize() default 1;
/**
* 拉取间隔周期单位:ms
*/
int period() default 100;
}

114
im-commom/src/main/java/com/bx/imcommon/mq/RedisMQPullTask.java

@ -0,0 +1,114 @@
package com.bx.imcommon.mq;
import com.alibaba.fastjson.JSONObject;
import com.bx.imcommon.util.ThreadPoolExecutorFactory;
import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* reids 队列拉取定时任务
*
* @author: Blue
* @date: 2024-07-15
* @version: 1.0
*/
@Slf4j
@Component
public class RedisMQPullTask implements CommandLineRunner {
private static final ScheduledThreadPoolExecutor EXECUTOR = ThreadPoolExecutorFactory.getThreadPoolExecutor();
@Autowired(required = false)
private List<RedisMQConsumer> consumers = Collections.emptyList();
@Autowired
private RedisMQTemplate redisMQTemplate;
@Override
public void run(String... args) {
consumers.forEach((consumer -> {
// 注解参数
RedisMQListener annotation = consumer.getClass().getAnnotation(RedisMQListener.class);
String queue = annotation.queue();
int batchSize = annotation.batchSize();
int period = annotation.period();
// 获取泛型类型
Type superClass = consumer.getClass().getGenericSuperclass();
Type type = ((ParameterizedType)superClass).getActualTypeArguments()[0];
EXECUTOR.execute(new Runnable() {
@Override
public void run() {
List<Object> datas = new LinkedList<>();
try {
if(redisMQTemplate.isClose()){
return;
}
if (consumer.isReady()) {
String key = consumer.generateKey();
// 拉取一个批次的数据
List<Object> objects = pullBatch(key, batchSize);
for (Object obj : objects) {
if (obj instanceof JSONObject) {
JSONObject jsonObject = (JSONObject)obj;
Object data = jsonObject.toJavaObject(type);
consumer.onMessage(data);
datas.add(data);
}
}
if (!datas.isEmpty()) {
consumer.onMessage(datas);
}
}
} catch (Exception e) {
log.error("数据消费异常,队列:{}", queue, e);
return;
}
// 继续消费数据
if (!EXECUTOR.isShutdown()) {
if (datas.size() < batchSize) {
// 数据已经消费完,等待下一个周期继续拉取
EXECUTOR.schedule(this, period, TimeUnit.MICROSECONDS);
} else {
// 数据没有消费完,直接开启下一个消费周期
EXECUTOR.execute(this);
}
}
}
});
}));
}
private List<Object> pullBatch(String key, Integer batchSize) {
List<Object> objects = new LinkedList<>();
if (redisMQTemplate.isSupportBatchPull()) {
// 版本大于6.2,支持批量拉取
objects = redisMQTemplate.opsForList().leftPop(key, batchSize);
} else {
// 版本小于6.2,只能逐条拉取
Object obj = redisMQTemplate.opsForList().leftPop(key);
while (!Objects.isNull(obj) && objects.size() < batchSize) {
objects.add(obj);
obj = redisMQTemplate.opsForList().leftPop(key);
}
}
return objects;
}
@PreDestroy
public void destory() {
log.info("消费线程停止...");
ThreadPoolExecutorFactory.shutDown();
}
}

52
im-commom/src/main/java/com/bx/imcommon/mq/RedisMQTemplate.java

@ -0,0 +1,52 @@
package com.bx.imcommon.mq;
import org.apache.logging.log4j.util.Strings;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import java.util.Objects;
import java.util.Properties;
/**
* @author: Blue
* @date: 2024-07-16
* @version: 1.0
*/
public class RedisMQTemplate extends RedisTemplate<String, Object> {
private String version = Strings.EMPTY;
public String getVersion() {
if (version.isEmpty()) {
RedisConnection redisConnection = this.getConnectionFactory().getConnection();
Properties properties = redisConnection.info();
version = properties.getProperty("redis_version");
}
return version;
}
/**
* 是否支持批量拉取redis版本大于6.2支持批量拉取
* @return
*/
Boolean isSupportBatchPull() {
String version = getVersion();
String[] arr = version.split("\\.");
if (arr.length < 2) {
return false;
}
Integer firVersion = Integer.valueOf(arr[0]);
Integer secVersion = Integer.valueOf(arr[1]);
return firVersion > 6 || (firVersion == 6 && secVersion >= 2);
}
Boolean isClose(){
try {
return getConnectionFactory().getConnection().isClosed();
}catch (Exception e){
return true;
}
}
}

37
im-commom/src/main/java/com/bx/imcommon/util/ThreadPoolExecutorFactory.java

@ -2,9 +2,8 @@ package com.bx.imcommon.util;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/** /**
* 创建单例线程池 * 创建单例线程池
@ -38,7 +37,7 @@ public final class ThreadPoolExecutorFactory {
/** /**
* 线程池对象 * 线程池对象
*/ */
private static volatile ThreadPoolExecutor threadPoolExecutor = null; private static volatile ScheduledThreadPoolExecutor threadPoolExecutor = null;
/** /**
* 构造方法私有化 * 构造方法私有化
@ -49,34 +48,20 @@ public final class ThreadPoolExecutorFactory {
} }
} }
/**
* 重写readResolve方法
*/
private Object readResolve() {
//重写readResolve方法,防止序列化破坏单例
return ThreadPoolExecutorFactory.getThreadPoolExecutor();
}
/** /**
* 双检锁创建线程安全的单例 * 双检锁创建线程安全的单例
*/ */
public static ThreadPoolExecutor getThreadPoolExecutor() { public static ScheduledThreadPoolExecutor getThreadPoolExecutor() {
if (null == threadPoolExecutor) { if (null == threadPoolExecutor) {
synchronized (ThreadPoolExecutorFactory.class) { synchronized (ThreadPoolExecutorFactory.class) {
if (null == threadPoolExecutor) { if (null == threadPoolExecutor) {
threadPoolExecutor = new ThreadPoolExecutor( threadPoolExecutor = new ScheduledThreadPoolExecutor(
//核心线程数 //核心线程数
CORE_POOL_SIZE, CORE_POOL_SIZE,
//最大线程数,包含临时线程 //拒绝策略
MAX_IMUM_POOL_SIZE, new ThreadPoolExecutor.CallerRunsPolicy()
//临时线程的存活时间 );
KEEP_ALIVE_TIME,
//时间单位(毫秒)
TimeUnit.MILLISECONDS,
//等待队列
new LinkedBlockingQueue<>(QUEUE_SIZE),
//拒绝策略
new ThreadPoolExecutor.CallerRunsPolicy());
} }
} }
} }
@ -86,13 +71,13 @@ public final class ThreadPoolExecutorFactory {
/** /**
* 关闭线程池 * 关闭线程池
*/ */
public void shutDown() { public static void shutDown() {
if (threadPoolExecutor != null) { if (threadPoolExecutor != null) {
threadPoolExecutor.shutdown(); threadPoolExecutor.shutdown();
} }
} }
public void execute(Runnable runnable) { public static void execute(Runnable runnable) {
if (runnable == null) { if (runnable == null) {
return; return;
} }

26
im-docker-compose/README.md

@ -1,26 +0,0 @@
# docker 构建和部署指南
## 配置
请配置以下文件的挂载目录、端口号、环境变量、账户信息等。
- box-im/im-docker-compose/im-coturn/docker-compose.yml
- box-im/im-docker-compose/im-service/docker-compose.yml
- box-im/im-docker-compose/im-coturn/im-platform.env
- box-im/im-docker-compose/im-coturn/im-server.env
## 构建
可以在部署的目标服务器上在下面两个目录下构建出 im-server 和 im-platform 的 docker images.
- box-im/im-server/Dockerfile
- box-im/im-platform/Dockerfile
## 部署
在部署的目标服务器上通过 docker-compose 启动所有容器,
```bash
docker-compose -f im-docker-compose/im-coturn/docker-compose.yml up -d
docker-compose -f im-docker-compose/im-service/docker-compose.yml up -d
```

27
im-docker-compose/im-coturn/docker-compose.yml

@ -1,27 +0,0 @@
version: "3"
services:
im-coturn:
container_name: im-coturn
image: coturn/coturn:4.6.2-alpine
command: turnserver -v -a -c /etc/coturn/conf/coturn.conf
restart: always
ports:
- PORT_COTURN_LISTEN_OS:3478
- PORT_COTURN_LISTEN_OS:3478/udp
- PORT_COTURN_LISTEN_TLS_OS:5349
- PORT_COTURN_LISTEN_TLS_OS:5349/udp
- PORT_COTURN_RELAY_BEGIN_OS-PORT_COTURN_RELAY_END_OS:PORT_COTURN_RELAY_BEGIN_OS-PORT_COTURN_RELAY_END_OS/udp
# network_mode: "host"
environment:
- DETECT_EXTERNAL_IP=yes
- DETECT_RELAY_IP=yes
volumes:
- deploydir/docker_volumes_data/coturn/conf:/etc/coturn/conf
- deploydir/docker_volumes_data/coturn/certs:/etc/coturn/ssl
- deploydir/docker_volumes_data/coturn/log:/var/log/coturn/
networks:
default:
external:
name: turn-network

99
im-docker-compose/im-service/docker-compose.yml

@ -1,99 +0,0 @@
version: "3"
services:
im-mysql:
container_name: im-mysql
image: mysql:5.7
command: --default-authentication-plugin=mysql_native_password
restart: always
environment:
- TZ=Asia/Shanghai
- MYSQL_ROOT_PASSWORD=MYSQL_ROOT_USER_PASSWORD
- MYSQL_ROOT_HOST=%
- MYSQL_IM_USERNAME=MYSQL_USERNAME
- MYSQL_IM_PASSWORD=MYSQL_PASSWORD
volumes:
- deploydir/docker_volumes_data/mysql/data:/var/lib/mysql
- deploydir/docker_volumes_data/mysql/mysql.conf.d:/etc/mysql/mysql.conf.d
im-redis:
container_name: im-redis
image: redis:6-alpine
command: redis-server --requirepass REDIS_PASSWORD --appendonly yes --appendfsync everysec --auto-aof-rewrite-percentage 100 --auto-aof-rewrite-min-size 100mb
restart: always
# ports:
# - "127.0.0.1:6379:6379"
volumes:
- deploydir/docker_volumes_data/redis/data:/data
im-minio:
container_name: im-minio
image: bitnami/minio:2024
restart: always
# ports:
# - 9001:9001
# - 9002:9002
privileged: true
environment:
- MINIO_SKIP_CLIENT=yes
- MINIO_API_PORT_NUMBER=9001
- MINIO_CONSOLE_PORT_NUMBER=9002
# - MINIO_OPTS="--console-address :9002 --address :9001"
# - MINIO_DOMAIN=im_minio:9001
- MINIO_DATA_DIR=/data/minio
- MINIO_ROOT_USER=MINIO_USERNAME
- MINIO_ROOT_PASSWORD=MINIO_PASSWORD
volumes:
- deploydir/docker_volumes_data/minio/data:/data/minio
im-nginx:
container_name: im-nginx
image: openresty/openresty:1.21.4.1-0-alpine
restart: always
ports:
- PORT_NGINX_HTTP_OS:80
- PORT_NGINX_HTTPS_OS:443
- PORT_NGINX_WSS_OS:81
depends_on:
im-platform:
condition: service_started
im-server:
condition: service_started
volumes:
- deploydir/docker_volumes_data/nginx/data/conf:/etc/nginx ## configs
- deploydir/docker_volumes_data/nginx/data/certs:/etc/certs ## cert files
- deploydir/docker_volumes_data/nginx/data/web:/usr/share/nginx ## web
im-platform:
container_name: im-platform
image: im-platform:latest
restart: always
depends_on:
im-mysql:
condition: service_started
im-redis:
condition: service_started
im-minio:
condition: service_started
env_file:
- im-platform.env
im-server:
container_name: im-server
image: im-server:latest
restart: always
depends_on:
im-mysql:
condition: service_started
im-redis:
condition: service_started
im-minio:
condition: service_started
env_file:
- im-server.env
networks:
default:
external:
name: im-network

28
im-docker-compose/im-service/im-platform.env

@ -1,28 +0,0 @@
spring_datasource_username=MYSQL_USERNAME
spring_datasource_password=MYSQL_PASSWORD
spring_redis_password=REDIS_PASSWORD
spring_datasource_url=jdbc:mysql://im-mysql:3306/box-im?useSSL=false&useUnicode=true&characterEncoding=utf-8
spring_datasource_password=MYSQL_PASSWORD
spring_redis_host=im-redis
spring_redis_port=6379
spring_redis_password=REDIS_PASSWORD
minio_accessKey=MINIO_USERNAME
minio_secretKey=MINIO_PASSWORD
minio_public=https://IM_DOMAIN:PORT_NGINX_HTTPS_EXTERNAL/file
minio_endpoint=http://im-minio:9001
ICE_SERVER_1_URL=TURN_DOMAIN:PORT_COTURN_LISTEN_EXTERNAL
ICE_SERVER_1_USERNAME=TURN_USERNAME
ICE_SERVER_1_CREDENTIAL=TURN_PASSWORD
ICE_SERVER_2_URL=TURN_DOMAIN:PORT_COTURN_LISTEN_EXTERNAL
ICE_SERVER_2_USERNAME=TURN_USERNAME
ICE_SERVER_2_CREDENTIAL=TURN_PASSWORD
jwt_accessToken_secret=JWT_ACCESSTOKEN_SECRET
jwt_accessToken_expireIn=JWT_ACCESSTOKEN_EXPIREDIN
jwt_refreshToken_secret=JWT_REFRESHTOKEN_SECRET
jwt_refreshToken_expireIn=JWT_REFRESHTOKEN_EXPIREDIN

4
im-docker-compose/im-service/im-server.env

@ -1,4 +0,0 @@
spring_redis_host=im-redis
spring_redis_port=6379
spring_redis_password=REDIS_PASSWORD
jwt_accessToken_secret=JWT_ACCESSTOKEN_SECRET

78
im-platform/pom.xml

@ -5,22 +5,16 @@
<parent> <parent>
<artifactId>box-im</artifactId> <artifactId>box-im</artifactId>
<groupId>com.bx</groupId> <groupId>com.bx</groupId>
<version>2.0.0</version> <version>3.0.0</version>
</parent> </parent>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<artifactId>im-platform</artifactId> <artifactId>im-platform</artifactId>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>com.bx</groupId> <groupId>com.bx</groupId>
<artifactId>im-client</artifactId> <artifactId>im-client</artifactId>
<version>2.0.0</version> <version>3.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
@ -29,41 +23,38 @@
<dependency> <dependency>
<groupId>com.baomidou</groupId> <groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId> <artifactId>mybatis-plus-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>${mybatis.spring.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.alibaba</groupId> <groupId>com.alibaba</groupId>
<artifactId>druid</artifactId> <artifactId>druid</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>mysql</groupId> <groupId>com.mysql</groupId>
<artifactId>mysql-connector-java</artifactId> <artifactId>mysql-connector-j</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId> <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency> </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> <dependency>
<groupId>org.aspectj</groupId> <groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId> <artifactId>aspectjweaver</artifactId>
</dependency> </dependency>
<!-- 引入redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.security</groupId>
<artifactId>spring-boot-starter-security</artifactId> <artifactId>spring-security-crypto</artifactId>
<version>6.3.1</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.session</groupId> <groupId>org.springframework.session</groupId>
@ -77,27 +68,7 @@
<dependency> <dependency>
<groupId>io.minio</groupId> <groupId>io.minio</groupId>
<artifactId>minio</artifactId> <artifactId>minio</artifactId>
<version>8.4.3</version> <version>${minio.version}</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> </dependency>
<!--thumbnailator图片处理--> <!--thumbnailator图片处理-->
<dependency> <dependency>
@ -112,18 +83,21 @@
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.redisson</groupId> <groupId>org.redisson</groupId>
<artifactId>redisson</artifactId> <artifactId>redisson-spring-boot-starter</artifactId>
<version>3.17.3</version> <version>${redisson.version}</version>
</dependency>
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
<version>${knife4j.version}</version>
</dependency> </dependency>
</dependencies> </dependencies>
<build> <build>
<finalName>${project.artifactId}</finalName> <finalName>${project.artifactId}</finalName>
<plugins> <plugins>
<plugin> <plugin>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId> <artifactId>spring-boot-maven-plugin</artifactId>
<version>2.0.3.RELEASE</version>
<executions> <executions>
<execution> <execution>
<goals> <goals>

15
im-platform/src/main/java/com/bx/implatform/IMPlatformApp.java

@ -1,27 +1,18 @@
package com.bx.implatform; package com.bx.implatform;
import cn.hutool.core.util.StrUtil;
import com.bx.implatform.contant.RedisKey;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.mybatis.spring.annotation.MapperScan; 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.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy; 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 @Slf4j
@EnableAspectJAutoProxy(exposeProxy = true) @EnableAspectJAutoProxy(exposeProxy = true)
@MapperScan(basePackages = {"com.bx.implatform.mapper"}) @MapperScan(basePackages = {"com.bx.implatform.mapper"})
@SpringBootApplication(exclude = {SecurityAutoConfiguration.class})// 禁用secrity @SpringBootApplication
public class IMPlatformApp { public class IMPlatformApp {
public static void main(String[] args) { public static void main(String[] args) {

1
im-platform/src/main/java/com/bx/implatform/annotation/OnlineCheck.java

@ -4,7 +4,6 @@ import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;
/** /**
* 在线校验标注此注解的接口用户必须保持长连接否则将抛异常 * 在线校验标注此注解的接口用户必须保持长连接否则将抛异常

7
im-platform/src/main/java/com/bx/implatform/aspect/OnlineCheckAspect.java

@ -1,8 +1,6 @@
package com.bx.implatform.aspect; package com.bx.implatform.aspect;
import cn.hutool.core.util.StrUtil;
import com.bx.imclient.IMClient; import com.bx.imclient.IMClient;
import com.bx.implatform.annotation.RedisLock;
import com.bx.implatform.exception.GlobalException; import com.bx.implatform.exception.GlobalException;
import com.bx.implatform.session.SessionContext; import com.bx.implatform.session.SessionContext;
import com.bx.implatform.session.UserSession; import com.bx.implatform.session.UserSession;
@ -11,13 +9,8 @@ import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/** /**
* @author: blue * @author: blue
* @date: 2024-06-16 * @date: 2024-06-16

2
im-platform/src/main/java/com/bx/implatform/aspect/RedisLockAspect.java

@ -8,8 +8,6 @@ import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature; import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.Redisson;
import org.redisson.RedissonLock;
import org.redisson.api.RLock; import org.redisson.api.RLock;
import org.redisson.api.RedissonClient; import org.redisson.api.RedissonClient;
import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.core.DefaultParameterNameDiscoverer;

11
im-platform/src/main/java/com/bx/implatform/config/MvcConfig.java

@ -19,13 +19,10 @@ public class MvcConfig implements WebMvcConfigurer {
@Override @Override
public void addInterceptors(InterceptorRegistry registry) { public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(xssInterceptor) registry.addInterceptor(xssInterceptor).addPathPatterns("/**").excludePathPatterns("/error");
.addPathPatterns("/**") registry.addInterceptor(authInterceptor).addPathPatterns("/**")
.excludePathPatterns("/error"); .excludePathPatterns("/login", "/logout", "/register", "/refreshToken", "/swagger/**", "/v3/api-docs/**",
registry.addInterceptor(authInterceptor) "/swagger-resources/**", "/swagger-ui.html", "/swagger-ui/**", "/doc.html");
.addPathPatterns("/**")
.excludePathPatterns("/login", "/logout", "/register", "/refreshToken",
"/swagger-resources/**", "/webjars/**", "/v2/**", "/swagger-ui.html/**");
} }
@Bean @Bean

54
im-platform/src/main/java/com/bx/implatform/config/RedisConfig.java

@ -1,18 +1,17 @@
package com.bx.implatform.config; package com.bx.implatform.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import jakarta.annotation.Resource;
import org.springframework.cache.CacheManager; import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching; 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.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary; import org.springframework.context.annotation.Primary;
@ -24,9 +23,7 @@ import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext; import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer;
import javax.annotation.Resource;
import java.time.Duration; import java.time.Duration;
import java.util.Objects;
@EnableCaching @EnableCaching
@Configuration @Configuration
@ -40,7 +37,6 @@ public class RedisConfig extends CachingConfigurerSupport {
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory); redisTemplate.setConnectionFactory(redisConnectionFactory);
// 设置值(value)的序列化采用jackson2JsonRedisSerializer // 设置值(value)的序列化采用jackson2JsonRedisSerializer
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer()); redisTemplate.setValueSerializer(jackson2JsonRedisSerializer());
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer()); redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer());
@ -51,42 +47,28 @@ public class RedisConfig extends CachingConfigurerSupport {
return redisTemplate; return redisTemplate;
} }
@Bean
public CacheManager cacheManager() {
// 设置redis缓存管理器
RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofSeconds(600))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer()));
return RedisCacheManager.builder(factory).cacheDefaults(cacheConfiguration).build();
}
@Bean @Bean
public Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer() { public Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer() {
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper(); ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 解决jackson2无法反序列化LocalDateTime的问题 // 解决jackson2无法反序列化LocalDateTime的问题
om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
om.registerModule(new JavaTimeModule()); om.registerModule(new JavaTimeModule());
om.setSerializationInclusion(JsonInclude.Include.NON_NULL); // 忽略空值
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
jackson2JsonRedisSerializer.setObjectMapper(om); //忽略无效字段
return jackson2JsonRedisSerializer; om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
} return new Jackson2JsonRedisSerializer<>(om, Object.class);
@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();
} }
} }

9
im-platform/src/main/java/com/bx/implatform/config/RedissonConfig.java

@ -6,11 +6,8 @@ import org.redisson.api.RedissonClient;
import org.redisson.client.codec.StringCodec; import org.redisson.client.codec.StringCodec;
import org.redisson.config.Config; import org.redisson.config.Config;
import org.redisson.config.SingleServerConfig; import org.redisson.config.SingleServerConfig;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties; import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/** /**
* @author: Blue * @author: Blue
@ -18,9 +15,9 @@ import org.springframework.context.annotation.Configuration;
* @version: 1.0 * @version: 1.0
*/ */
@Configuration //@Configuration
@ConditionalOnClass(Config.class) //@ConditionalOnClass(Config.class)
@EnableConfigurationProperties(RedisProperties.class) //@EnableConfigurationProperties(RedisProperties.class)
public class RedissonConfig { public class RedissonConfig {
@Bean @Bean

51
im-platform/src/main/java/com/bx/implatform/config/SwaggerConfig.java

@ -1,41 +1,38 @@
package com.bx.implatform.config; package com.bx.implatform.config;
import io.swagger.annotations.ApiOperation;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; 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 @Configuration
@EnableSwagger2
public class SwaggerConfig { public class SwaggerConfig {
@Bean @Bean
public Docket createRestApi() { public GroupedOpenApi userApi() {
String[] paths = {"/**"};
return new Docket(DocumentationType.SWAGGER_2) String[] packagedToMatch = {"com.bx"};
.apiInfo(apiInfo()) return GroupedOpenApi.builder().group("IM-Platform")
.select() .pathsToMatch(paths)
//这里采用包含注解的方式来确定要显示的接口 .packagesToScan(packagedToMatch).build();
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
//这里采用包扫描的方式来确定要显示的接口
.paths(PathSelectors.any())
.build();
} }
private ApiInfo apiInfo() { @Bean
return new ApiInfoBuilder() public OpenAPI customOpenAPI() {
.title("IM Platform doc") Contact contact = new Contact();
.description("盒子IM API文档") contact.setName("Blue");
.termsOfServiceUrl("http://8.134.92.70/") return new OpenAPI().info(new Info()
.version("1.0") .title("盒子IM接口文档")
.build(); .description("盒子IM业务平台服务")
.contact(contact)
.version("3.0")
.termsOfService("https://www.boxim.online")
.license(new License().name("MIT")
.url("https://www.boxim.online")));
} }
} }

13
im-platform/src/main/java/com/bx/implatform/contant/Constant.java

@ -2,20 +2,21 @@ package com.bx.implatform.contant;
public final class Constant { public final class Constant {
private Constant() { /**
} * 系统用户id
*/
public static final Long SYS_USER_ID = 0L;
/** /**
* 最大图片上传大小 * 最大图片上传大小
*/ */
public static final long MAX_IMAGE_SIZE = 20 * 1024 * 1024; public static final Long MAX_IMAGE_SIZE = 20 * 1024 * 1024L;
/** /**
* 最大上传文件大小 * 最大上传文件大小
*/ */
public static final long MAX_FILE_SIZE = 20 * 1024 * 1024; public static final Long MAX_FILE_SIZE = 20 * 1024 * 1024L;
/** /**
* 群聊最大人数 * 群聊最大人数
*/ */
public static final long MAX_GROUP_MEMBER = 500; public static final Long MAX_GROUP_MEMBER = 500L;
} }

31
im-platform/src/main/java/com/bx/implatform/contant/RedisKey.java

@ -18,31 +18,38 @@ public final class RedisKey {
* webrtc 群通话 * webrtc 群通话
*/ */
public static final String IM_WEBRTC_GROUP_SESSION = "im:webrtc:group:session"; public static final String IM_WEBRTC_GROUP_SESSION = "im:webrtc:group:session";
/**
* 用户被封禁消息队列
*/
public static final String IM_QUEUE_USER_BANNED = "im:queue:user:banned";
/**
* 群聊被封禁消息队列
*/
public static final String IM_QUEUE_GROUP_BANNED = "im:queue:group:banned";
/** /**
* 缓存前缀 * 群聊解封消息队列
*/ */
public static final String IM_CACHE = "im:cache:"; public static final String IM_QUEUE_GROUP_UNBAN = "im:queue:group:unban";
/** /**
* 缓存是否好友bool * 缓存是否好友bool
*/ */
public static final String IM_CACHE_FRIEND = IM_CACHE + "friend"; public static final String IM_CACHE_FRIEND = "im:cache:friend";
/** /**
* 缓存群聊信息 * 缓存群聊信息
*/ */
public static final String IM_CACHE_GROUP = IM_CACHE + "group"; public static final String IM_CACHE_GROUP = "im:cache:group";
/** /**
* 缓存群聊成员id * 缓存群聊成员id
*/ */
public static final String IM_CACHE_GROUP_MEMBER_ID = IM_CACHE + "group_member_ids"; public static final String IM_CACHE_GROUP_MEMBER_ID = "im:cache:group_member_ids";
/**
* 分布式锁前缀
*/
public static final String IM_LOCK = "im:lock:";
/** /**
* 分布式锁前缀 * 分布式锁前缀
*/ */
public static final String IM_LOCK_RTC_GROUP = IM_LOCK + "rtc:group"; public static final String IM_LOCK_RTC_GROUP = "im:lock:rtc:group";
} }

15
im-platform/src/main/java/com/bx/implatform/controller/FileController.java

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

30
im-platform/src/main/java/com/bx/implatform/controller/FriendController.java

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

37
im-platform/src/main/java/com/bx/implatform/controller/GroupController.java

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

37
im-platform/src/main/java/com/bx/implatform/controller/GroupMessageController.java

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

25
im-platform/src/main/java/com/bx/implatform/controller/LoginController.java

@ -5,47 +5,44 @@ import com.bx.implatform.dto.ModifyPwdDTO;
import com.bx.implatform.dto.RegisterDTO; import com.bx.implatform.dto.RegisterDTO;
import com.bx.implatform.result.Result; import com.bx.implatform.result.Result;
import com.bx.implatform.result.ResultUtils; import com.bx.implatform.result.ResultUtils;
import com.bx.implatform.service.IUserService; import com.bx.implatform.service.UserService;
import com.bx.implatform.vo.LoginVO; import com.bx.implatform.vo.LoginVO;
import io.swagger.annotations.Api; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.annotations.ApiOperation; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import javax.validation.Valid; @Tag(name = "注册登陆")
@Api(tags = "用户登录和注册")
@RestController @RestController
@RequiredArgsConstructor @RequiredArgsConstructor
public class LoginController { public class LoginController {
private final IUserService userService; private final UserService userService;
@PostMapping("/login") @PostMapping("/login")
@ApiOperation(value = "用户登陆", notes = "用户登陆") @Operation(summary = "用户登陆", description = "用户登陆")
public Result login(@Valid @RequestBody LoginDTO dto) { public Result<LoginVO> login(@Valid @RequestBody LoginDTO dto) {
LoginVO vo = userService.login(dto); LoginVO vo = userService.login(dto);
return ResultUtils.success(vo); return ResultUtils.success(vo);
} }
@PutMapping("/refreshToken") @PutMapping("/refreshToken")
@ApiOperation(value = "刷新token", notes = "用refreshtoken换取新的token") @Operation(summary = "刷新token", description = "用refreshtoken换取新的token")
public Result refreshToken(@RequestHeader("refreshToken") String refreshToken) { public Result refreshToken(@RequestHeader("refreshToken") String refreshToken) {
LoginVO vo = userService.refreshToken(refreshToken); LoginVO vo = userService.refreshToken(refreshToken);
return ResultUtils.success(vo); return ResultUtils.success(vo);
} }
@PostMapping("/register") @PostMapping("/register")
@ApiOperation(value = "用户注册", notes = "用户注册") @Operation(summary = "用户注册", description = "用户注册")
public Result register(@Valid @RequestBody RegisterDTO dto) { public Result register(@Valid @RequestBody RegisterDTO dto) {
userService.register(dto); userService.register(dto);
return ResultUtils.success(); return ResultUtils.success();
} }
@PutMapping("/modifyPwd") @PutMapping("/modifyPwd")
@ApiOperation(value = "修改密码", notes = "修改用户密码") @Operation(summary = "修改密码", description = "修改用户密码")
public Result modifyPassword(@Valid @RequestBody ModifyPwdDTO dto) { public Result modifyPassword(@Valid @RequestBody ModifyPwdDTO dto) {
userService.modifyPassword(dto); userService.modifyPassword(dto);
return ResultUtils.success(); return ResultUtils.success();

38
im-platform/src/main/java/com/bx/implatform/controller/PrivateMessageController.java

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

13
im-platform/src/main/java/com/bx/implatform/controller/SystemController.java

@ -1,14 +1,15 @@
package com.bx.implatform.controller; package com.bx.implatform.controller;
import com.bx.implatform.config.WebrtcConfig; import com.bx.implatform.config.WebrtcConfig;
import com.bx.implatform.dto.PrivateMessageDTO;
import com.bx.implatform.result.Result; import com.bx.implatform.result.Result;
import com.bx.implatform.result.ResultUtils; import com.bx.implatform.result.ResultUtils;
import com.bx.implatform.vo.SystemConfigVO; import com.bx.implatform.vo.SystemConfigVO;
import io.swagger.annotations.Api; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.annotations.ApiOperation; import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@ -17,7 +18,7 @@ import org.springframework.web.bind.annotation.*;
* @date: 2024-06-10 * @date: 2024-06-10
* @version: 1.0 * @version: 1.0
*/ */
@Api(tags = "系统相关") @Tag(name = "系统相关")
@RestController @RestController
@RequestMapping("/system") @RequestMapping("/system")
@RequiredArgsConstructor @RequiredArgsConstructor
@ -26,7 +27,7 @@ public class SystemController {
private final WebrtcConfig webrtcConfig; private final WebrtcConfig webrtcConfig;
@GetMapping("/config") @GetMapping("/config")
@ApiOperation(value = "加载系统配置", notes = "加载系统配置") @Operation(summary = "加载系统配置", description = "加载系统配置")
public Result<SystemConfigVO> loadConfig() { public Result<SystemConfigVO> loadConfig() {
return ResultUtils.success(new SystemConfigVO(webrtcConfig)); return ResultUtils.success(new SystemConfigVO(webrtcConfig));
} }

30
im-platform/src/main/java/com/bx/implatform/controller/UserController.java

@ -3,38 +3,38 @@ package com.bx.implatform.controller;
import com.bx.implatform.entity.User; import com.bx.implatform.entity.User;
import com.bx.implatform.result.Result; import com.bx.implatform.result.Result;
import com.bx.implatform.result.ResultUtils; import com.bx.implatform.result.ResultUtils;
import com.bx.implatform.service.IUserService; import com.bx.implatform.service.UserService;
import com.bx.implatform.session.SessionContext; import com.bx.implatform.session.SessionContext;
import com.bx.implatform.session.UserSession; import com.bx.implatform.session.UserSession;
import com.bx.implatform.util.BeanUtils; import com.bx.implatform.util.BeanUtils;
import com.bx.implatform.vo.OnlineTerminalVO; import com.bx.implatform.vo.OnlineTerminalVO;
import com.bx.implatform.vo.UserVO; import com.bx.implatform.vo.UserVO;
import io.swagger.annotations.Api; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.annotations.ApiOperation; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import java.util.List; import java.util.List;
@Api(tags = "用户") @Tag(name = "用户相关")
@RestController @RestController
@RequestMapping("/user") @RequestMapping("/user")
@RequiredArgsConstructor @RequiredArgsConstructor
public class UserController { public class UserController {
private final IUserService userService; private final UserService userService;
@GetMapping("/terminal/online") @GetMapping("/terminal/online")
@ApiOperation(value = "判断用户哪个终端在线", notes = "返回在线的用户id的终端集合") @Operation(summary = "判断用户哪个终端在线", description = "返回在线的用户id的终端集合")
public Result<List<OnlineTerminalVO>> getOnlineTerminal(@NotEmpty @RequestParam("userIds") String userIds) { public Result<List<OnlineTerminalVO>> getOnlineTerminal(@NotNull @RequestParam("userIds") String userIds) {
return ResultUtils.success(userService.getOnlineTerminals(userIds)); return ResultUtils.success(userService.getOnlineTerminals(userIds));
} }
@GetMapping("/self") @GetMapping("/self")
@ApiOperation(value = "获取当前用户信息", notes = "获取当前用户信息") @Operation(summary = "获取当前用户信息", description = "获取当前用户信息")
public Result<UserVO> findSelfInfo() { public Result<UserVO> findSelfInfo() {
UserSession session = SessionContext.getSession(); UserSession session = SessionContext.getSession();
User user = userService.getById(session.getUserId()); User user = userService.getById(session.getUserId());
@ -44,21 +44,21 @@ public class UserController {
@GetMapping("/find/{id}") @GetMapping("/find/{id}")
@ApiOperation(value = "查找用户", notes = "根据id查找用户") @Operation(summary = "查找用户", description = "根据id查找用户")
public Result<UserVO> findById(@NotEmpty @PathVariable("id") Long id) { public Result<UserVO> findById(@NotNull @PathVariable("id") Long id) {
return ResultUtils.success(userService.findUserById(id)); return ResultUtils.success(userService.findUserById(id));
} }
@PutMapping("/update") @PutMapping("/update")
@ApiOperation(value = "修改用户信息", notes = "修改用户信息,仅允许修改登录用户信息") @Operation(summary = "修改用户信息", description = "修改用户信息,仅允许修改登录用户信息")
public Result update(@Valid @RequestBody UserVO vo) { public Result update(@Valid @RequestBody UserVO vo) {
userService.update(vo); userService.update(vo);
return ResultUtils.success(); return ResultUtils.success();
} }
@GetMapping("/findByName") @GetMapping("/findByName")
@ApiOperation(value = "查找用户", notes = "根据用户名或昵称查找用户") @Operation(summary = "查找用户", description = "根据用户名或昵称查找用户")
public Result<List<UserVO>> findByName(@RequestParam("name") String name) { public Result<List<UserVO>> findByName(@RequestParam String name) {
return ResultUtils.success(userService.findUserByName(name)); return ResultUtils.success(userService.findUserByName(name));
} }
} }

58
im-platform/src/main/java/com/bx/implatform/controller/WebrtcGroupController.java

@ -1,126 +1,124 @@
package com.bx.implatform.controller; package com.bx.implatform.controller;
import com.bx.implatform.config.WebrtcConfig;
import com.bx.implatform.dto.*; import com.bx.implatform.dto.*;
import com.bx.implatform.result.Result; import com.bx.implatform.result.Result;
import com.bx.implatform.result.ResultUtils; import com.bx.implatform.result.ResultUtils;
import com.bx.implatform.service.IWebrtcGroupService; import com.bx.implatform.service.WebrtcGroupService;
import com.bx.implatform.vo.WebrtcGroupInfoVO; import com.bx.implatform.vo.WebrtcGroupInfoVO;
import io.swagger.annotations.Api; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.annotations.ApiOperation; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
/** /**
* @author: Blue * @author: Blue
* @date: 2024-06-01 * @date: 2024-06-01
* @version: 1.0 * @version: 1.0
*/ */
@Api(tags = "webrtc视频多人通话") @Tag(name = "多人通话")
@RestController @RestController
@RequestMapping("/webrtc/group") @RequestMapping("/webrtc/group")
@RequiredArgsConstructor @RequiredArgsConstructor
public class WebrtcGroupController { public class WebrtcGroupController {
private final IWebrtcGroupService webrtcGroupService; private final WebrtcGroupService webrtcGroupService;
@ApiOperation(httpMethod = "POST", value = "发起群视频通话") @Operation(summary = "发起群视频通话")
@PostMapping("/setup") @PostMapping("/setup")
public Result setup(@Valid @RequestBody WebrtcGroupSetupDTO dto) { public Result setup(@Valid @RequestBody WebrtcGroupSetupDTO dto) {
webrtcGroupService.setup(dto); webrtcGroupService.setup(dto);
return ResultUtils.success(); return ResultUtils.success();
} }
@ApiOperation(httpMethod = "POST", value = "接受通话") @Operation(summary = "接受通话")
@PostMapping("/accept") @PostMapping("/accept")
public Result accept(@RequestParam Long groupId) { public Result accept(@RequestParam("groupId") Long groupId) {
webrtcGroupService.accept(groupId); webrtcGroupService.accept(groupId);
return ResultUtils.success(); return ResultUtils.success();
} }
@ApiOperation(httpMethod = "POST", value = "拒绝通话") @Operation(summary = "拒绝通话")
@PostMapping("/reject") @PostMapping("/reject")
public Result reject(@RequestParam Long groupId) { public Result reject(@RequestParam("groupId") Long groupId) {
webrtcGroupService.reject(groupId); webrtcGroupService.reject(groupId);
return ResultUtils.success(); return ResultUtils.success();
} }
@ApiOperation(httpMethod = "POST", value = "通话失败") @Operation(summary = "通话失败")
@PostMapping("/failed") @PostMapping("/failed")
public Result failed(@Valid @RequestBody WebrtcGroupFailedDTO dto) { public Result failed(@Valid @RequestBody WebrtcGroupFailedDTO dto) {
webrtcGroupService.failed(dto); webrtcGroupService.failed(dto);
return ResultUtils.success(); return ResultUtils.success();
} }
@ApiOperation(httpMethod = "POST", value = "进入视频通话") @Operation(summary = "进入视频通话")
@PostMapping("/join") @PostMapping("/join")
public Result join(@RequestParam Long groupId) { public Result join(@RequestParam("groupId") Long groupId) {
webrtcGroupService.join(groupId); webrtcGroupService.join(groupId);
return ResultUtils.success(); return ResultUtils.success();
} }
@ApiOperation(httpMethod = "POST", value = "取消通话") @Operation(summary = "取消通话")
@PostMapping("/cancel") @PostMapping("/cancel")
public Result cancel(@RequestParam Long groupId) { public Result cancel(@RequestParam("groupId") Long groupId) {
webrtcGroupService.cancel(groupId); webrtcGroupService.cancel(groupId);
return ResultUtils.success(); return ResultUtils.success();
} }
@ApiOperation(httpMethod = "POST", value = "离开视频通话") @Operation(summary = "离开视频通话")
@PostMapping("/quit") @PostMapping("/quit")
public Result quit(@RequestParam Long groupId) { public Result quit(@RequestParam("groupId") Long groupId) {
webrtcGroupService.quit(groupId); webrtcGroupService.quit(groupId);
return ResultUtils.success(); return ResultUtils.success();
} }
@ApiOperation(httpMethod = "POST", value = "推送offer信息") @Operation(summary = "推送offer信息")
@PostMapping("/offer") @PostMapping("/offer")
public Result offer(@Valid @RequestBody WebrtcGroupOfferDTO dto) { public Result offer(@Valid @RequestBody WebrtcGroupOfferDTO dto) {
webrtcGroupService.offer(dto); webrtcGroupService.offer(dto);
return ResultUtils.success(); return ResultUtils.success();
} }
@ApiOperation(httpMethod = "POST", value = "推送answer信息") @Operation(summary = "推送answer信息")
@PostMapping("/answer") @PostMapping("/answer")
public Result answer(@Valid @RequestBody WebrtcGroupAnswerDTO dto) { public Result answer(@Valid @RequestBody WebrtcGroupAnswerDTO dto) {
webrtcGroupService.answer(dto); webrtcGroupService.answer(dto);
return ResultUtils.success(); return ResultUtils.success();
} }
@ApiOperation(httpMethod = "POST", value = "邀请用户进入视频通话") @Operation(summary = "邀请用户进入视频通话")
@PostMapping("/invite") @PostMapping("/invite")
public Result invite(@Valid @RequestBody WebrtcGroupInviteDTO dto) { public Result invite(@Valid @RequestBody WebrtcGroupInviteDTO dto) {
webrtcGroupService.invite(dto); webrtcGroupService.invite(dto);
return ResultUtils.success(); return ResultUtils.success();
} }
@ApiOperation(httpMethod = "POST", value = "同步candidate") @Operation(summary = "同步candidate")
@PostMapping("/candidate") @PostMapping("/candidate")
public Result candidate(@Valid @RequestBody WebrtcGroupCandidateDTO dto) { public Result candidate(@Valid @RequestBody WebrtcGroupCandidateDTO dto) {
webrtcGroupService.candidate(dto); webrtcGroupService.candidate(dto);
return ResultUtils.success(); return ResultUtils.success();
} }
@ApiOperation(httpMethod = "POST", value = "设备操作") @Operation(summary = "设备操作")
@PostMapping("/device") @PostMapping("/device")
public Result device(@Valid @RequestBody WebrtcGroupDeviceDTO dto) { public Result device(@Valid @RequestBody WebrtcGroupDeviceDTO dto) {
webrtcGroupService.device(dto); webrtcGroupService.device(dto);
return ResultUtils.success(); return ResultUtils.success();
} }
@ApiOperation(httpMethod = "GET", value = "获取通话信息") @Operation(summary = "获取通话信息")
@GetMapping("/info") @GetMapping("/info")
public Result<WebrtcGroupInfoVO> info(@RequestParam Long groupId) { public Result<WebrtcGroupInfoVO> info(@RequestParam("groupId") Long groupId) {
return ResultUtils.success(webrtcGroupService.info(groupId)); return ResultUtils.success(webrtcGroupService.info(groupId));
} }
@ApiOperation(httpMethod = "POST", value = "获取通话信息") @Operation(summary = "获取通话信息")
@PostMapping("/heartbeat") @PostMapping("/heartbeat")
public Result heartbeat(@RequestParam Long groupId) { public Result heartbeat(@RequestParam("groupId") Long groupId) {
webrtcGroupService.heartbeat(groupId); webrtcGroupService.heartbeat(groupId);
return ResultUtils.success(); return ResultUtils.success();
} }
} }

34
im-platform/src/main/java/com/bx/implatform/controller/WebrtcPrivateController.java

@ -1,78 +1,74 @@
package com.bx.implatform.controller; package com.bx.implatform.controller;
import com.bx.implatform.annotation.OnlineCheck; import com.bx.implatform.annotation.OnlineCheck;
import com.bx.implatform.config.ICEServer;
import com.bx.implatform.result.Result; import com.bx.implatform.result.Result;
import com.bx.implatform.result.ResultUtils; import com.bx.implatform.result.ResultUtils;
import com.bx.implatform.service.IWebrtcPrivateService; import com.bx.implatform.service.WebrtcPrivateService;
import io.swagger.annotations.Api; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.annotations.ApiOperation; import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.util.List; @Tag(name = "单人通话")
@Api(tags = "webrtc视频单人通话")
@RestController @RestController
@RequestMapping("/webrtc/private") @RequestMapping("/webrtc/private")
@RequiredArgsConstructor @RequiredArgsConstructor
public class WebrtcPrivateController { public class WebrtcPrivateController {
private final IWebrtcPrivateService webrtcPrivateService; private final WebrtcPrivateService webrtcPrivateService;
@OnlineCheck @OnlineCheck
@ApiOperation(httpMethod = "POST", value = "呼叫视频通话") @Operation(summary = "呼叫视频通话")
@PostMapping("/call") @PostMapping("/call")
public Result call(@RequestParam Long uid, @RequestParam(defaultValue = "video") String mode, @RequestBody String offer) { public Result call(@RequestParam Long uid, @RequestParam(defaultValue = "video") String mode,
@RequestBody String offer) {
webrtcPrivateService.call(uid, mode, offer); webrtcPrivateService.call(uid, mode, offer);
return ResultUtils.success(); return ResultUtils.success();
} }
@ApiOperation(httpMethod = "POST", value = "接受视频通话") @Operation(summary = "接受视频通话")
@PostMapping("/accept") @PostMapping("/accept")
public Result accept(@RequestParam Long uid, @RequestBody String answer) { public Result accept(@RequestParam Long uid, @RequestBody String answer) {
webrtcPrivateService.accept(uid, answer); webrtcPrivateService.accept(uid, answer);
return ResultUtils.success(); return ResultUtils.success();
} }
@Operation(summary = "拒绝视频通话")
@ApiOperation(httpMethod = "POST", value = "拒绝视频通话")
@PostMapping("/reject") @PostMapping("/reject")
public Result reject(@RequestParam Long uid) { public Result reject(@RequestParam Long uid) {
webrtcPrivateService.reject(uid); webrtcPrivateService.reject(uid);
return ResultUtils.success(); return ResultUtils.success();
} }
@ApiOperation(httpMethod = "POST", value = "取消呼叫") @Operation(summary = "取消呼叫")
@PostMapping("/cancel") @PostMapping("/cancel")
public Result cancel(@RequestParam Long uid) { public Result cancel(@RequestParam Long uid) {
webrtcPrivateService.cancel(uid); webrtcPrivateService.cancel(uid);
return ResultUtils.success(); return ResultUtils.success();
} }
@ApiOperation(httpMethod = "POST", value = "呼叫失败") @Operation(summary = "呼叫失败")
@PostMapping("/failed") @PostMapping("/failed")
public Result failed(@RequestParam Long uid, @RequestParam String reason) { public Result failed(@RequestParam Long uid, @RequestParam String reason) {
webrtcPrivateService.failed(uid, reason); webrtcPrivateService.failed(uid, reason);
return ResultUtils.success(); return ResultUtils.success();
} }
@ApiOperation(httpMethod = "POST", value = "挂断") @Operation(summary = "挂断")
@PostMapping("/handup") @PostMapping("/handup")
public Result handup(@RequestParam Long uid) { public Result handup(@RequestParam Long uid) {
webrtcPrivateService.handup(uid); webrtcPrivateService.handup(uid);
return ResultUtils.success(); return ResultUtils.success();
} }
@PostMapping("/candidate") @PostMapping("/candidate")
@ApiOperation(httpMethod = "POST", value = "同步candidate") @Operation(summary = "同步candidate")
public Result candidate(@RequestParam Long uid, @RequestBody String candidate) { public Result candidate(@RequestParam Long uid, @RequestBody String candidate) {
webrtcPrivateService.candidate(uid, candidate); webrtcPrivateService.candidate(uid, candidate);
return ResultUtils.success(); return ResultUtils.success();
} }
@ApiOperation(httpMethod = "POST", value = "获取通话信息") @Operation(summary = "获取通话信息")
@PostMapping("/heartbeat") @PostMapping("/heartbeat")
public Result heartbeat(@RequestParam Long uid) { public Result heartbeat(@RequestParam Long uid) {
webrtcPrivateService.heartbeat(uid); webrtcPrivateService.heartbeat(uid);

21
im-platform/src/main/java/com/bx/implatform/dto/GroupBanDTO.java

@ -0,0 +1,21 @@
package com.bx.implatform.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* @author: Blue
* @date: 2024-07-14
* @version: 1.0
*/
@Data
@Schema(description = "群组封禁")
public class GroupBanDTO {
@Schema(description = "群组id")
private Long id;
@Schema(description = "封禁原因")
private String reason;
}

21
im-platform/src/main/java/com/bx/implatform/dto/GroupMessageDTO.java

@ -1,36 +1,35 @@
package com.bx.implatform.dto; package com.bx.implatform.dto;
import io.swagger.annotations.ApiModel; import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.annotations.ApiModelProperty; import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data; import lombok.Data;
import org.hibernate.validator.constraints.Length; 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; import java.util.List;
@Data @Data
@ApiModel("群聊消息DTO") @Schema(description = "群聊消息DTO")
public class GroupMessageDTO { public class GroupMessageDTO {
@NotNull(message = "群聊id不可为空") @NotNull(message = "群聊id不可为空")
@ApiModelProperty(value = "群聊id") @Schema(description = "群聊id")
private Long groupId; private Long groupId;
@Length(max = 1024, message = "发送内容长度不得大于1024") @Length(max = 1024, message = "发送内容长度不得大于1024")
@NotEmpty(message = "发送内容不可为空") @NotEmpty(message = "发送内容不可为空")
@ApiModelProperty(value = "发送内容") @Schema(description = "发送内容")
private String content; private String content;
@NotNull(message = "消息类型不可为空") @NotNull(message = "消息类型不可为空")
@ApiModelProperty(value = "消息类型") @Schema(description = "消息类型 0:文字 1:图片 2:文件 3:语音 4:视频")
private Integer type; private Integer type;
@ApiModelProperty(value = "是否回执消息") @Schema(description = "是否回执消息")
private Boolean receipt = false; private Boolean receipt = false;
@Size(max = 20, message = "一次最多只能@20个小伙伴哦") @Size(max = 20, message = "一次最多只能@20个小伙伴哦")
@ApiModelProperty(value = "被@用户列表") @Schema(description = "被@用户列表")
private List<Long> atUserIds; private List<Long> atUserIds;
} }

18
im-platform/src/main/java/com/bx/implatform/dto/GroupUnbanDTO.java

@ -0,0 +1,18 @@
package com.bx.implatform.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* @author: Blue
* @date: 2024-07-14
* @version: 1.0
*/
@Data
@Schema(description = "群组解锁")
public class GroupUnbanDTO {
@Schema(description = "群组id")
private Long id;
}

20
im-platform/src/main/java/com/bx/implatform/dto/LoginDTO.java

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

12
im-platform/src/main/java/com/bx/implatform/dto/ModifyPwdDTO.java

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

16
im-platform/src/main/java/com/bx/implatform/dto/PrivateMessageDTO.java

@ -1,29 +1,27 @@
package com.bx.implatform.dto; package com.bx.implatform.dto;
import io.swagger.annotations.ApiModel; import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.annotations.ApiModelProperty; import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data; import lombok.Data;
import org.hibernate.validator.constraints.Length; import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
@Data @Data
@ApiModel("私聊消息DTO") @Schema(description = "私聊消息DTO")
public class PrivateMessageDTO { public class PrivateMessageDTO {
@NotNull(message = "接收用户id不可为空") @NotNull(message = "接收用户id不可为空")
@ApiModelProperty(value = "接收用户id") @Schema(description = "接收用户id")
private Long recvId; private Long recvId;
@Length(max = 1024, message = "内容长度不得大于1024") @Length(max = 1024, message = "内容长度不得大于1024")
@NotEmpty(message = "发送内容不可为空") @NotEmpty(message = "发送内容不可为空")
@ApiModelProperty(value = "发送内容") @Schema(description = "发送内容")
private String content; private String content;
@NotNull(message = "消息类型不可为空") @NotNull(message = "消息类型不可为空")
@ApiModelProperty(value = "消息类型") @Schema(description = "消息类型 0:文字 1:图片 2:文件 3:语音 4:视频")
private Integer type; private Integer type;
} }

14
im-platform/src/main/java/com/bx/implatform/dto/RegisterDTO.java

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

21
im-platform/src/main/java/com/bx/implatform/dto/UserBanDTO.java

@ -0,0 +1,21 @@
package com.bx.implatform.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* @author: Blue
* @date: 2024-07-14
* @version: 1.0
*/
@Data
@Schema(description = "用户锁定DTO")
public class UserBanDTO {
@Schema(description = "用户id")
private Long id;
@Schema(description = "锁定原因")
private String reason;
}

16
im-platform/src/main/java/com/bx/implatform/dto/WebrtcGroupAnswerDTO.java

@ -1,31 +1,29 @@
package com.bx.implatform.dto; package com.bx.implatform.dto;
import io.swagger.annotations.ApiModel; import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.annotations.ApiModelProperty; import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data; import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
/** /**
* @author: Blue * @author: Blue
* @date: 2024-06-01 * @date: 2024-06-01
* @version: 1.0 * @version: 1.0
*/ */
@Data @Data
@ApiModel("回复用户连接请求DTO") @Schema(description = "回复用户连接请求DTO")
public class WebrtcGroupAnswerDTO { public class WebrtcGroupAnswerDTO {
@NotNull(message = "群聊id不可为空") @NotNull(message = "群聊id不可为空")
@ApiModelProperty(value = "群聊id") @Schema(description = "群聊id")
private Long groupId; private Long groupId;
@NotNull(message = "用户id不可为空") @NotNull(message = "用户id不可为空")
@ApiModelProperty(value = "用户id,代表回复谁的连接请求") @Schema(description = "用户id,代表回复谁的连接请求")
private Long userId; private Long userId;
@NotEmpty(message = "anwer不可为空") @NotEmpty(message = "anwer不可为空")
@ApiModelProperty(value = "用户本地anwer信息") @Schema(description = "用户本地anwer信息")
private String answer; private String answer;
} }

17
im-platform/src/main/java/com/bx/implatform/dto/WebrtcGroupCandidateDTO.java

@ -1,32 +1,29 @@
package com.bx.implatform.dto; package com.bx.implatform.dto;
import io.swagger.annotations.ApiModel; import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.annotations.ApiModelProperty; import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data; import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.List;
/** /**
* @author: Blue * @author: Blue
* @date: 2024-06-01 * @date: 2024-06-01
* @version: 1.0 * @version: 1.0
*/ */
@Data @Data
@ApiModel("发起群视频通话DTO") @Schema(description = "发起群视频通话DTO")
public class WebrtcGroupCandidateDTO { public class WebrtcGroupCandidateDTO {
@NotNull(message = "群聊id不可为空") @NotNull(message = "群聊id不可为空")
@ApiModelProperty(value = "群聊id") @Schema(description = "群聊id")
private Long groupId; private Long groupId;
@NotNull(message = "用户id不可为空") @NotNull(message = "用户id不可为空")
@ApiModelProperty(value = "用户id") @Schema(description = "用户id")
private Long userId; private Long userId;
@NotEmpty(message = "candidate信息不可为空") @NotEmpty(message = "candidate信息不可为空")
@ApiModelProperty(value = "candidate信息") @Schema(description = "candidate信息")
private String candidate; private String candidate;
} }

15
im-platform/src/main/java/com/bx/implatform/dto/WebrtcGroupDeviceDTO.java

@ -1,29 +1,26 @@
package com.bx.implatform.dto; package com.bx.implatform.dto;
import io.swagger.annotations.ApiModel; import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.annotations.ApiModelProperty; import jakarta.validation.constraints.NotNull;
import lombok.Data; import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
/** /**
* @author: Blue * @author: Blue
* @date: 2024-06-01 * @date: 2024-06-01
* @version: 1.0 * @version: 1.0
*/ */
@Data @Data
@ApiModel("用户设备操作DTO") @Schema(description = "用户设备操作DTO")
public class WebrtcGroupDeviceDTO { public class WebrtcGroupDeviceDTO {
@NotNull(message = "群聊id不可为空") @NotNull(message = "群聊id不可为空")
@ApiModelProperty(value = "群聊id") @Schema(description = "群聊id")
private Long groupId; private Long groupId;
@ApiModelProperty(value = "是否开启摄像头") @Schema(description = "是否开启摄像头")
private Boolean isCamera; private Boolean isCamera;
@ApiModelProperty(value = "是否开启麦克风") @Schema(description = "是否开启麦克风")
private Boolean isMicroPhone; private Boolean isMicroPhone;
} }

12
im-platform/src/main/java/com/bx/implatform/dto/WebrtcGroupFailedDTO.java

@ -1,25 +1,23 @@
package com.bx.implatform.dto; package com.bx.implatform.dto;
import io.swagger.annotations.ApiModel; import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.annotations.ApiModelProperty; import jakarta.validation.constraints.NotNull;
import lombok.Data; import lombok.Data;
import javax.validation.constraints.NotNull;
/** /**
* @author: Blue * @author: Blue
* @date: 2024-06-01 * @date: 2024-06-01
* @version: 1.0 * @version: 1.0
*/ */
@Data @Data
@ApiModel("用户通话失败DTO") @Schema(description = "用户通话失败DTO")
public class WebrtcGroupFailedDTO { public class WebrtcGroupFailedDTO {
@NotNull(message = "群聊id不可为空") @NotNull(message = "群聊id不可为空")
@ApiModelProperty(value = "群聊id") @Schema(description = "群聊id")
private Long groupId; private Long groupId;
@ApiModelProperty(value = "失败原因") @Schema(description = "失败原因")
private String reason; private String reason;
} }

13
im-platform/src/main/java/com/bx/implatform/dto/WebrtcGroupInviteDTO.java

@ -1,12 +1,11 @@
package com.bx.implatform.dto; package com.bx.implatform.dto;
import com.bx.implatform.session.WebrtcUserInfo; import com.bx.implatform.session.WebrtcUserInfo;
import io.swagger.annotations.ApiModel; import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.annotations.ApiModelProperty; import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data; import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.List; import java.util.List;
/** /**
@ -15,15 +14,15 @@ import java.util.List;
* @version: 1.0 * @version: 1.0
*/ */
@Data @Data
@ApiModel("邀请用户进入群视频通话DTO") @Schema(description = "邀请用户进入群视频通话DTO")
public class WebrtcGroupInviteDTO { public class WebrtcGroupInviteDTO {
@NotNull(message = "群聊id不可为空") @NotNull(message = "群聊id不可为空")
@ApiModelProperty(value = "群聊id") @Schema(description = "群聊id")
private Long groupId; private Long groupId;
@NotEmpty(message = "参与用户信息不可为空") @NotEmpty(message = "参与用户信息不可为空")
@ApiModelProperty(value = "参与用户信息") @Schema(description = "参与用户信息")
private List<WebrtcUserInfo> userInfos; private List<WebrtcUserInfo> userInfos;
} }

11
im-platform/src/main/java/com/bx/implatform/dto/WebrtcGroupJoinDTO.java

@ -1,23 +1,20 @@
package com.bx.implatform.dto; package com.bx.implatform.dto;
import io.swagger.annotations.ApiModel; import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.annotations.ApiModelProperty; import jakarta.validation.constraints.NotNull;
import lombok.Data; import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
/** /**
* @author: Blue * @author: Blue
* @date: 2024-06-01 * @date: 2024-06-01
* @version: 1.0 * @version: 1.0
*/ */
@Data @Data
@ApiModel("进入群视频通话DTO") @Schema(description = "进入群视频通话DTO")
public class WebrtcGroupJoinDTO { public class WebrtcGroupJoinDTO {
@NotNull(message = "群聊id不可为空") @NotNull(message = "群聊id不可为空")
@ApiModelProperty(value = "群聊id") @Schema(description = "群聊id")
private Long groupId; private Long groupId;
} }

16
im-platform/src/main/java/com/bx/implatform/dto/WebrtcGroupOfferDTO.java

@ -1,31 +1,29 @@
package com.bx.implatform.dto; package com.bx.implatform.dto;
import io.swagger.annotations.ApiModel; import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.annotations.ApiModelProperty; import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data; import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
/** /**
* @author: Blue * @author: Blue
* @date: 2024-06-01 * @date: 2024-06-01
* @version: 1.0 * @version: 1.0
*/ */
@Data @Data
@ApiModel("回复用户连接请求DTO") @Schema(description = "回复用户连接请求DTO")
public class WebrtcGroupOfferDTO { public class WebrtcGroupOfferDTO {
@NotNull(message = "群聊id不可为空") @NotNull(message = "群聊id不可为空")
@ApiModelProperty(value = "群聊id") @Schema(description = "群聊id")
private Long groupId; private Long groupId;
@NotNull(message = "用户id不可为空") @NotNull(message = "用户id不可为空")
@ApiModelProperty(value = "用户id,代表回复谁的连接请求") @Schema(description = "用户id,代表回复谁的连接请求")
private Long userId; private Long userId;
@NotEmpty(message = "offer不可为空") @NotEmpty(message = "offer不可为空")
@ApiModelProperty(value = "用户offer信息") @Schema(description = "用户offer信息")
private String offer; private String offer;
} }

13
im-platform/src/main/java/com/bx/implatform/dto/WebrtcGroupSetupDTO.java

@ -1,12 +1,11 @@
package com.bx.implatform.dto; package com.bx.implatform.dto;
import com.bx.implatform.session.WebrtcUserInfo; import com.bx.implatform.session.WebrtcUserInfo;
import io.swagger.annotations.ApiModel; import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.annotations.ApiModelProperty; import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data; import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.List; import java.util.List;
/** /**
@ -15,15 +14,15 @@ import java.util.List;
* @version: 1.0 * @version: 1.0
*/ */
@Data @Data
@ApiModel("发起群视频通话DTO") @Schema(description = "发起群视频通话DTO")
public class WebrtcGroupSetupDTO { public class WebrtcGroupSetupDTO {
@NotNull(message = "群聊id不可为空") @NotNull(message = "群聊id不可为空")
@ApiModelProperty(value = "群聊id") @Schema(description = "群聊id")
private Long groupId; private Long groupId;
@NotEmpty(message = "参与用户信息不可为空") @NotEmpty(message = "参与用户信息不可为空")
@ApiModelProperty(value = "参与用户信息") @Schema(description = "参与用户信息")
private List<WebrtcUserInfo> userInfos; private List<WebrtcUserInfo> userInfos;
} }

22
im-platform/src/main/java/com/bx/implatform/entity/Friend.java

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

41
im-platform/src/main/java/com/bx/implatform/entity/Group.java

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

46
im-platform/src/main/java/com/bx/implatform/entity/GroupMember.java

@ -1,14 +1,12 @@
package com.bx.implatform.entity; package com.bx.implatform.entity;
import com.baomidou.mybatisplus.annotation.IdType; import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.activerecord.Model; import com.baomidou.mybatisplus.extension.activerecord.Model;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.util.Date; import java.util.Date;
/** /**
@ -24,67 +22,59 @@ import java.util.Date;
@TableName("im_group_member") @TableName("im_group_member")
public class GroupMember extends Model<GroupMember> { public class GroupMember extends Model<GroupMember> {
private static final long serialVersionUID = 1L;
/** /**
* id * id
*/ */
@TableId(value = "id", type = IdType.AUTO) @TableId
private Long id; private Long id;
/** /**
* 群id * 群id
*/ */
@TableField("group_id")
private Long groupId; private Long groupId;
/** /**
* 用户id * 用户id
*/ */
@TableField("user_id")
private Long userId; private Long userId;
/** /**
* 群内显示名 * 用户昵
*/ */
@TableField("alias_name") private String userNickName;
private String aliasName;
/** /**
* 头像 * 显示昵称备注
*/ */
@TableField("head_image") private String remarkNickName;
private String headImage;
/** /**
* 备注 * 用户头像
*/ */
@TableField("remark") private String headImage;
private String remark;
/** /**
* 是否已离开群聊 * 显示群名备注
*/ */
@TableField("quit") private String remarkGroupName;
private Boolean quit;
/** /**
* 退群时间 * 是否已退出
*/ */
@TableField("quit_time") private Boolean quit;
private Date quitTime;
/** /**
* 创建时间 * 创建时间
*/ */
@TableField("created_time")
private Date createdTime; private Date createdTime;
/**
* 退出时间
*/
private Date quitTime;
@Override public String getShowNickName() {
protected Serializable pkVal() { return StrUtil.isEmpty(remarkNickName) ? userNickName : remarkNickName;
return this.id;
} }
} }

30
im-platform/src/main/java/com/bx/implatform/entity/GroupMessage.java

@ -1,14 +1,9 @@
package com.bx.implatform.entity; 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.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.util.Date; import java.util.Date;
/** /**
@ -20,87 +15,68 @@ import java.util.Date;
* @since 2022-10-31 * @since 2022-10-31
*/ */
@Data @Data
@EqualsAndHashCode(callSuper = false)
@TableName("im_group_message")
public class GroupMessage extends Model<GroupMessage> {
private static final long serialVersionUID = 1L; @TableName("im_group_message")
public class GroupMessage {
/** /**
* id * id
*/ */
@TableId(value = "id", type = IdType.AUTO) @TableId
private Long id; private Long id;
/** /**
* 群id * 群id
*/ */
@TableField("group_id")
private Long groupId; private Long groupId;
/** /**
* 发送用户id * 发送用户id
*/ */
@TableField("send_id")
private Long sendId; private Long sendId;
/** /**
* 发送用户昵称 * 发送用户昵称
*/ */
@TableField("send_nick_name")
private String sendNickName; private String sendNickName;
/** /**
* 接受用户id,为空表示全体发送 * 接受用户id,为空表示全体发送
*/ */
@TableField("recv_ids")
private String recvIds; private String recvIds;
/** /**
* @用户列表 * @用户列表
*/ */
@TableField("at_user_ids")
private String atUserIds; private String atUserIds;
/** /**
* 发送内容 * 发送内容
*/ */
@TableField("content")
private String content; private String content;
/** /**
* 消息类型 MessageType * 消息类型 MessageType
*/ */
@TableField("type")
private Integer type; private Integer type;
/** /**
* 是否回执消息 * 是否回执消息
*/ */
@TableField("receipt")
private Boolean receipt; private Boolean receipt;
/** /**
* 回执消息是否完成 * 回执消息是否完成
*/ */
@TableField("receipt_ok")
private Boolean receiptOk; private Boolean receiptOk;
/** /**
* 状态 MessageStatus * 状态 MessageStatus
*/ */
@TableField("status")
private Integer status; private Integer status;
/** /**
* 发送时间 * 发送时间
*/ */
@TableField("send_time")
private Date sendTime; private Date sendTime;
@Override
protected Serializable pkVal() {
return this.id;
}
} }

25
im-platform/src/main/java/com/bx/implatform/entity/PrivateMessage.java

@ -1,14 +1,8 @@
package com.bx.implatform.entity; 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.annotation.TableName;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.util.Date; import java.util.Date;
/** /**
@ -20,59 +14,44 @@ import java.util.Date;
* @since 2022-10-01 * @since 2022-10-01
*/ */
@Data @Data
@EqualsAndHashCode(callSuper = false)
@TableName("im_private_message") @TableName("im_private_message")
public class PrivateMessage extends Model<PrivateMessage> { public class PrivateMessage {
private static final long serialVersionUID = 1L;
/** /**
* id * id
*/ */
@TableId(value = "id", type = IdType.AUTO)
private Long id; private Long id;
/** /**
* 发送用户id * 发送用户id
*/ */
@TableField("send_id")
private Long sendId; private Long sendId;
/** /**
* 接收用户id * 接收用户id
*/ */
@TableField("recv_id")
private Long recvId; private Long recvId;
/** /**
* 发送内容 * 发送内容
*/ */
@TableField("content")
private String content; private String content;
/** /**
* 消息类型 0:文字 1:图片 2:文件 3:语音 10:撤回消息 * 消息类型 MessageType
*/ */
@TableField("type")
private Integer type; private Integer type;
/** /**
* 状态 * 状态
*/ */
@TableField("status")
private Integer status; private Integer status;
/** /**
* 发送时间 * 发送时间
*/ */
@TableField("send_time")
private Date sendTime; private Date sendTime;
@Override
protected Serializable pkVal() {
return this.id;
}
} }

50
im-platform/src/main/java/com/bx/implatform/entity/SensitiveWord.java

@ -0,0 +1,50 @@
package com.bx.implatform.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDateTime;
/**
* 敏感词
*
* @author Blue
* @since 1.0.0 2024-07-20
*/
@Data
@TableName("im_sensitive_word")
public class SensitiveWord {
/**
* id
*/
@TableId
private Long id;
/**
* 敏感词内容
*/
private String content;
/**
* 是否启用
*/
private Boolean enabled;
/**
* 创建者
*/
@TableField(fill = FieldFill.INSERT)
private Long creator;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
}

61
im-platform/src/main/java/com/bx/implatform/entity/User.java

@ -1,14 +1,9 @@
package com.bx.implatform.entity; 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.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import lombok.Data; import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.util.Date; import java.util.Date;
/** /**
@ -20,81 +15,73 @@ import java.util.Date;
* @since 2022-10-01 * @since 2022-10-01
*/ */
@Data @Data
@EqualsAndHashCode(callSuper = false)
@TableName("im_user") @TableName("im_user")
public class User extends Model<User> { public class User {
private static final long serialVersionUID = 1L;
/** /**
* id * id
*/ */
@TableId(value = "id", type = IdType.AUTO) @TableId
private Long id; private Long id;
/** /**
* 用户名 * 用户名
*/ */
@TableField("user_name")
private String userName; private String userName;
/** /**
* 用户 * 用户昵称
*/ */
@TableField("nick_name")
private String nickName; private String nickName;
/** /**
* 性别 * 用户头像
*/ */
@TableField("sex") private String headImage;
private Integer sex;
/** /**
* 头像 * 用户头像缩略图
*/ */
@TableField("head_image") private String headImageThumb;
private String headImage;
/** /**
* 头像缩略图 * 密码(明文)
*/ */
@TableField("head_image_thumb") private String password;
private String headImageThumb;
/** /**
* 用户类型 1:普通用户 2:审核专用账户 * 性别 0: 1::
*/ */
@TableField("type") private Integer sex;
private Integer type;
/** /**
* 个性签名 * 个性签名
*/ */
@TableField("signature")
private String signature; private String signature;
/** /**
* 密码(明文) * 账号是否被封禁
*/ */
@TableField("password") private Boolean isBanned;
private String password;
/**
* 账号被封禁原因
*/
private String reason;
/** /**
* 最后登录时间 * 最后登录时间
*/ */
@TableField("last_login_time")
private Date lastLoginTime; private Date lastLoginTime;
/** /**
* 创建时间 * 创建时间(注册时间)
*/ */
@TableField("created_time")
private Date createdTime; private Date createdTime;
/**
@Override * 账号类型 1:普通用户 2:wx小程序审核账户
protected Serializable pkVal() { */
return this.id; private Integer type;
}
} }

59
im-platform/src/main/java/com/bx/implatform/enums/MessageType.java

@ -9,6 +9,7 @@ import lombok.AllArgsConstructor;
* 20-29: 提示类消息: 在会话中间显示的提示 * 20-29: 提示类消息: 在会话中间显示的提示
* 30-39: UI交互类消息: 显示加载状态等 * 30-39: UI交互类消息: 显示加载状态等
* 40-49: 操作交互类消息: 语音通话视频通话消息等 * 40-49: 操作交互类消息: 语音通话视频通话消息等
* 50-60: 后台操作类消息: 用户封禁群组封禁等
* 100-199: 单人语音通话rtc信令 * 100-199: 单人语音通话rtc信令
* 200-299: 多人语音通话rtc信令 * 200-299: 多人语音通话rtc信令
* *
@ -16,62 +17,22 @@ import lombok.AllArgsConstructor;
@AllArgsConstructor @AllArgsConstructor
public enum MessageType { public enum MessageType {
/** TEXT(0, "文字消息"),
* 文字 IMAGE(1, "图片消息"),
*/ FILE(2, "文件消息"),
TEXT(0, "文字"), AUDIO(3, "语音消息"),
/** VIDEO(4, "视频消息"),
* 图片
*/
IMAGE(1, "图片"),
/**
* 文件
*/
FILE(2, "文件"),
/**
* 音频
*/
AUDIO(3, "音频"),
/**
* 视频
*/
VIDEO(4, "视频"),
/**
* 撤回
*/
RECALL(10, "撤回"), RECALL(10, "撤回"),
/**
* 已读
*/
READED(11, "已读"), READED(11, "已读"),
/**
* 消息已读回执
*/
RECEIPT(12, "消息已读回执"), RECEIPT(12, "消息已读回执"),
/**
* 时间提示
*/
TIP_TIME(20,"时间提示"), TIP_TIME(20,"时间提示"),
/**
* 文字提示
*/
TIP_TEXT(21,"文字提示"), TIP_TEXT(21,"文字提示"),
/** LOADING(30,"加载中标记"),
* 消息加载标记
*/
LOADING(30,"加载中"),
/**
* 语音通话提示
*/
ACT_RT_VOICE(40,"语音通话"), ACT_RT_VOICE(40,"语音通话"),
/**
* 视频通话提示
*/
ACT_RT_VIDEO(41,"视频通话"), ACT_RT_VIDEO(41,"视频通话"),
USER_BANNED(50,"用户封禁"),
GROUP_BANNED(51,"群聊封禁"),
GROUP_UNBAN(52,"群聊解封"),
RTC_CALL_VOICE(100, "语音呼叫"), RTC_CALL_VOICE(100, "语音呼叫"),
RTC_CALL_VIDEO(101, "视频呼叫"), RTC_CALL_VIDEO(101, "视频呼叫"),
RTC_ACCEPT(102, "接受"), RTC_ACCEPT(102, "接受"),

6
im-platform/src/main/java/com/bx/implatform/filter/CacheFilter.java

@ -1,11 +1,11 @@
package com.bx.implatform.filter; package com.bx.implatform.filter;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.boot.web.servlet.ServletComponentScan; import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException; import java.io.IOException;
@Component @Component

8
im-platform/src/main/java/com/bx/implatform/filter/CacheHttpServletRequestWrapper.java

@ -1,11 +1,11 @@
package com.bx.implatform.filter; package com.bx.implatform.filter;
import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import org.apache.commons.compress.utils.IOUtils; import org.apache.commons.compress.utils.IOUtils;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*; import java.io.*;
public class CacheHttpServletRequestWrapper extends HttpServletRequestWrapper { public class CacheHttpServletRequestWrapper extends HttpServletRequestWrapper {

102
im-platform/src/main/java/com/bx/implatform/generator/CodeGenerator.java

@ -1,102 +0,0 @@
package com.bx.implatform.generator;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.VelocityTemplateEngine;
import java.util.ArrayList;
import java.util.List;
public class CodeGenerator {
public static void main(String[] args) {
// 代码生成器
AutoGenerator mpg = new AutoGenerator();
// 全局配置
GlobalConfig gc = new GlobalConfig();
//生成的代码输出路径,自己根据需要修改
String projectPath = "d:\\work\\project\\code";
gc.setOutputDir(projectPath + "/src/main/java");
gc.setAuthor("blue");
gc.setOpen(false);
gc.setFileOverride(true);
gc.setActiveRecord(true);
gc.setBaseColumnList(true);
gc.setBaseResultMap(true);
gc.setIdType(IdType.AUTO);
gc.setDateType(DateType.ONLY_DATE);
mpg.setGlobalConfig(gc);
// 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql://localhost:3306/box-im?useUnicode=true&characterEncoding=utf-8");
dsc.setDriverName("com.mysql.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("root");
mpg.setDataSource(dsc);
// 包配置
PackageConfig pc = new PackageConfig();
pc.setModuleName("");
pc.setParent("com.bx");
mpg.setPackageInfo(pc);
// 如果模板引擎是 velocity
String templatePath = "/templates/mapper.xml.vm";
// 自定义输出配置
List<FileOutConfig> focList = new ArrayList<>();
// 自定义配置会被优先输出
focList.add(new FileOutConfig(templatePath) {
@Override
public String outputFile(TableInfo tableInfo) {
// 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
return projectPath + "/src/main/resources/mapper/"
+ tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
}
});
// 自定义配置
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
// to do nothing
}
};
cfg.setFileOutConfigList(focList);
mpg.setCfg(cfg);
// 配置模板
TemplateConfig templateConfig = new TemplateConfig();
templateConfig.setXml(null);
mpg.setTemplate(templateConfig);
// 策略配置
StrategyConfig strategy = new StrategyConfig();
// 下划线转驼峰
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setEntityTableFieldAnnotationEnable(true);
strategy.setVersionFieldName("version");
//逻辑删除的字段
strategy.setLogicDeleteFieldName("deleted");
strategy.setEntityLombokModel(true);
strategy.setRestControllerStyle(true);
//多张表的时候直接在代码中写表名
strategy.setInclude("friends");
strategy.setTablePrefix("");
mpg.setStrategy(strategy);
mpg.setTemplateEngine(new VelocityTemplateEngine());
mpg.execute();
}
}

7
im-platform/src/main/java/com/bx/implatform/interceptor/AuthInterceptor.java

@ -7,6 +7,8 @@ import com.bx.implatform.config.JwtProperties;
import com.bx.implatform.enums.ResultCode; import com.bx.implatform.enums.ResultCode;
import com.bx.implatform.exception.GlobalException; import com.bx.implatform.exception.GlobalException;
import com.bx.implatform.session.UserSession; import com.bx.implatform.session.UserSession;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -14,9 +16,6 @@ import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod; import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Slf4j @Slf4j
@Component @Component
@AllArgsConstructor @AllArgsConstructor
@ -38,7 +37,7 @@ public class AuthInterceptor implements HandlerInterceptor {
} }
String strJson = JwtUtil.getInfo(token); String strJson = JwtUtil.getInfo(token);
UserSession userSession = JSON.parseObject(strJson, UserSession.class); UserSession userSession = JSON.parseObject(strJson, UserSession.class);
//验证 token // 验证 token
if (!JwtUtil.checkSign(token, jwtProperties.getAccessTokenSecret())) { if (!JwtUtil.checkSign(token, jwtProperties.getAccessTokenSecret())) {
log.error("token已失效,用户:{}", userSession.getUserName()); log.error("token已失效,用户:{}", userSession.getUserName());
log.error("token:{}", token); log.error("token:{}", token);

4
im-platform/src/main/java/com/bx/implatform/interceptor/XssInterceptor.java

@ -3,14 +3,14 @@ package com.bx.implatform.interceptor;
import com.bx.implatform.enums.ResultCode; import com.bx.implatform.enums.ResultCode;
import com.bx.implatform.exception.GlobalException; import com.bx.implatform.exception.GlobalException;
import com.bx.implatform.util.XssUtil; import com.bx.implatform.util.XssUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.util.Map; import java.util.Map;

2
im-platform/src/main/java/com/bx/implatform/listener/GroupMessageListener.java

@ -5,9 +5,7 @@ import com.bx.imclient.listener.MessageListener;
import com.bx.imcommon.enums.IMListenerType; import com.bx.imcommon.enums.IMListenerType;
import com.bx.imcommon.enums.IMSendCode; import com.bx.imcommon.enums.IMSendCode;
import com.bx.imcommon.model.IMSendResult; import com.bx.imcommon.model.IMSendResult;
import com.bx.implatform.contant.RedisKey;
import com.bx.implatform.vo.GroupMessageVO; import com.bx.implatform.vo.GroupMessageVO;
import com.bx.implatform.vo.PrivateMessageVO;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.RedisTemplate;

4
im-platform/src/main/java/com/bx/implatform/listener/PrivateMessageListener.java

@ -9,7 +9,7 @@ import com.bx.imcommon.enums.IMSendCode;
import com.bx.imcommon.model.IMSendResult; import com.bx.imcommon.model.IMSendResult;
import com.bx.implatform.entity.PrivateMessage; import com.bx.implatform.entity.PrivateMessage;
import com.bx.implatform.enums.MessageStatus; import com.bx.implatform.enums.MessageStatus;
import com.bx.implatform.service.IPrivateMessageService; import com.bx.implatform.service.PrivateMessageService;
import com.bx.implatform.vo.PrivateMessageVO; import com.bx.implatform.vo.PrivateMessageVO;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -24,7 +24,7 @@ import java.util.Set;
public class PrivateMessageListener implements MessageListener<PrivateMessageVO> { public class PrivateMessageListener implements MessageListener<PrivateMessageVO> {
@Lazy @Lazy
@Autowired @Autowired
private IPrivateMessageService privateMessageService; private PrivateMessageService privateMessageService;
@Override @Override
public void process(List<IMSendResult<PrivateMessageVO>> results) { public void process(List<IMSendResult<PrivateMessageVO>> results) {
Set<Long> messageIds = new HashSet<>(); Set<Long> messageIds = new HashSet<>();

26
im-platform/src/main/java/com/bx/implatform/listener/SystemMessageListener.java

@ -0,0 +1,26 @@
package com.bx.implatform.listener;
import com.bx.imclient.annotation.IMListener;
import com.bx.imclient.listener.MessageListener;
import com.bx.imcommon.enums.IMListenerType;
import com.bx.imcommon.enums.IMSendCode;
import com.bx.imcommon.model.IMSendResult;
import com.bx.implatform.vo.SystemMessageVO;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
@Slf4j
@IMListener(type = IMListenerType.SYSTEM_MESSAGE)
public class SystemMessageListener implements MessageListener<SystemMessageVO> {
@Override
public void process(List<IMSendResult<SystemMessageVO>> results) {
for(IMSendResult<SystemMessageVO> result : results){
SystemMessageVO messageInfo = result.getData();
if (result.getCode().equals(IMSendCode.SUCCESS.code())) {
log.info("消息送达,消息id:{},接收者:{},终端:{}", messageInfo.getId(), result.getReceiver().getId(), result.getReceiver().getTerminal());
}
}
}
}

16
im-platform/src/main/java/com/bx/implatform/mapper/SensitiveWordMapper.java

@ -0,0 +1,16 @@
package com.bx.implatform.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.bx.implatform.entity.SensitiveWord;
import org.apache.ibatis.annotations.Mapper;
/**
* 敏感词
*
* @author Blue
* @since 1.0.0 2024-07-20
*/
@Mapper
public interface SensitiveWordMapper extends BaseMapper<SensitiveWord> {
}

2
im-platform/src/main/java/com/bx/implatform/service/IFriendService.java → im-platform/src/main/java/com/bx/implatform/service/FriendService.java

@ -6,7 +6,7 @@ import com.bx.implatform.vo.FriendVO;
import java.util.List; import java.util.List;
public interface IFriendService extends IService<Friend> { public interface FriendService extends IService<Friend> {
/** /**
* 判断用户2是否用户1的好友 * 判断用户2是否用户1的好友

2
im-platform/src/main/java/com/bx/implatform/service/IGroupMemberService.java → im-platform/src/main/java/com/bx/implatform/service/GroupMemberService.java

@ -5,7 +5,7 @@ import com.bx.implatform.entity.GroupMember;
import java.util.List; import java.util.List;
public interface IGroupMemberService extends IService<GroupMember> { public interface GroupMemberService extends IService<GroupMember> {
/** /**
* 根据群聊id和用户id查询群聊成员 * 根据群聊id和用户id查询群聊成员

4
im-platform/src/main/java/com/bx/implatform/service/IGroupMessageService.java → im-platform/src/main/java/com/bx/implatform/service/GroupMessageService.java

@ -7,7 +7,7 @@ import com.bx.implatform.vo.GroupMessageVO;
import java.util.List; import java.util.List;
public interface IGroupMessageService extends IService<GroupMessage> { public interface GroupMessageService extends IService<GroupMessage> {
/** /**
* 发送群聊消息(高并发接口查询mysql接口都要进行缓存) * 发送群聊消息(高并发接口查询mysql接口都要进行缓存)
@ -15,7 +15,7 @@ public interface IGroupMessageService extends IService<GroupMessage> {
* @param dto 群聊消息 * @param dto 群聊消息
* @return 群聊id * @return 群聊id
*/ */
Long sendMessage(GroupMessageDTO dto); GroupMessageVO sendMessage(GroupMessageDTO dto);
/** /**
* 撤回消息 * 撤回消息

4
im-platform/src/main/java/com/bx/implatform/service/IGroupService.java → im-platform/src/main/java/com/bx/implatform/service/GroupService.java

@ -8,7 +8,7 @@ import com.bx.implatform.vo.GroupVO;
import java.util.List; import java.util.List;
public interface IGroupService extends IService<Group> { public interface GroupService extends IService<Group> {
/** /**
* 创建新群聊 * 创建新群聊
@ -68,7 +68,7 @@ public interface IGroupService extends IService<Group> {
* @param groupId 群聊id * @param groupId 群聊id
* @return 群聊实体 * @return 群聊实体
*/ */
Group getById(Long groupId); Group getAndCheckById(Long groupId);
/** /**
* 根据id查找群聊 * 根据id查找群聊

4
im-platform/src/main/java/com/bx/implatform/service/IPrivateMessageService.java → im-platform/src/main/java/com/bx/implatform/service/PrivateMessageService.java

@ -7,7 +7,7 @@ import com.bx.implatform.vo.PrivateMessageVO;
import java.util.List; import java.util.List;
public interface IPrivateMessageService extends IService<PrivateMessage> { public interface PrivateMessageService extends IService<PrivateMessage> {
/** /**
* 发送私聊消息(高并发接口查询mysql接口都要进行缓存) * 发送私聊消息(高并发接口查询mysql接口都要进行缓存)
@ -15,7 +15,7 @@ public interface IPrivateMessageService extends IService<PrivateMessage> {
* @param dto 私聊消息 * @param dto 私聊消息
* @return 消息id * @return 消息id
*/ */
Long sendMessage(PrivateMessageDTO dto); PrivateMessageVO sendMessage(PrivateMessageDTO dto);
/** /**

22
im-platform/src/main/java/com/bx/implatform/service/SensitiveWordService.java

@ -0,0 +1,22 @@
package com.bx.implatform.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.bx.implatform.dto.LoginDTO;
import com.bx.implatform.dto.ModifyPwdDTO;
import com.bx.implatform.dto.RegisterDTO;
import com.bx.implatform.entity.SensitiveWord;
import com.bx.implatform.entity.User;
import com.bx.implatform.vo.LoginVO;
import com.bx.implatform.vo.OnlineTerminalVO;
import com.bx.implatform.vo.UserVO;
import java.util.List;
public interface SensitiveWordService extends IService<SensitiveWord> {
/**
* 查询所有开启的敏感词
* @return
*/
List<String> findAllEnabledWords();
}

2
im-platform/src/main/java/com/bx/implatform/service/IUserService.java → im-platform/src/main/java/com/bx/implatform/service/UserService.java

@ -11,7 +11,7 @@ import com.bx.implatform.vo.UserVO;
import java.util.List; import java.util.List;
public interface IUserService extends IService<User> { public interface UserService extends IService<User> {
/** /**
* 用户登录 * 用户登录

3
im-platform/src/main/java/com/bx/implatform/service/IWebrtcGroupService.java → im-platform/src/main/java/com/bx/implatform/service/WebrtcGroupService.java

@ -1,10 +1,9 @@
package com.bx.implatform.service; package com.bx.implatform.service;
import com.bx.implatform.config.WebrtcConfig;
import com.bx.implatform.dto.*; import com.bx.implatform.dto.*;
import com.bx.implatform.vo.WebrtcGroupInfoVO; import com.bx.implatform.vo.WebrtcGroupInfoVO;
public interface IWebrtcGroupService { public interface WebrtcGroupService {
/** /**
* 发起通话 * 发起通话

6
im-platform/src/main/java/com/bx/implatform/service/IWebrtcPrivateService.java → im-platform/src/main/java/com/bx/implatform/service/WebrtcPrivateService.java

@ -1,15 +1,11 @@
package com.bx.implatform.service; package com.bx.implatform.service;
import com.bx.implatform.config.ICEServer;
import java.util.List;
/** /**
* webrtc 通信服务 * webrtc 通信服务
* *
* @author * @author
*/ */
public interface IWebrtcPrivateService { public interface WebrtcPrivateService {
void call(Long uid, String mode,String offer); void call(Long uid, String mode,String offer);

18
im-platform/src/main/java/com/bx/implatform/service/impl/FriendServiceImpl.java

@ -7,11 +7,10 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bx.implatform.contant.RedisKey; import com.bx.implatform.contant.RedisKey;
import com.bx.implatform.entity.Friend; import com.bx.implatform.entity.Friend;
import com.bx.implatform.entity.User; import com.bx.implatform.entity.User;
import com.bx.implatform.enums.ResultCode;
import com.bx.implatform.exception.GlobalException; import com.bx.implatform.exception.GlobalException;
import com.bx.implatform.mapper.FriendMapper; import com.bx.implatform.mapper.FriendMapper;
import com.bx.implatform.mapper.UserMapper; import com.bx.implatform.mapper.UserMapper;
import com.bx.implatform.service.IFriendService; import com.bx.implatform.service.FriendService;
import com.bx.implatform.session.SessionContext; import com.bx.implatform.session.SessionContext;
import com.bx.implatform.session.UserSession; import com.bx.implatform.session.UserSession;
import com.bx.implatform.vo.FriendVO; import com.bx.implatform.vo.FriendVO;
@ -25,12 +24,13 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.List; import java.util.List;
import java.util.Objects;
@Slf4j @Slf4j
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@CacheConfig(cacheNames = RedisKey.IM_CACHE_FRIEND) @CacheConfig(cacheNames = RedisKey.IM_CACHE_FRIEND)
public class FriendServiceImpl extends ServiceImpl<FriendMapper, Friend> implements IFriendService { public class FriendServiceImpl extends ServiceImpl<FriendMapper, Friend> implements FriendService {
private final UserMapper userMapper; private final UserMapper userMapper;
@ -47,7 +47,7 @@ public class FriendServiceImpl extends ServiceImpl<FriendMapper, Friend> impleme
public void addFriend(Long friendId) { public void addFriend(Long friendId) {
long userId = SessionContext.getSession().getUserId(); long userId = SessionContext.getSession().getUserId();
if (friendId.equals(userId)) { if (friendId.equals(userId)) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "不允许添加自己为好友"); throw new GlobalException("不允许添加自己为好友");
} }
// 互相绑定好友关系 // 互相绑定好友关系
FriendServiceImpl proxy = (FriendServiceImpl) AopContext.currentProxy(); FriendServiceImpl proxy = (FriendServiceImpl) AopContext.currentProxy();
@ -87,12 +87,10 @@ public class FriendServiceImpl extends ServiceImpl<FriendMapper, Friend> impleme
queryWrapper.lambda() queryWrapper.lambda()
.eq(Friend::getUserId, userId) .eq(Friend::getUserId, userId)
.eq(Friend::getFriendId, vo.getId()); .eq(Friend::getFriendId, vo.getId());
Friend f = this.getOne(queryWrapper); Friend f = this.getOne(queryWrapper);
if (f == null) { if (Objects.isNull(f)) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "对方不是您的好友"); throw new GlobalException("对方不是您的好友");
} }
f.setFriendHeadImage(vo.getHeadImage()); f.setFriendHeadImage(vo.getHeadImage());
f.setFriendNickName(vo.getNickName()); f.setFriendNickName(vo.getNickName());
this.updateById(f); this.updateById(f);
@ -148,8 +146,8 @@ public class FriendServiceImpl extends ServiceImpl<FriendMapper, Friend> impleme
.eq(Friend::getUserId, session.getUserId()) .eq(Friend::getUserId, session.getUserId())
.eq(Friend::getFriendId, friendId); .eq(Friend::getFriendId, friendId);
Friend friend = this.getOne(wrapper); Friend friend = this.getOne(wrapper);
if (friend == null) { if (Objects.isNull(friend)) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "对方不是您的好友"); throw new GlobalException("对方不是您的好友");
} }
FriendVO vo = new FriendVO(); FriendVO vo = new FriendVO();
vo.setId(friend.getFriendId()); vo.setId(friend.getFriendId());

4
im-platform/src/main/java/com/bx/implatform/service/impl/GroupMemberServiceImpl.java

@ -9,7 +9,7 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bx.implatform.contant.RedisKey; import com.bx.implatform.contant.RedisKey;
import com.bx.implatform.entity.GroupMember; import com.bx.implatform.entity.GroupMember;
import com.bx.implatform.mapper.GroupMemberMapper; import com.bx.implatform.mapper.GroupMemberMapper;
import com.bx.implatform.service.IGroupMemberService; import com.bx.implatform.service.GroupMemberService;
import com.bx.implatform.util.DateTimeUtils; import com.bx.implatform.util.DateTimeUtils;
import org.springframework.cache.annotation.CacheConfig; import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CacheEvict;
@ -22,7 +22,7 @@ import java.util.stream.Collectors;
@Service @Service
@CacheConfig(cacheNames = RedisKey.IM_CACHE_GROUP_MEMBER_ID) @CacheConfig(cacheNames = RedisKey.IM_CACHE_GROUP_MEMBER_ID)
public class GroupMemberServiceImpl extends ServiceImpl<GroupMemberMapper, GroupMember> implements IGroupMemberService { public class GroupMemberServiceImpl extends ServiceImpl<GroupMemberMapper, GroupMember> implements GroupMemberService {
@CacheEvict(key = "#member.getGroupId()") @CacheEvict(key = "#member.getGroupId()")
@Override @Override
public boolean save(GroupMember member) { public boolean save(GroupMember member) {

61
im-platform/src/main/java/com/bx/implatform/service/impl/GroupMessageServiceImpl.java

@ -21,12 +21,11 @@ import com.bx.implatform.entity.GroupMember;
import com.bx.implatform.entity.GroupMessage; import com.bx.implatform.entity.GroupMessage;
import com.bx.implatform.enums.MessageStatus; import com.bx.implatform.enums.MessageStatus;
import com.bx.implatform.enums.MessageType; import com.bx.implatform.enums.MessageType;
import com.bx.implatform.enums.ResultCode;
import com.bx.implatform.exception.GlobalException; import com.bx.implatform.exception.GlobalException;
import com.bx.implatform.mapper.GroupMessageMapper; import com.bx.implatform.mapper.GroupMessageMapper;
import com.bx.implatform.service.IGroupMemberService; import com.bx.implatform.service.GroupMemberService;
import com.bx.implatform.service.IGroupMessageService; import com.bx.implatform.service.GroupMessageService;
import com.bx.implatform.service.IGroupService; import com.bx.implatform.service.GroupService;
import com.bx.implatform.session.SessionContext; import com.bx.implatform.session.SessionContext;
import com.bx.implatform.session.UserSession; import com.bx.implatform.session.UserSession;
import com.bx.implatform.util.BeanUtils; import com.bx.implatform.util.BeanUtils;
@ -47,27 +46,22 @@ import java.util.stream.Collectors;
@Slf4j @Slf4j
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, GroupMessage> implements IGroupMessageService { public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, GroupMessage> implements
private final IGroupService groupService; GroupMessageService {
private final IGroupMemberService groupMemberService; private final GroupService groupService;
private final GroupMemberService groupMemberService;
private final RedisTemplate<String, Object> redisTemplate; private final RedisTemplate<String, Object> redisTemplate;
private final IMClient imClient; private final IMClient imClient;
private final SensitiveFilterUtil sensitiveFilterUtil; private final SensitiveFilterUtil sensitiveFilterUtil;
@Override @Override
public Long sendMessage(GroupMessageDTO dto) { public GroupMessageVO sendMessage(GroupMessageDTO dto) {
UserSession session = SessionContext.getSession(); UserSession session = SessionContext.getSession();
Group group = groupService.getById(dto.getGroupId()); Group group = groupService.getAndCheckById(dto.getGroupId());
if (Objects.isNull(group)) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "群聊不存在");
}
if (Boolean.TRUE.equals(group.getDeleted())) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "群聊已解散");
}
// 是否在群聊里面 // 是否在群聊里面
GroupMember member = groupMemberService.findByGroupAndUserId(dto.getGroupId(), session.getUserId()); GroupMember member = groupMemberService.findByGroupAndUserId(dto.getGroupId(), session.getUserId());
if (Objects.isNull(member) || member.getQuit()) { if (Objects.isNull(member) || member.getQuit()) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "您已不在群聊里面,无法发送消息"); throw new GlobalException("您已不在群聊里面,无法发送消息");
} }
// 群聊成员列表 // 群聊成员列表
List<Long> userIds = groupMemberService.findUserIdsByGroupId(group.getId()); List<Long> userIds = groupMemberService.findUserIdsByGroupId(group.getId());
@ -77,14 +71,15 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
GroupMessage msg = BeanUtils.copyProperties(dto, GroupMessage.class); GroupMessage msg = BeanUtils.copyProperties(dto, GroupMessage.class);
msg.setSendId(session.getUserId()); msg.setSendId(session.getUserId());
msg.setSendTime(new Date()); msg.setSendTime(new Date());
msg.setSendNickName(member.getAliasName()); msg.setSendNickName(member.getShowNickName());
if (CollectionUtil.isNotEmpty(dto.getAtUserIds())) { if (CollectionUtil.isNotEmpty(dto.getAtUserIds())) {
msg.setAtUserIds(StrUtil.join(",", dto.getAtUserIds())); msg.setAtUserIds(StrUtil.join(",", dto.getAtUserIds()));
} }
this.save(msg); this.save(msg);
// 过滤消息内容 // 过滤内容中的敏感词
String content = sensitiveFilterUtil.filter(dto.getContent()); if(MessageType.TEXT.code().equals(dto.getType())){
msg.setContent(content); msg.setContent(sensitiveFilterUtil.filter(dto.getContent()));
}
// 群发 // 群发
GroupMessageVO msgInfo = BeanUtils.copyProperties(msg, GroupMessageVO.class); GroupMessageVO msgInfo = BeanUtils.copyProperties(msg, GroupMessageVO.class);
msgInfo.setAtUserIds(dto.getAtUserIds()); msgInfo.setAtUserIds(dto.getAtUserIds());
@ -95,7 +90,7 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
sendMessage.setData(msgInfo); sendMessage.setData(msgInfo);
imClient.sendGroupMessage(sendMessage); imClient.sendGroupMessage(sendMessage);
log.info("发送群聊消息,发送id:{},群聊id:{},内容:{}", session.getUserId(), dto.getGroupId(), dto.getContent()); log.info("发送群聊消息,发送id:{},群聊id:{},内容:{}", session.getUserId(), dto.getGroupId(), dto.getContent());
return msg.getId(); return msgInfo;
} }
@Override @Override
@ -103,18 +98,18 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
UserSession session = SessionContext.getSession(); UserSession session = SessionContext.getSession();
GroupMessage msg = this.getById(id); GroupMessage msg = this.getById(id);
if (Objects.isNull(msg)) { if (Objects.isNull(msg)) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "消息不存在"); throw new GlobalException("消息不存在");
} }
if (!msg.getSendId().equals(session.getUserId())) { if (!msg.getSendId().equals(session.getUserId())) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "这条消息不是由您发送,无法撤回"); throw new GlobalException("这条消息不是由您发送,无法撤回");
} }
if (System.currentTimeMillis() - msg.getSendTime().getTime() > IMConstant.ALLOW_RECALL_SECOND * 1000) { if (System.currentTimeMillis() - msg.getSendTime().getTime() > IMConstant.ALLOW_RECALL_SECOND * 1000) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "消息已发送超过5分钟,无法撤回"); throw new GlobalException("消息已发送超过5分钟,无法撤回");
} }
// 判断是否在群里 // 判断是否在群里
GroupMember member = groupMemberService.findByGroupAndUserId(msg.getGroupId(), session.getUserId()); GroupMember member = groupMemberService.findByGroupAndUserId(msg.getGroupId(), session.getUserId());
if (Objects.isNull(member) || Boolean.TRUE.equals(member.getQuit())) { if (Objects.isNull(member) || Boolean.TRUE.equals(member.getQuit())) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "您已不在群聊里面,无法撤回消息"); throw new GlobalException("您已不在群聊里面,无法撤回消息");
} }
// 修改数据库 // 修改数据库
msg.setStatus(MessageStatus.RECALL.code()); msg.setStatus(MessageStatus.RECALL.code());
@ -125,7 +120,7 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
userIds = userIds.stream().filter(uid -> !session.getUserId().equals(uid)).collect(Collectors.toList()); userIds = userIds.stream().filter(uid -> !session.getUserId().equals(uid)).collect(Collectors.toList());
GroupMessageVO msgInfo = BeanUtils.copyProperties(msg, GroupMessageVO.class); GroupMessageVO msgInfo = BeanUtils.copyProperties(msg, GroupMessageVO.class);
msgInfo.setType(MessageType.RECALL.code()); msgInfo.setType(MessageType.RECALL.code());
String content = String.format("'%s'撤回了一条消息", member.getAliasName()); String content = String.format("'%s'撤回了一条消息", member.getShowNickName());
msgInfo.setContent(content); msgInfo.setContent(content);
msgInfo.setSendTime(new Date()); msgInfo.setSendTime(new Date());
@ -151,7 +146,7 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
public void pullOfflineMessage(Long minId) { public void pullOfflineMessage(Long minId) {
UserSession session = SessionContext.getSession(); UserSession session = SessionContext.getSession();
if(!imClient.isOnline(session.getUserId())){ if(!imClient.isOnline(session.getUserId())){
throw new GlobalException(ResultCode.PROGRAM_ERROR, "网络连接失败,无法拉取离线消息"); throw new GlobalException("网络连接失败,无法拉取离线消息");
} }
// 查询用户加入的群组 // 查询用户加入的群组
List<GroupMember> members = groupMemberService.findByUserId(session.getUserId()); List<GroupMember> members = groupMemberService.findByUserId(session.getUserId());
@ -164,14 +159,14 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
} }
// 开启加载中标志 // 开启加载中标志
this.sendLoadingMessage(true); this.sendLoadingMessage(true);
// 只能拉取最近1个月的,最多拉取1000条 // 只能拉取最近3个月的,最多拉取3000条
Date minDate = DateUtils.addMonths(new Date(), -1); Date minDate = DateUtils.addMonths(new Date(), -3);
LambdaQueryWrapper<GroupMessage> wrapper = Wrappers.lambdaQuery(); LambdaQueryWrapper<GroupMessage> wrapper = Wrappers.lambdaQuery();
wrapper.gt(GroupMessage::getId, minId) wrapper.gt(GroupMessage::getId, minId)
.gt(GroupMessage::getSendTime, minDate) .gt(GroupMessage::getSendTime, minDate)
.in(GroupMessage::getGroupId, groupIds) .in(GroupMessage::getGroupId, groupIds)
.ne(GroupMessage::getStatus, MessageStatus.RECALL.code()) .ne(GroupMessage::getStatus, MessageStatus.RECALL.code())
.orderByDesc(GroupMessage::getId).last("limit 1000"); .orderByDesc(GroupMessage::getId).last("limit 3000");
List<GroupMessage> messages = this.list(wrapper); List<GroupMessage> messages = this.list(wrapper);
// 通过群聊对消息进行分组 // 通过群聊对消息进行分组
Map<Long, List<GroupMessage>> messageGroupMap = messages.stream().collect(Collectors.groupingBy(GroupMessage::getGroupId)); Map<Long, List<GroupMessage>> messageGroupMap = messages.stream().collect(Collectors.groupingBy(GroupMessage::getGroupId));
@ -315,12 +310,12 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
UserSession session = SessionContext.getSession(); UserSession session = SessionContext.getSession();
GroupMessage message = this.getById(messageId); GroupMessage message = this.getById(messageId);
if (Objects.isNull(message)) { if (Objects.isNull(message)) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "消息不存在"); throw new GlobalException("消息不存在");
} }
// 是否在群聊里面 // 是否在群聊里面
GroupMember member = groupMemberService.findByGroupAndUserId(groupId, session.getUserId()); GroupMember member = groupMemberService.findByGroupAndUserId(groupId, session.getUserId());
if (Objects.isNull(member) || member.getQuit()) { if (Objects.isNull(member) || member.getQuit()) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "您已不在群聊里面"); throw new GlobalException("您已不在群聊里面");
} }
// 已读位置key // 已读位置key
String key = StrUtil.join(":", RedisKey.IM_GROUP_READED_POSITION, groupId); String key = StrUtil.join(":", RedisKey.IM_GROUP_READED_POSITION, groupId);
@ -339,7 +334,7 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
// 群聊成员信息 // 群聊成员信息
GroupMember member = groupMemberService.findByGroupAndUserId(groupId, userId); GroupMember member = groupMemberService.findByGroupAndUserId(groupId, userId);
if (Objects.isNull(member) || member.getQuit()) { if (Objects.isNull(member) || member.getQuit()) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "您已不在群聊中"); throw new GlobalException("您已不在群聊中");
} }
// 查询聊天记录,只查询加入群聊时间之后的消息 // 查询聊天记录,只查询加入群聊时间之后的消息
QueryWrapper<GroupMessage> wrapper = new QueryWrapper<>(); QueryWrapper<GroupMessage> wrapper = new QueryWrapper<>();

143
im-platform/src/main/java/com/bx/implatform/service/impl/GroupServiceImpl.java

@ -14,11 +14,13 @@ import com.bx.implatform.contant.RedisKey;
import com.bx.implatform.entity.*; import com.bx.implatform.entity.*;
import com.bx.implatform.enums.MessageStatus; import com.bx.implatform.enums.MessageStatus;
import com.bx.implatform.enums.MessageType; import com.bx.implatform.enums.MessageType;
import com.bx.implatform.enums.ResultCode;
import com.bx.implatform.exception.GlobalException; import com.bx.implatform.exception.GlobalException;
import com.bx.implatform.mapper.GroupMapper; import com.bx.implatform.mapper.GroupMapper;
import com.bx.implatform.mapper.GroupMessageMapper; import com.bx.implatform.mapper.GroupMessageMapper;
import com.bx.implatform.service.*; import com.bx.implatform.service.FriendService;
import com.bx.implatform.service.GroupMemberService;
import com.bx.implatform.service.GroupService;
import com.bx.implatform.service.UserService;
import com.bx.implatform.session.SessionContext; import com.bx.implatform.session.SessionContext;
import com.bx.implatform.session.UserSession; import com.bx.implatform.session.UserSession;
import com.bx.implatform.util.BeanUtils; import com.bx.implatform.util.BeanUtils;
@ -28,7 +30,6 @@ import com.bx.implatform.vo.GroupMessageVO;
import com.bx.implatform.vo.GroupVO; import com.bx.implatform.vo.GroupVO;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cache.annotation.CacheConfig; import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Cacheable;
@ -40,14 +41,14 @@ import java.util.*;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Slf4j @Slf4j
@CacheConfig(cacheNames = RedisKey.IM_CACHE_GROUP)
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements IGroupService { @CacheConfig(cacheNames = RedisKey.IM_CACHE_GROUP)
private final IUserService userService; public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements GroupService {
private final IGroupMemberService groupMemberService; private final UserService userService;
private final GroupMemberService groupMemberService;
private final GroupMessageMapper groupMessageMapper; private final GroupMessageMapper groupMessageMapper;
private final IFriendService friendsService; private final FriendService friendsService;
private final IMClient imClient; private final IMClient imClient;
private final RedisTemplate<String, Object> redisTemplate; private final RedisTemplate<String, Object> redisTemplate;
@ -60,17 +61,17 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
group.setOwnerId(user.getId()); group.setOwnerId(user.getId());
this.save(group); this.save(group);
// 把群主加入群 // 把群主加入群
GroupMember groupMember = new GroupMember(); GroupMember member = new GroupMember();
groupMember.setGroupId(group.getId()); member.setGroupId(group.getId());
groupMember.setUserId(user.getId()); member.setUserId(user.getId());
groupMember.setHeadImage(user.getHeadImageThumb()); member.setHeadImage(user.getHeadImageThumb());
groupMember.setAliasName(StringUtils.isEmpty(vo.getAliasName()) ? session.getNickName() : vo.getAliasName()); member.setRemarkNickName(vo.getRemarkNickName());
groupMember.setRemark(StringUtils.isEmpty(vo.getRemark()) ? group.getName() : vo.getRemark()); member.setRemarkGroupName(vo.getRemarkGroupName());
groupMemberService.save(groupMember); groupMemberService.save(member);
// 返回
vo.setId(group.getId()); vo.setId(group.getId());
vo.setAliasName(groupMember.getAliasName()); vo.setShowNickName(StrUtil.isEmpty(vo.getRemarkNickName()) ? member.getUserNickName() : vo.getRemarkNickName());
vo.setRemark(groupMember.getRemark()); vo.setShowGroupName(StrUtil.isEmpty(vo.getRemarkGroupName()) ? group.getName() : vo.getRemarkGroupName());
log.info("创建群聊,群聊id:{},群聊名称:{}", group.getId(), group.getName()); log.info("创建群聊,群聊id:{},群聊名称:{}", group.getId(), group.getName());
return vo; return vo;
} }
@ -81,20 +82,22 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
public GroupVO modifyGroup(GroupVO vo) { public GroupVO modifyGroup(GroupVO vo) {
UserSession session = SessionContext.getSession(); UserSession session = SessionContext.getSession();
// 校验是不是群主,只有群主能改信息 // 校验是不是群主,只有群主能改信息
Group group = this.getById(vo.getId()); Group group = this.getAndCheckById(vo.getId());
// 群主有权修改群基本信息
if (group.getOwnerId().equals(session.getUserId())) {
group = BeanUtils.copyProperties(vo, Group.class);
this.updateById(group);
}
// 更新成员信息 // 更新成员信息
GroupMember member = groupMemberService.findByGroupAndUserId(vo.getId(), session.getUserId()); GroupMember member = groupMemberService.findByGroupAndUserId(vo.getId(), session.getUserId());
if (Objects.isNull(member) || member.getQuit()) { if (Objects.isNull(member) || member.getQuit()) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "您不是群聊的成员"); throw new GlobalException("您不是群聊的成员");
} }
member.setAliasName(StringUtils.isEmpty(vo.getAliasName()) ? session.getNickName() : vo.getAliasName()); member.setRemarkNickName(vo.getRemarkNickName());
member.setRemark(StringUtils.isEmpty(vo.getRemark()) ? Objects.requireNonNull(group).getName() : vo.getRemark()); member.setRemarkGroupName(vo.getRemarkGroupName());
groupMemberService.updateById(member); groupMemberService.updateById(member);
// 群主有权修改群基本信息
if (group.getOwnerId().equals(session.getUserId())) {
group = BeanUtils.copyProperties(vo, Group.class);
this.updateById(group);
}
vo.setShowNickName(StrUtil.isEmpty(vo.getRemarkNickName()) ? member.getUserNickName() : vo.getRemarkNickName());
vo.setShowGroupName(StrUtil.isEmpty(vo.getRemarkGroupName()) ? group.getName() : vo.getRemarkGroupName());
log.info("修改群聊,群聊id:{},群聊名称:{}", group.getId(), group.getName()); log.info("修改群聊,群聊id:{},群聊名称:{}", group.getId(), group.getName());
return vo; return vo;
} }
@ -106,7 +109,7 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
UserSession session = SessionContext.getSession(); UserSession session = SessionContext.getSession();
Group group = this.getById(groupId); Group group = this.getById(groupId);
if (!group.getOwnerId().equals(session.getUserId())) { if (!group.getOwnerId().equals(session.getUserId())) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "只有群主才有权限解除群聊"); throw new GlobalException("只有群主才有权限解除群聊");
} }
// 群聊用户id // 群聊用户id
List<Long> userIds = groupMemberService.findUserIdsByGroupId(groupId); List<Long> userIds = groupMemberService.findUserIdsByGroupId(groupId);
@ -119,7 +122,7 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
String key = StrUtil.join(":", RedisKey.IM_GROUP_READED_POSITION, groupId); String key = StrUtil.join(":", RedisKey.IM_GROUP_READED_POSITION, groupId);
redisTemplate.delete(key); redisTemplate.delete(key);
// 推送解散群聊提示 // 推送解散群聊提示
this.sendTipMessage(groupId,userIds,String.format("'%s'解散了群聊",session.getNickName())); this.sendTipMessage(groupId, userIds, String.format("'%s'解散了群聊", session.getNickName()));
log.info("删除群聊,群聊id:{},群聊名称:{}", group.getId(), group.getName()); log.info("删除群聊,群聊id:{},群聊名称:{}", group.getId(), group.getName());
} }
@ -128,35 +131,35 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
Long userId = SessionContext.getSession().getUserId(); Long userId = SessionContext.getSession().getUserId();
Group group = this.getById(groupId); Group group = this.getById(groupId);
if (group.getOwnerId().equals(userId)) { if (group.getOwnerId().equals(userId)) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "您是群主,不可退出群聊"); throw new GlobalException("您是群主,不可退出群聊");
} }
// 删除群聊成员 // 删除群聊成员
groupMemberService.removeByGroupAndUserId(groupId, userId); groupMemberService.removeByGroupAndUserId(groupId, userId);
// 清理已读缓存 // 清理已读缓存
String key = StrUtil.join(":", RedisKey.IM_GROUP_READED_POSITION, groupId); String key = StrUtil.join(":", RedisKey.IM_GROUP_READED_POSITION, groupId);
redisTemplate.opsForHash().delete(key,userId.toString()); redisTemplate.opsForHash().delete(key, userId.toString());
// 推送退出群聊提示 // 推送退出群聊提示
this.sendTipMessage(groupId,Arrays.asList(userId),"您已退出群聊"); this.sendTipMessage(groupId, Arrays.asList(userId), "您已退出群聊");
log.info("退出群聊,群聊id:{},群聊名称:{},用户id:{}", group.getId(), group.getName(), userId); log.info("退出群聊,群聊id:{},群聊名称:{},用户id:{}", group.getId(), group.getName(), userId);
} }
@Override @Override
public void kickGroup(Long groupId, Long userId) { public void kickGroup(Long groupId, Long userId) {
UserSession session = SessionContext.getSession(); UserSession session = SessionContext.getSession();
Group group = this.getById(groupId); Group group = this.getAndCheckById(groupId);
if (!group.getOwnerId().equals(session.getUserId())) { if (!group.getOwnerId().equals(session.getUserId())) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "您不是群主,没有权限踢人"); throw new GlobalException("您不是群主,没有权限踢人");
} }
if (userId.equals(session.getUserId())) { if (userId.equals(session.getUserId())) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "亲,不能移除自己哟"); throw new GlobalException("亲,不能移除自己哟");
} }
// 删除群聊成员 // 删除群聊成员
groupMemberService.removeByGroupAndUserId(groupId, userId); groupMemberService.removeByGroupAndUserId(groupId, userId);
// 清理已读缓存 // 清理已读缓存
String key = StrUtil.join(":", RedisKey.IM_GROUP_READED_POSITION, groupId); String key = StrUtil.join(":", RedisKey.IM_GROUP_READED_POSITION, groupId);
redisTemplate.opsForHash().delete(key,userId.toString()); redisTemplate.opsForHash().delete(key, userId.toString());
// 推送踢出群聊提示 // 推送踢出群聊提示
this.sendTipMessage(groupId,Arrays.asList(userId),"您已被移出群聊"); this.sendTipMessage(groupId, Arrays.asList(userId), "您已被移出群聊");
log.info("踢出群聊,群聊id:{},群聊名称:{},用户id:{}", group.getId(), group.getName(), userId); log.info("踢出群聊,群聊id:{},群聊名称:{},用户id:{}", group.getId(), group.getName(), userId);
} }
@ -165,28 +168,33 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
UserSession session = SessionContext.getSession(); UserSession session = SessionContext.getSession();
Group group = super.getById(groupId); Group group = super.getById(groupId);
if (Objects.isNull(group)) { if (Objects.isNull(group)) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "群组不存在"); throw new GlobalException("群组不存在");
} }
GroupMember member = groupMemberService.findByGroupAndUserId(groupId, session.getUserId()); GroupMember member = groupMemberService.findByGroupAndUserId(groupId, session.getUserId());
if (Objects.isNull(member)) { if (Objects.isNull(member)) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "您未加入群聊"); throw new GlobalException("您未加入群聊");
} }
GroupVO vo = BeanUtils.copyProperties(group, GroupVO.class); GroupVO vo = BeanUtils.copyProperties(group, GroupVO.class);
vo.setAliasName(member.getAliasName()); vo.setRemarkGroupName(member.getRemarkGroupName());
vo.setRemark(member.getRemark()); vo.setRemarkNickName(member.getRemarkNickName());
vo.setShowNickName(member.getShowNickName());
vo.setShowGroupName(StrUtil.isEmpty(vo.getRemarkGroupName()) ? group.getName() : vo.getRemarkGroupName());
vo.setQuit(member.getQuit()); vo.setQuit(member.getQuit());
return vo; return vo;
} }
@Cacheable(key = "#groupId") @Cacheable(key = "#groupId")
@Override @Override
public Group getById(Long groupId) { public Group getAndCheckById(Long groupId) {
Group group = super.getById(groupId); Group group = super.getById(groupId);
if (Objects.isNull(group)) { if (Objects.isNull(group)) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "群组不存在"); throw new GlobalException("群组不存在");
} }
if (group.getDeleted()) { if (group.getDeleted()) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "群组'" + group.getName() + "'已解散"); throw new GlobalException("群组'" + group.getName() + "'已解散");
}
if (group.getIsBanned()) {
throw new GlobalException("群组'" + group.getName() + "'已被封禁,原因:" + group.getReason());
} }
return group; return group;
} }
@ -210,8 +218,9 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
return groups.stream().map(g -> { return groups.stream().map(g -> {
GroupVO vo = BeanUtils.copyProperties(g, GroupVO.class); GroupVO vo = BeanUtils.copyProperties(g, GroupVO.class);
GroupMember member = groupMembers.stream().filter(m -> g.getId().equals(m.getGroupId())).findFirst().get(); GroupMember member = groupMembers.stream().filter(m -> g.getId().equals(m.getGroupId())).findFirst().get();
vo.setAliasName(member.getAliasName()); vo.setShowNickName(
vo.setRemark(member.getRemark()); StrUtil.isEmpty(vo.getRemarkNickName()) ? session.getNickName() : vo.getRemarkNickName());
vo.setShowGroupName(StrUtil.isEmpty(vo.getRemarkGroupName()) ? g.getName() : vo.getRemarkGroupName());
vo.setQuit(member.getQuit()); vo.setQuit(member.getQuit());
return vo; return vo;
}).collect(Collectors.toList()); }).collect(Collectors.toList());
@ -220,35 +229,33 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
@Override @Override
public void invite(GroupInviteVO vo) { public void invite(GroupInviteVO vo) {
UserSession session = SessionContext.getSession(); UserSession session = SessionContext.getSession();
Group group = this.getById(vo.getGroupId()); Group group = this.getAndCheckById(vo.getGroupId());
if (Objects.isNull(group)) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "群聊不存在");
}
GroupMember member = groupMemberService.findByGroupAndUserId(vo.getGroupId(), session.getUserId()); GroupMember member = groupMemberService.findByGroupAndUserId(vo.getGroupId(), session.getUserId());
if (Objects.isNull(group) || member.getQuit()) { if (Objects.isNull(group) || member.getQuit()) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "您不在群聊中,邀请失败"); throw new GlobalException("您不在群聊中,邀请失败");
} }
// 群聊人数校验 // 群聊人数校验
List<GroupMember> members = groupMemberService.findByGroupId(vo.getGroupId()); List<GroupMember> members = groupMemberService.findByGroupId(vo.getGroupId());
long size = members.stream().filter(m -> !m.getQuit()).count(); long size = members.stream().filter(m -> !m.getQuit()).count();
if (vo.getFriendIds().size() + size > Constant.MAX_GROUP_MEMBER) { if (vo.getFriendIds().size() + size > Constant.MAX_GROUP_MEMBER) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "群聊人数不能大于" + Constant.MAX_GROUP_MEMBER + "人"); throw new GlobalException("群聊人数不能大于" + Constant.MAX_GROUP_MEMBER + "人");
} }
// 找出好友信息 // 找出好友信息
List<Friend> friends = friendsService.findFriendByUserId(session.getUserId()); List<Friend> friends = friendsService.findFriendByUserId(session.getUserId());
List<Friend> friendsList = vo.getFriendIds().stream().map(id -> friends.stream().filter(f -> f.getFriendId().equals(id)).findFirst().get()) List<Friend> friendsList = vo.getFriendIds().stream()
.collect(Collectors.toList()); .map(id -> friends.stream().filter(f -> f.getFriendId().equals(id)).findFirst().get())
.collect(Collectors.toList());
if (friendsList.size() != vo.getFriendIds().size()) { if (friendsList.size() != vo.getFriendIds().size()) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "部分用户不是您的好友,邀请失败"); throw new GlobalException("部分用户不是您的好友,邀请失败");
} }
// 批量保存成员数据 // 批量保存成员数据
List<GroupMember> groupMembers = friendsList.stream().map(f -> { List<GroupMember> groupMembers = friendsList.stream().map(f -> {
Optional<GroupMember> optional = members.stream().filter(m -> m.getUserId().equals(f.getFriendId())).findFirst(); Optional<GroupMember> optional =
members.stream().filter(m -> m.getUserId().equals(f.getFriendId())).findFirst();
GroupMember groupMember = optional.orElseGet(GroupMember::new); GroupMember groupMember = optional.orElseGet(GroupMember::new);
groupMember.setGroupId(vo.getGroupId()); groupMember.setGroupId(vo.getGroupId());
groupMember.setUserId(f.getFriendId()); groupMember.setUserId(f.getFriendId());
groupMember.setAliasName(f.getFriendNickName()); groupMember.setUserNickName(f.getFriendNickName());
groupMember.setRemark(group.getName());
groupMember.setHeadImage(f.getFriendHeadImage()); groupMember.setHeadImage(f.getFriendHeadImage());
groupMember.setCreatedTime(new Date()); groupMember.setCreatedTime(new Date());
groupMember.setQuit(false); groupMember.setQuit(false);
@ -259,25 +266,29 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
} }
// 推送进入群聊消息 // 推送进入群聊消息
List<Long> userIds = groupMemberService.findUserIdsByGroupId(vo.getGroupId()); List<Long> userIds = groupMemberService.findUserIdsByGroupId(vo.getGroupId());
String memberNames = groupMembers.stream().map(GroupMember::getAliasName).collect(Collectors.joining(",")); String memberNames = groupMembers.stream().map(GroupMember::getShowNickName).collect(Collectors.joining(","));
String content = String.format("'%s'邀请'%s'加入了群聊",session.getNickName(), memberNames); String content = String.format("'%s'邀请'%s'加入了群聊", session.getNickName(), memberNames);
this.sendTipMessage(vo.getGroupId(),userIds,content); this.sendTipMessage(vo.getGroupId(), userIds, content);
log.info("邀请进入群聊,群聊id:{},群聊名称:{},被邀请用户id:{}", group.getId(), group.getName(), vo.getFriendIds()); log.info("邀请进入群聊,群聊id:{},群聊名称:{},被邀请用户id:{}", group.getId(), group.getName(),
vo.getFriendIds());
} }
@Override @Override
public List<GroupMemberVO> findGroupMembers(Long groupId) { public List<GroupMemberVO> findGroupMembers(Long groupId) {
Group group = getAndCheckById(groupId);
List<GroupMember> members = groupMemberService.findByGroupId(groupId); List<GroupMember> members = groupMemberService.findByGroupId(groupId);
List<Long> userIds = members.stream().map(GroupMember::getUserId).collect(Collectors.toList()); List<Long> userIds = members.stream().map(GroupMember::getUserId).collect(Collectors.toList());
List<Long> onlineUserIds = imClient.getOnlineUser(userIds); List<Long> onlineUserIds = imClient.getOnlineUser(userIds);
return members.stream().map(m -> { return members.stream().map(m -> {
GroupMemberVO vo = BeanUtils.copyProperties(m, GroupMemberVO.class); GroupMemberVO vo = BeanUtils.copyProperties(m, GroupMemberVO.class);
vo.setShowNickName(m.getShowNickName());
vo.setShowGroupName(StrUtil.isEmpty(m.getRemarkGroupName()) ? group.getName() : m.getRemarkGroupName());
vo.setOnline(onlineUserIds.contains(m.getUserId())); vo.setOnline(onlineUserIds.contains(m.getUserId()));
return vo; return vo;
}).sorted((m1, m2) -> m2.getOnline().compareTo(m1.getOnline())).collect(Collectors.toList()); }).sorted((m1, m2) -> m2.getOnline().compareTo(m1.getOnline())).collect(Collectors.toList());
} }
private void sendTipMessage(Long groupId,List<Long> recvIds,String content){ private void sendTipMessage(Long groupId, List<Long> recvIds, String content) {
UserSession session = SessionContext.getSession(); UserSession session = SessionContext.getSession();
// 消息入库 // 消息入库
GroupMessage message = new GroupMessage(); GroupMessage message = new GroupMessage();
@ -291,14 +302,14 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
message.setRecvIds(CommaTextUtils.asText(recvIds)); message.setRecvIds(CommaTextUtils.asText(recvIds));
groupMessageMapper.insert(message); groupMessageMapper.insert(message);
// 推送 // 推送
GroupMessageVO msgInfo = BeanUtils.copyProperties(message,GroupMessageVO.class); GroupMessageVO msgInfo = BeanUtils.copyProperties(message, GroupMessageVO.class);
IMGroupMessage<GroupMessageVO> sendMessage = new IMGroupMessage<>(); IMGroupMessage<GroupMessageVO> sendMessage = new IMGroupMessage<>();
sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal())); sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal()));
if(CollUtil.isEmpty(recvIds)){ if (CollUtil.isEmpty(recvIds)) {
// 为空表示向全体发送 // 为空表示向全体发送
List<Long> userIds = groupMemberService.findUserIdsByGroupId(groupId); List<Long> userIds = groupMemberService.findUserIdsByGroupId(groupId);
sendMessage.setRecvIds(userIds); sendMessage.setRecvIds(userIds);
}else{ } else {
sendMessage.setRecvIds(recvIds); sendMessage.setRecvIds(recvIds);
} }
sendMessage.setData(msgInfo); sendMessage.setData(msgInfo);

37
im-platform/src/main/java/com/bx/implatform/service/impl/PrivateMessageServiceImpl.java

@ -16,11 +16,10 @@ import com.bx.implatform.entity.Friend;
import com.bx.implatform.entity.PrivateMessage; import com.bx.implatform.entity.PrivateMessage;
import com.bx.implatform.enums.MessageStatus; import com.bx.implatform.enums.MessageStatus;
import com.bx.implatform.enums.MessageType; import com.bx.implatform.enums.MessageType;
import com.bx.implatform.enums.ResultCode;
import com.bx.implatform.exception.GlobalException; import com.bx.implatform.exception.GlobalException;
import com.bx.implatform.mapper.PrivateMessageMapper; import com.bx.implatform.mapper.PrivateMessageMapper;
import com.bx.implatform.service.IFriendService; import com.bx.implatform.service.FriendService;
import com.bx.implatform.service.IPrivateMessageService; import com.bx.implatform.service.PrivateMessageService;
import com.bx.implatform.session.SessionContext; import com.bx.implatform.session.SessionContext;
import com.bx.implatform.session.UserSession; import com.bx.implatform.session.UserSession;
import com.bx.implatform.util.BeanUtils; import com.bx.implatform.util.BeanUtils;
@ -38,18 +37,19 @@ import java.util.stream.Collectors;
@Slf4j @Slf4j
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper, PrivateMessage> implements IPrivateMessageService { public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper, PrivateMessage> implements
PrivateMessageService {
private final IFriendService friendService; private final FriendService friendService;
private final IMClient imClient; private final IMClient imClient;
private final SensitiveFilterUtil sensitiveFilterUtil; private final SensitiveFilterUtil sensitiveFilterUtil;
@Override @Override
public Long sendMessage(PrivateMessageDTO dto) { public PrivateMessageVO sendMessage(PrivateMessageDTO dto) {
UserSession session = SessionContext.getSession(); UserSession session = SessionContext.getSession();
Boolean isFriends = friendService.isFriend(session.getUserId(), dto.getRecvId()); Boolean isFriends = friendService.isFriend(session.getUserId(), dto.getRecvId());
if (Boolean.FALSE.equals(isFriends)) { if (Boolean.FALSE.equals(isFriends)) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "您已不是对方好友,无法发送消息"); throw new GlobalException("您已不是对方好友,无法发送消息");
} }
// 保存消息 // 保存消息
PrivateMessage msg = BeanUtils.copyProperties(dto, PrivateMessage.class); PrivateMessage msg = BeanUtils.copyProperties(dto, PrivateMessage.class);
@ -57,9 +57,10 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
msg.setStatus(MessageStatus.UNSEND.code()); msg.setStatus(MessageStatus.UNSEND.code());
msg.setSendTime(new Date()); msg.setSendTime(new Date());
this.save(msg); this.save(msg);
// 过滤消息内容 // 过滤内容中的敏感词
String content = sensitiveFilterUtil.filter(dto.getContent()); if(MessageType.TEXT.code().equals(dto.getType())){
msg.setContent(content); msg.setContent(sensitiveFilterUtil.filter(dto.getContent()));
}
// 推送消息 // 推送消息
PrivateMessageVO msgInfo = BeanUtils.copyProperties(msg, PrivateMessageVO.class); PrivateMessageVO msgInfo = BeanUtils.copyProperties(msg, PrivateMessageVO.class);
IMPrivateMessage<PrivateMessageVO> sendMessage = new IMPrivateMessage<>(); IMPrivateMessage<PrivateMessageVO> sendMessage = new IMPrivateMessage<>();
@ -70,7 +71,7 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
sendMessage.setSendResult(true); sendMessage.setSendResult(true);
imClient.sendPrivateMessage(sendMessage); imClient.sendPrivateMessage(sendMessage);
log.info("发送私聊消息,发送id:{},接收id:{},内容:{}", session.getUserId(), dto.getRecvId(), dto.getContent()); log.info("发送私聊消息,发送id:{},接收id:{},内容:{}", session.getUserId(), dto.getRecvId(), dto.getContent());
return msg.getId(); return msgInfo;
} }
@Override @Override
@ -78,13 +79,13 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
UserSession session = SessionContext.getSession(); UserSession session = SessionContext.getSession();
PrivateMessage msg = this.getById(id); PrivateMessage msg = this.getById(id);
if (Objects.isNull(msg)) { if (Objects.isNull(msg)) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "消息不存在"); throw new GlobalException("消息不存在");
} }
if (!msg.getSendId().equals(session.getUserId())) { if (!msg.getSendId().equals(session.getUserId())) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "这条消息不是由您发送,无法撤回"); throw new GlobalException("这条消息不是由您发送,无法撤回");
} }
if (System.currentTimeMillis() - msg.getSendTime().getTime() > IMConstant.ALLOW_RECALL_SECOND * 1000) { if (System.currentTimeMillis() - msg.getSendTime().getTime() > IMConstant.ALLOW_RECALL_SECOND * 1000) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "消息已发送超过5分钟,无法撤回"); throw new GlobalException("消息已发送超过5分钟,无法撤回");
} }
// 修改消息状态 // 修改消息状态
msg.setStatus(MessageStatus.RECALL.code()); msg.setStatus(MessageStatus.RECALL.code());
@ -138,7 +139,7 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
public void pullOfflineMessage(Long minId) { public void pullOfflineMessage(Long minId) {
UserSession session = SessionContext.getSession(); UserSession session = SessionContext.getSession();
if(!imClient.isOnline(session.getUserId())){ if(!imClient.isOnline(session.getUserId())){
throw new GlobalException(ResultCode.PROGRAM_ERROR, "网络连接失败,无法拉取离线消息"); throw new GlobalException("网络连接失败,无法拉取离线消息");
} }
// 查询用户好友列表 // 查询用户好友列表
List<Friend> friends = friendService.findFriendByUserId(session.getUserId()); List<Friend> friends = friendService.findFriendByUserId(session.getUserId());
@ -152,8 +153,8 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
List<Long> friendIds = friends.stream().map(Friend::getFriendId).collect(Collectors.toList()); List<Long> friendIds = friends.stream().map(Friend::getFriendId).collect(Collectors.toList());
// 获取当前用户的消息 // 获取当前用户的消息
LambdaQueryWrapper<PrivateMessage> queryWrapper = Wrappers.lambdaQuery(); LambdaQueryWrapper<PrivateMessage> queryWrapper = Wrappers.lambdaQuery();
// 只能拉取最近1个月的1000条消息 // 只能拉取最近3个月的3000条消息
Date minDate = DateUtils.addMonths(new Date(), -1); Date minDate = DateUtils.addMonths(new Date(), -3);
queryWrapper.gt(PrivateMessage::getId, minId) queryWrapper.gt(PrivateMessage::getId, minId)
.ge(PrivateMessage::getSendTime, minDate) .ge(PrivateMessage::getSendTime, minDate)
.ne(PrivateMessage::getStatus, MessageStatus.RECALL.code()) .ne(PrivateMessage::getStatus, MessageStatus.RECALL.code())
@ -163,7 +164,7 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
.or(wp -> wp.eq(PrivateMessage::getRecvId, session.getUserId()) .or(wp -> wp.eq(PrivateMessage::getRecvId, session.getUserId())
.in(PrivateMessage::getSendId, friendIds))) .in(PrivateMessage::getSendId, friendIds)))
.orderByDesc(PrivateMessage::getId) .orderByDesc(PrivateMessage::getId)
.last("limit 1000"); .last("limit 3000");
List<PrivateMessage> messages = this.list(queryWrapper); List<PrivateMessage> messages = this.list(queryWrapper);
// 消息顺序从小到大 // 消息顺序从小到大
CollectionUtil.reverse(messages); CollectionUtil.reverse(messages);

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save