Browse Source

!21 2.0版本上线

Merge pull request !21 from blue/v_2.0.0
master
blue 2 years ago
committed by Gitee
parent
commit
26fa63eb75
No known key found for this signature in database GPG Key ID: 173E9B9CA92EEF8F
  1. 1
      .gitignore
  2. 21
      im-client/src/main/java/com/bx/imclient/IMClient.java
  3. 24
      im-client/src/main/java/com/bx/imclient/config/RedisConfig.java
  4. 6
      im-client/src/main/java/com/bx/imclient/listener/MessageListener.java
  5. 19
      im-client/src/main/java/com/bx/imclient/listener/MessageListenerMulticaster.java
  6. 159
      im-client/src/main/java/com/bx/imclient/sender/IMSender.java
  7. 3
      im-client/src/main/java/com/bx/imclient/task/AbstractPullMessageTask.java
  8. 12
      im-client/src/main/java/com/bx/imclient/task/PullSendResultGroupMessageTask.java
  9. 12
      im-client/src/main/java/com/bx/imclient/task/PullSendResultPrivateMessageTask.java
  10. 3
      im-commom/src/main/java/com/bx/imcommon/contant/IMConstant.java
  11. 9
      im-commom/src/main/java/com/bx/imcommon/contant/IMRedisKey.java
  12. 1
      im-commom/src/main/java/com/bx/imcommon/enums/IMCmdType.java
  13. 12
      im-commom/src/main/java/com/bx/imcommon/enums/IMSendCode.java
  14. 42
      im-commom/src/main/java/com/bx/imcommon/enums/IMTerminalType.java
  15. 42
      im-commom/src/main/java/com/bx/imcommon/model/GroupMessageInfo.java
  16. 43
      im-commom/src/main/java/com/bx/imcommon/model/IMGroupMessage.java
  17. 2
      im-commom/src/main/java/com/bx/imcommon/model/IMHeartbeatInfo.java
  18. 2
      im-commom/src/main/java/com/bx/imcommon/model/IMLoginInfo.java
  19. 44
      im-commom/src/main/java/com/bx/imcommon/model/IMPrivateMessage.java
  20. 20
      im-commom/src/main/java/com/bx/imcommon/model/IMRecvInfo.java
  21. 28
      im-commom/src/main/java/com/bx/imcommon/model/IMSendResult.java
  22. 18
      im-commom/src/main/java/com/bx/imcommon/model/IMSessionInfo.java
  23. 30
      im-commom/src/main/java/com/bx/imcommon/model/IMUserInfo.java
  24. 42
      im-commom/src/main/java/com/bx/imcommon/model/PrivateMessageInfo.java
  25. 24
      im-commom/src/main/java/com/bx/imcommon/model/SendResult.java
  26. 4
      im-platform/src/main/java/com/bx/implatform/IMPlatformApp.java
  27. 3
      im-platform/src/main/java/com/bx/implatform/config/MinIoClientConfig.java
  28. 4
      im-platform/src/main/java/com/bx/implatform/config/MvcConfig.java
  29. 6
      im-platform/src/main/java/com/bx/implatform/config/RedisConfig.java
  30. 8
      im-platform/src/main/java/com/bx/implatform/contant/Constant.java
  31. 4
      im-platform/src/main/java/com/bx/implatform/contant/RedisKey.java
  32. 2
      im-platform/src/main/java/com/bx/implatform/controller/FriendController.java
  33. 4
      im-platform/src/main/java/com/bx/implatform/controller/GroupController.java
  34. 8
      im-platform/src/main/java/com/bx/implatform/controller/GroupMessageController.java
  35. 9
      im-platform/src/main/java/com/bx/implatform/controller/LoginController.java
  36. 8
      im-platform/src/main/java/com/bx/implatform/controller/PrivateMessageController.java
  37. 14
      im-platform/src/main/java/com/bx/implatform/controller/UserController.java
  38. 79
      im-platform/src/main/java/com/bx/implatform/controller/WebrtcController.java
  39. 28
      im-platform/src/main/java/com/bx/implatform/dto/GroupMessageDTO.java
  40. 15
      im-platform/src/main/java/com/bx/implatform/dto/LoginDTO.java
  41. 21
      im-platform/src/main/java/com/bx/implatform/dto/ModifyPwdDTO.java
  42. 31
      im-platform/src/main/java/com/bx/implatform/dto/PrivateMessageDTO.java
  43. 2
      im-platform/src/main/java/com/bx/implatform/dto/RegisterDTO.java
  44. 5
      im-platform/src/main/java/com/bx/implatform/entity/User.java
  45. 13
      im-platform/src/main/java/com/bx/implatform/enums/FileType.java
  46. 14
      im-platform/src/main/java/com/bx/implatform/enums/MessageStatus.java
  47. 14
      im-platform/src/main/java/com/bx/implatform/enums/MessageType.java
  48. 1
      im-platform/src/main/java/com/bx/implatform/enums/ResultCode.java
  49. 2
      im-platform/src/main/java/com/bx/implatform/exception/GlobalExceptionHandler.java
  50. 1
      im-platform/src/main/java/com/bx/implatform/filter/CacheFilter.java
  51. 8
      im-platform/src/main/java/com/bx/implatform/filter/CacheHttpServletRequestWrapper.java
  52. 1
      im-platform/src/main/java/com/bx/implatform/interceptor/AuthInterceptor.java
  53. 2
      im-platform/src/main/java/com/bx/implatform/interceptor/XssInterceptor.java
  54. 20
      im-platform/src/main/java/com/bx/implatform/listener/GroupMessageListener.java
  55. 41
      im-platform/src/main/java/com/bx/implatform/listener/PrivateMessageListener.java
  56. 2
      im-platform/src/main/java/com/bx/implatform/service/IFriendService.java
  57. 1
      im-platform/src/main/java/com/bx/implatform/service/IGroupMemberService.java
  58. 8
      im-platform/src/main/java/com/bx/implatform/service/IGroupMessageService.java
  59. 4
      im-platform/src/main/java/com/bx/implatform/service/IGroupService.java
  60. 8
      im-platform/src/main/java/com/bx/implatform/service/IPrivateMessageService.java
  61. 7
      im-platform/src/main/java/com/bx/implatform/service/IUserService.java
  62. 32
      im-platform/src/main/java/com/bx/implatform/service/IWebrtcService.java
  63. 14
      im-platform/src/main/java/com/bx/implatform/service/impl/FriendServiceImpl.java
  64. 26
      im-platform/src/main/java/com/bx/implatform/service/impl/GroupMemberServiceImpl.java
  65. 123
      im-platform/src/main/java/com/bx/implatform/service/impl/GroupMessageServiceImpl.java
  66. 86
      im-platform/src/main/java/com/bx/implatform/service/impl/GroupServiceImpl.java
  67. 104
      im-platform/src/main/java/com/bx/implatform/service/impl/PrivateMessageServiceImpl.java
  68. 74
      im-platform/src/main/java/com/bx/implatform/service/impl/UserServiceImpl.java
  69. 245
      im-platform/src/main/java/com/bx/implatform/service/impl/WebrtcServiceImpl.java
  70. 4
      im-platform/src/main/java/com/bx/implatform/service/thirdparty/FileService.java
  71. 13
      im-platform/src/main/java/com/bx/implatform/session/UserSession.java
  72. 32
      im-platform/src/main/java/com/bx/implatform/session/WebrtcSession.java
  73. 1081
      im-platform/src/main/java/com/bx/implatform/util/DateTimeUtils.java
  74. 40
      im-platform/src/main/java/com/bx/implatform/vo/GroupMessageVO.java
  75. 3
      im-platform/src/main/java/com/bx/implatform/vo/GroupVO.java
  76. 1
      im-platform/src/main/java/com/bx/implatform/vo/LoginVO.java
  77. 41
      im-platform/src/main/java/com/bx/implatform/vo/PrivateMessageVO.java
  78. 6
      im-platform/src/main/java/com/bx/implatform/vo/UserVO.java
  79. 7
      im-platform/src/main/resources/db/db.sql
  80. 1
      im-server/src/main/java/com/bx/imserver/IMServerApp.java
  81. 23
      im-server/src/main/java/com/bx/imserver/config/RedisConfig.java
  82. 12
      im-server/src/main/java/com/bx/imserver/constant/ChannelAttrKey.java
  83. 25
      im-server/src/main/java/com/bx/imserver/netty/IMChannelHandler.java
  84. 4
      im-server/src/main/java/com/bx/imserver/netty/IMServerGroup.java
  85. 30
      im-server/src/main/java/com/bx/imserver/netty/UserChannelCtxMap.java
  86. 2
      im-server/src/main/java/com/bx/imserver/netty/processor/AbstractMessageProcessor.java
  87. 62
      im-server/src/main/java/com/bx/imserver/netty/processor/GroupMessageProcessor.java
  88. 33
      im-server/src/main/java/com/bx/imserver/netty/processor/HeartbeatProcessor.java
  89. 48
      im-server/src/main/java/com/bx/imserver/netty/processor/LoginProcessor.java
  90. 57
      im-server/src/main/java/com/bx/imserver/netty/processor/PrivateMessageProcessor.java
  91. 12
      im-server/src/main/java/com/bx/imserver/netty/processor/ProcessorFactory.java
  92. 2
      im-server/src/main/java/com/bx/imserver/netty/tcp/TcpSocketServer.java
  93. 2
      im-server/src/main/java/com/bx/imserver/netty/ws/WebSocketServer.java
  94. 4
      im-server/src/main/java/com/bx/imserver/task/AbstractPullMessageTask.java
  95. 28
      im-server/src/main/java/com/bx/imserver/task/PullUnreadGroupMessageTask.java
  96. 25
      im-server/src/main/java/com/bx/imserver/task/PullUnreadPrivateMessageTask.java
  97. BIN
      im-ui/public/favicon.ico
  98. 2
      im-ui/public/index.html
  99. BIN
      im-ui/public/logo.png
  100. 6
      im-ui/src/api/enums.js

1
.gitignore

@ -7,3 +7,4 @@
/im-server/src/main/resources/application-prod.yml
/im-server/src/main/resources/logback-prod.xml
/im-commom/im-commom.iml
/im-uniapp/node_modules/

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

@ -1,13 +1,12 @@
package com.bx.imclient;
import com.bx.imclient.listener.MessageListenerMulticaster;
import com.bx.imclient.sender.IMSender;
import com.bx.imcommon.model.GroupMessageInfo;
import com.bx.imcommon.model.PrivateMessageInfo;
import com.bx.imcommon.model.IMGroupMessage;
import com.bx.imcommon.model.IMPrivateMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import java.util.List;
@Configuration
public class IMClient {
@ -27,21 +26,19 @@ public class IMClient {
/**
* 发送私聊消息发送结果通过MessageListener接收
*
* @param recvId 接收用户id
* @param messageInfo 消息体将转成json发送到客户端
* @param message 私有消息
*/
public void sendPrivateMessage(Long recvId, PrivateMessageInfo... messageInfo){
imSender.sendPrivateMessage(recvId,messageInfo);
public void sendPrivateMessage(IMPrivateMessage<?> message){
imSender.sendPrivateMessage(message);
}
/**
* 发送群聊消息发送结果通过MessageListener接收
*
* @param recvIds 群聊用户id列表
* @param messageInfo 消息体将转成json发送到客户端
* @param message 群聊消息
*/
public void sendGroupMessage(List<Long> recvIds, GroupMessageInfo... messageInfo){
imSender.sendGroupMessage(recvIds,messageInfo);
public void sendGroupMessage(IMGroupMessage<?> message){
imSender.sendGroupMessage(message);
}

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

@ -1,5 +1,6 @@
package com.bx.imclient.config;
import com.alibaba.fastjson.support.spring.FastJsonRedisSerializer;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
@ -9,6 +10,7 @@ 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.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@ -17,16 +19,13 @@ import javax.annotation.Resource;
@Configuration("IMRedisConfig")
public class RedisConfig {
@Resource
private RedisConnectionFactory factory;
@Bean("IMRedisTemplate")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate();
redisTemplate.setConnectionFactory(redisConnectionFactory);
// 设置值(value)的序列化采用jackson2JsonRedisSerializer
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer());
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer());
// 设置值(value)的序列化采用FastJsonRedisSerializer
redisTemplate.setValueSerializer(fastJsonRedisSerializer());
redisTemplate.setHashValueSerializer(fastJsonRedisSerializer());
// 设置键(key)的序列化采用StringRedisSerializer。
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
@ -34,16 +33,9 @@ public class RedisConfig {
return redisTemplate;
}
@Bean
public Jackson2JsonRedisSerializer jackson2JsonRedisSerializer(){
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 解决jackson2无法反序列化LocalDateTime的问题
om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
jackson2JsonRedisSerializer.setObjectMapper(om);
return jackson2JsonRedisSerializer;
public FastJsonRedisSerializer fastJsonRedisSerializer(){
FastJsonRedisSerializer <Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
return fastJsonRedisSerializer;
}
}

6
im-client/src/main/java/com/bx/imclient/listener/MessageListener.java

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

19
im-client/src/main/java/com/bx/imclient/listener/MessageListenerMulticaster.java

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

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

@ -1,14 +1,12 @@
package com.bx.imclient.sender;
import com.bx.imclient.listener.MessageListenerMulticaster;
import com.bx.imcommon.contant.RedisKey;
import com.bx.imcommon.contant.IMRedisKey;
import com.bx.imcommon.enums.IMCmdType;
import com.bx.imcommon.enums.IMListenerType;
import com.bx.imcommon.enums.IMSendCode;
import com.bx.imcommon.model.GroupMessageInfo;
import com.bx.imcommon.model.IMRecvInfo;
import com.bx.imcommon.model.PrivateMessageInfo;
import com.bx.imcommon.model.SendResult;
import com.bx.imcommon.enums.IMTerminalType;
import com.bx.imcommon.model.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
@ -25,91 +23,130 @@ public class IMSender {
@Autowired
@Qualifier("IMRedisTemplate")
private RedisTemplate redisTemplate;
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private MessageListenerMulticaster listenerMulticaster;
public void sendPrivateMessage(Long recvId, PrivateMessageInfo... messageInfos){
public void sendPrivateMessage(IMPrivateMessage<?> message) {
for (Integer terminal : message.getRecvTerminals()) {
// 获取对方连接的channelId
String key = RedisKey.IM_USER_SERVER_ID + recvId;
String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID, message.getRecvId().toString(), terminal.toString());
Integer serverId = (Integer)redisTemplate.opsForValue().get(key);
// 如果对方在线,将数据存储至redis,等待拉取推送
if (serverId != null) {
String sendKey = RedisKey.IM_UNREAD_PRIVATE_QUEUE + serverId;
IMRecvInfo[] recvInfos = new IMRecvInfo[messageInfos.length];
for (int i=0;i<messageInfos.length;i++){
IMRecvInfo<PrivateMessageInfo> recvInfo = new IMRecvInfo<>();
String sendKey = String.join(":", IMRedisKey.IM_UNREAD_PRIVATE_QUEUE, serverId.toString());
IMRecvInfo recvInfo = new IMRecvInfo();
recvInfo.setCmd(IMCmdType.PRIVATE_MESSAGE.code());
List recvIds = new LinkedList();
recvIds.add(recvId);
recvInfo.setRecvIds(recvIds);
recvInfo.setData(messageInfos[i]);
recvInfos[i] = recvInfo;
}
redisTemplate.opsForList().rightPushAll(sendKey, recvInfos);
}else{
recvInfo.setSendResult(message.getSendResult());
recvInfo.setSender(message.getSender());
recvInfo.setReceivers(Collections.singletonList(new IMUserInfo(message.getRecvId(), terminal)));
recvInfo.setData(message.getData());
redisTemplate.opsForList().rightPush(sendKey, recvInfo);
} else if (message.getSendResult()) {
// 回复消息状态
for(PrivateMessageInfo messageInfo : messageInfos ) {
SendResult result = new SendResult();
result.setMessageInfo(messageInfo);
result.setRecvId(recvId);
result.setCode(IMSendCode.NOT_ONLINE);
IMSendResult result = new IMSendResult();
result.setSender(message.getSender());
result.setReceiver(new IMUserInfo(message.getRecvId(), terminal));
result.setCode(IMSendCode.NOT_ONLINE.code());
result.setData(message.getData());
listenerMulticaster.multicast(IMListenerType.PRIVATE_MESSAGE, result);
}
}
// 推送给自己的其他终端
if(message.getSendToSelf()){
for (Integer terminal : IMTerminalType.codes()) {
if (message.getSender().getTerminal().equals(terminal)) {
continue;
}
public void sendGroupMessage(List<Long> recvIds, GroupMessageInfo... messageInfos){
// 根据群聊每个成员所连的IM-server,进行分组
List<Long> offLineIds = Collections.synchronizedList(new LinkedList<Long>());
Map<Integer, List<Long>> serverMap = new ConcurrentHashMap<>();
recvIds.parallelStream().forEach(id->{
String key = RedisKey.IM_USER_SERVER_ID + id;
// 获取终端连接的channelId
String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID, message.getSender().getId().toString(), terminal.toString());
Integer serverId = (Integer)redisTemplate.opsForValue().get(key);
// 如果终端在线,将数据存储至redis,等待拉取推送
if (serverId != null) {
// 此处需要加锁,否则list可以会被覆盖
synchronized(serverMap){
if(serverMap.containsKey(serverId)){
serverMap.get(serverId).add(id);
}else {
List<Long> list = Collections.synchronizedList(new LinkedList<Long>());
list.add(id);
serverMap.put(serverId,list);
String sendKey = String.join(":", IMRedisKey.IM_UNREAD_PRIVATE_QUEUE, serverId.toString());
IMRecvInfo recvInfo = new IMRecvInfo();
// 自己的消息不需要回推消息结果
recvInfo.setSendResult(false);
recvInfo.setCmd(IMCmdType.PRIVATE_MESSAGE.code());
recvInfo.setSender(message.getSender());
recvInfo.setReceivers(Collections.singletonList(new IMUserInfo(message.getSender().getId(), terminal)));
recvInfo.setData(message.getData());
redisTemplate.opsForList().rightPush(sendKey, recvInfo);
}
}
}
}
public void sendGroupMessage(IMGroupMessage<?> message) {
// 根据群聊每个成员所连的IM-server,进行分组
List<IMUserInfo> offLineUsers = Collections.synchronizedList(new LinkedList<>());
// 格式:map<服务器id,list<接收方>>
Map<Integer, List<IMUserInfo>> serverMap = new ConcurrentHashMap<>();
for (Integer terminal : message.getRecvTerminals()) {
message.getRecvIds().parallelStream().forEach(id -> {
String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID, id.toString(), terminal.toString());
Integer serverId = (Integer)redisTemplate.opsForValue().get(key);
if (serverId != null) {
List<IMUserInfo> list = serverMap.computeIfAbsent(serverId, o -> Collections.synchronizedList(new LinkedList<>()));
list.add(new IMUserInfo(id, terminal));
} else {
offLineIds.add(id);
// 加入离线列表
offLineUsers.add(new IMUserInfo(id, terminal));
}
});
}
// 逐个server发送
for (Map.Entry<Integer,List<Long>> entry : serverMap.entrySet()) {
IMRecvInfo[] recvInfos = new IMRecvInfo[messageInfos.length];
for (int i=0;i<messageInfos.length;i++){
IMRecvInfo<GroupMessageInfo> recvInfo = new IMRecvInfo<>();
for (Map.Entry<Integer, List<IMUserInfo>> entry : serverMap.entrySet()) {
IMRecvInfo recvInfo = new IMRecvInfo();
recvInfo.setCmd(IMCmdType.GROUP_MESSAGE.code());
recvInfo.setRecvIds(new LinkedList<>(entry.getValue()));
recvInfo.setData(messageInfos[i]);
recvInfos[i] = recvInfo;
}
String key = RedisKey.IM_UNREAD_GROUP_QUEUE +entry.getKey();
redisTemplate.opsForList().rightPushAll(key,recvInfos);
recvInfo.setReceivers(new LinkedList<>(entry.getValue()));
recvInfo.setSender(message.getSender());
recvInfo.setSendResult(message.getSendResult());
recvInfo.setData(message.getData());
// 推送至队列
String key = String.join(":", IMRedisKey.IM_UNREAD_GROUP_QUEUE, entry.getKey().toString());
redisTemplate.opsForList().rightPush(key, recvInfo);
}
// 不在线的用户,回复消息状态
for(GroupMessageInfo messageInfo:messageInfos ){
for(Long id : offLineIds){
// 回复消息状态
SendResult result = new SendResult();
result.setMessageInfo(messageInfo);
result.setRecvId(id);
result.setCode(IMSendCode.NOT_ONLINE);
// 对离线用户回复消息状态
if (message.getSendResult()) {
for (IMUserInfo offLineUser : offLineUsers) {
IMSendResult result = new IMSendResult();
result.setSender(message.getSender());
result.setReceiver(offLineUser);
result.setCode(IMSendCode.NOT_ONLINE.code());
result.setData(message.getData());
listenerMulticaster.multicast(IMListenerType.GROUP_MESSAGE, result);
}
}
// 推送给自己的其他终端
if (message.getSendToSelf()) {
for (Integer terminal : IMTerminalType.codes()) {
if (terminal.equals(message.getSender().getTerminal())) {
continue;
}
// 获取终端连接的channelId
String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID, message.getSender().getId().toString(), terminal.toString());
Integer serverId = (Integer)redisTemplate.opsForValue().get(key);
// 如果终端在线,将数据存储至redis,等待拉取推送
if (serverId != null) {
IMRecvInfo recvInfo = new IMRecvInfo();
recvInfo.setCmd(IMCmdType.GROUP_MESSAGE.code());
recvInfo.setSender(message.getSender());
recvInfo.setReceivers(Collections.singletonList(new IMUserInfo(message.getSender().getId(), terminal)));
// 自己的消息不需要回推消息结果
recvInfo.setSendResult(false);
recvInfo.setData(message.getData());
String sendKey = String.join(":", IMRedisKey.IM_UNREAD_GROUP_QUEUE, serverId.toString());
redisTemplate.opsForList().rightPush(sendKey, recvInfo);
}
}
}
}
public Boolean isOnline(Long userId) {
String key = RedisKey.IM_USER_SERVER_ID + userId;
return redisTemplate.hasKey(key);
String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID, userId.toString(), "*");
return !redisTemplate.keys(key).isEmpty();
}
}

3
im-client/src/main/java/com/bx/imclient/task/AbstractPullMessageTask.java

@ -5,8 +5,7 @@ import lombok.extern.slf4j.Slf4j;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.*;
@Slf4j
public abstract class AbstractPullMessageTask {

12
im-client/src/main/java/com/bx/imclient/task/PullSendResultGroupMessageTask.java

@ -1,9 +1,10 @@
package com.bx.imclient.task;
import com.alibaba.fastjson.JSONObject;
import com.bx.imclient.listener.MessageListenerMulticaster;
import com.bx.imcommon.contant.RedisKey;
import com.bx.imcommon.contant.IMRedisKey;
import com.bx.imcommon.enums.IMListenerType;
import com.bx.imcommon.model.SendResult;
import com.bx.imcommon.model.IMSendResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
@ -23,9 +24,10 @@ public class PullSendResultGroupMessageTask extends AbstractPullMessageTask{
@Override
public void pullMessage() {
String key = RedisKey.IM_RESULT_GROUP_QUEUE;
SendResult result = (SendResult)redisTemplate.opsForList().leftPop(key,10, TimeUnit.SECONDS);
if(result != null) {
String key = IMRedisKey.IM_RESULT_GROUP_QUEUE;
JSONObject jsonObject = (JSONObject)redisTemplate.opsForList().leftPop(key,10, TimeUnit.SECONDS);
if(jsonObject != null) {
IMSendResult result = jsonObject.toJavaObject(IMSendResult.class);
listenerMulticaster.multicast(IMListenerType.GROUP_MESSAGE,result);
}
}

12
im-client/src/main/java/com/bx/imclient/task/PullSendResultPrivateMessageTask.java

@ -1,9 +1,10 @@
package com.bx.imclient.task;
import com.alibaba.fastjson.JSONObject;
import com.bx.imclient.listener.MessageListenerMulticaster;
import com.bx.imcommon.contant.RedisKey;
import com.bx.imcommon.contant.IMRedisKey;
import com.bx.imcommon.enums.IMListenerType;
import com.bx.imcommon.model.SendResult;
import com.bx.imcommon.model.IMSendResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
@ -28,9 +29,10 @@ public class PullSendResultPrivateMessageTask extends AbstractPullMessageTask{
@Override
public void pullMessage() {
String key = RedisKey.IM_RESULT_PRIVATE_QUEUE;
SendResult result = (SendResult)redisTemplate.opsForList().leftPop(key,10, TimeUnit.SECONDS);
if(result != null) {
String key = IMRedisKey.IM_RESULT_PRIVATE_QUEUE;
JSONObject jsonObject = (JSONObject)redisTemplate.opsForList().leftPop(key,10, TimeUnit.SECONDS);
if(jsonObject != null) {
IMSendResult result = jsonObject.toJavaObject(IMSendResult.class);
listenerMulticaster.multicast(IMListenerType.PRIVATE_MESSAGE, result);
}
}

3
im-commom/src/main/java/com/bx/imcommon/contant/Constant.java → im-commom/src/main/java/com/bx/imcommon/contant/IMConstant.java

@ -1,7 +1,7 @@
package com.bx.imcommon.contant;
public class Constant {
public class IMConstant {
// 在线状态过期时间 600s
public static final long ONLINE_TIMEOUT_SECOND = 600;
@ -9,5 +9,4 @@ public class Constant {
public static final long ALLOW_RECALL_SECOND = 300;
}

9
im-commom/src/main/java/com/bx/imcommon/contant/RedisKey.java → im-commom/src/main/java/com/bx/imcommon/contant/IMRedisKey.java

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

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

@ -11,6 +11,7 @@ public enum IMCmdType {
GROUP_MESSAGE(4,"群发消息");
private Integer code;
private String desc;

12
im-commom/src/main/java/com/bx/imcommon/enums/IMSendCode.java

@ -8,7 +8,7 @@ public enum IMSendCode {
NOT_FIND_CHANNEL(2,"未找到对方的channel"),
UNKONW_ERROR(9999,"未知异常");
private int code;
private Integer code;
private String desc;
// 构造方法
@ -17,6 +17,16 @@ public enum IMSendCode {
this.desc = desc;
}
public static IMSendCode fromCode(Integer code){
for (IMSendCode typeEnum:values()) {
if (typeEnum.code.equals(code)) {
return typeEnum;
}
}
return null;
}
public String description() {
return desc;
}

42
im-commom/src/main/java/com/bx/imcommon/enums/IMTerminalType.java

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

42
im-commom/src/main/java/com/bx/imcommon/model/GroupMessageInfo.java

@ -1,42 +0,0 @@
package com.bx.imcommon.model;
import com.bx.imcommon.serializer.DateToLongSerializer;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import lombok.Data;
import java.util.Date;
@Data
public class GroupMessageInfo {
/*
* 消息id
*/
private Long id;
/*
* 群聊id
*/
private Long groupId;
/*
* 发送者id
*/
private Long sendId;
/*
* 消息内容
*/
private String content;
/*
* 消息内容类型 具体枚举值由应用层定义
*/
private Integer type;
/**
* 发送时间
*/
@JsonSerialize(using = DateToLongSerializer.class)
private Date sendTime;
}

43
im-commom/src/main/java/com/bx/imcommon/model/IMGroupMessage.java

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

2
im-commom/src/main/java/com/bx/imcommon/model/HeartbeatInfo.java → im-commom/src/main/java/com/bx/imcommon/model/IMHeartbeatInfo.java

@ -3,5 +3,5 @@ package com.bx.imcommon.model;
import lombok.Data;
@Data
public class HeartbeatInfo {
public class IMHeartbeatInfo {
}

2
im-commom/src/main/java/com/bx/imcommon/model/LoginInfo.java → im-commom/src/main/java/com/bx/imcommon/model/IMLoginInfo.java

@ -3,7 +3,7 @@ package com.bx.imcommon.model;
import lombok.Data;
@Data
public class LoginInfo {
public class IMLoginInfo {
private String accessToken;
}

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

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

20
im-commom/src/main/java/com/bx/imcommon/model/IMRecvInfo.java

@ -5,22 +5,32 @@ import lombok.Data;
import java.util.List;
@Data
public class IMRecvInfo<T> {
public class IMRecvInfo {
/*
* 命令类型
* 命令类型 IMCmdType
*/
private Integer cmd;
/*
* 接收者id列表
* 发送方
*/
private List<Long> recvIds;
private IMUserInfo sender;
/*
* 接收方用户列表
*/
List<IMUserInfo> receivers;
/*
* 是否需要回调发送结果
*/
private Boolean sendResult;
/*
* 推送消息体
*/
private T data;
private Object data;
}

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

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

18
im-commom/src/main/java/com/bx/imcommon/model/IMSessionInfo.java

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

30
im-commom/src/main/java/com/bx/imcommon/model/IMUserInfo.java

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

42
im-commom/src/main/java/com/bx/imcommon/model/PrivateMessageInfo.java

@ -1,42 +0,0 @@
package com.bx.imcommon.model;
import com.bx.imcommon.serializer.DateToLongSerializer;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import lombok.Data;
import java.util.Date;
@Data
public class PrivateMessageInfo {
/*
* 消息id
*/
private long id;
/*
* 发送者id
*/
private Long sendId;
/*
* 接收者id
*/
private Long recvId;
/*
* 发送内容
*/
private String content;
/*
* 消息内容类型 具体枚举值由应用层定义
*/
private Integer type;
/**
* 发送时间
*/
@JsonSerialize(using = DateToLongSerializer.class)
private Date sendTime;
}

24
im-commom/src/main/java/com/bx/imcommon/model/SendResult.java

@ -1,24 +0,0 @@
package com.bx.imcommon.model;
import com.bx.imcommon.enums.IMSendCode;
import lombok.Data;
@Data
public class SendResult<T> {
/*
* 接收者id
*/
private Long recvId;
/*
* 发送状态
*/
private IMSendCode code;
/*
* 消息体(透传)
*/
private T messageInfo;
}

4
im-platform/src/main/java/com/bx/implatform/ImplatformApp.java → im-platform/src/main/java/com/bx/implatform/IMPlatformApp.java

@ -12,9 +12,9 @@ import org.springframework.context.annotation.EnableAspectJAutoProxy;
@EnableAspectJAutoProxy(exposeProxy = true)
@MapperScan(basePackages = {"com.bx.implatform.mapper"})
@SpringBootApplication(exclude= {SecurityAutoConfiguration.class })// 禁用secrity
public class ImplatformApp {
public class IMPlatformApp {
public static void main(String[] args) {
SpringApplication.run(ImplatformApp.class,args);
SpringApplication.run(IMPlatformApp.class,args);
}
}

3
im-platform/src/main/java/com/bx/implatform/config/MinIoClientConfig.java

@ -20,10 +20,9 @@ public class MinIoClientConfig {
@Bean
public MinioClient minioClient(){
// 注入minio 客户端
MinioClient client = MinioClient.builder()
return MinioClient.builder()
.endpoint(endpoint)
.credentials(accessKey, secretKey)
.build();
return client;
}
}

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

@ -16,7 +16,7 @@ public class MvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(XssInterceptor())
registry.addInterceptor(xssInterceptor())
.addPathPatterns("/**");
registry.addInterceptor(authInterceptor())
.addPathPatterns("/**")
@ -30,7 +30,7 @@ public class MvcConfig implements WebMvcConfigurer {
}
@Bean
public XssInterceptor XssInterceptor() {
public XssInterceptor xssInterceptor() {
return new XssInterceptor();
}

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

@ -38,7 +38,7 @@ public class RedisConfig extends CachingConfigurerSupport {
@Primary
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate();
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
// 设置值(value)的序列化采用jackson2JsonRedisSerializer
@ -52,8 +52,8 @@ public class RedisConfig extends CachingConfigurerSupport {
}
@Bean
public Jackson2JsonRedisSerializer jackson2JsonRedisSerializer(){
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
public Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer(){
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 解决jackson2无法反序列化LocalDateTime的问题

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

@ -8,13 +8,5 @@ public class Constant {
public static final long MAX_FILE_SIZE = 10*1024*1024;
// 群聊最大人数
public static final long MAX_GROUP_MEMBER = 500;
// accessToken 过期时间(半小时)
public static final Integer ACCESS_TOKEN_EXPIRE = 30 * 60;
// refreshToken 过期时间(7天)
public static final Integer REFRESH_TOKEN_EXPIRE = 7 * 24 * 60 * 60 ;
// accessToken 加密秘钥
// refreshToken 加密秘钥
public static final String ACCESS_TOKEN_SECRET = "MIIBIjANBgkq";
public static final String REFRESH_TOKEN_SECRET = "IKDiqVmn0VFU";
}

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

@ -3,7 +3,9 @@ package com.bx.implatform.contant;
public class RedisKey {
// 已读群聊消息位置(已读最大id)
public final static String IM_GROUP_READED_POSITION = "im:readed:group:position:";
public final static String IM_GROUP_READED_POSITION = "im:readed:group:position";
// webrtc 会话信息
public final static String IM_WEBRTC_SESSION = "im:webrtc:session";
// 缓存前缀
public final static String IM_CACHE = "im:cache:";
// 缓存是否好友:bool

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

@ -27,7 +27,7 @@ public class FriendController {
@GetMapping("/list")
@ApiOperation(value = "好友列表",notes="获取好友列表")
public Result< List<FriendVO>> findFriends(){
List<Friend> friends = friendService.findFriendByUserId(SessionContext.getSession().getId());
List<Friend> friends = friendService.findFriendByUserId(SessionContext.getSession().getUserId());
List<FriendVO> vos = friends.stream().map(f->{
FriendVO vo = new FriendVO();
vo.setId(f.getFriendId());

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

@ -28,8 +28,8 @@ public class GroupController {
@ApiOperation(value = "创建群聊",notes="创建群聊")
@PostMapping("/create")
public Result<GroupVO> createGroup(@NotEmpty(message = "群名不能为空") @RequestParam String groupName){
return ResultUtils.success(groupService.createGroup(groupName));
public Result<GroupVO> createGroup(@Valid @RequestBody GroupVO vo){
return ResultUtils.success(groupService.createGroup(vo));
}
@ApiOperation(value = "修改群聊信息",notes="修改群聊信息")

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

@ -1,11 +1,11 @@
package com.bx.implatform.controller;
import com.bx.imcommon.model.GroupMessageInfo;
import com.bx.implatform.vo.GroupMessageVO;
import com.bx.implatform.result.Result;
import com.bx.implatform.result.ResultUtils;
import com.bx.implatform.service.IGroupMessageService;
import com.bx.implatform.vo.GroupMessageVO;
import com.bx.implatform.dto.GroupMessageDTO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
@ -27,7 +27,7 @@ public class GroupMessageController {
@PostMapping("/send")
@ApiOperation(value = "发送群聊消息",notes="发送群聊消息")
public Result<Long> sendMessage(@Valid @RequestBody GroupMessageVO vo){
public Result<Long> sendMessage(@Valid @RequestBody GroupMessageDTO vo){
return ResultUtils.success(groupMessageService.sendMessage(vo));
}
@ -47,7 +47,7 @@ public class GroupMessageController {
@GetMapping("/history")
@ApiOperation(value = "查询聊天记录",notes="查询聊天记录")
public Result<List<GroupMessageInfo>> 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 = "size不能为空") @RequestParam Long size){
return ResultUtils.success( groupMessageService.findHistoryMessage(groupId,page,size));

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

@ -1,6 +1,7 @@
package com.bx.implatform.controller;
import com.bx.implatform.dto.ModifyPwdDTO;
import com.bx.implatform.result.Result;
import com.bx.implatform.result.ResultUtils;
import com.bx.implatform.service.IUserService;
@ -45,4 +46,12 @@ public class LoginController {
userService.register(dto);
return ResultUtils.success();
}
@PutMapping("/modifyPwd")
@ApiOperation(value = "修改密码",notes="修改用户密码")
public Result update(@Valid @RequestBody ModifyPwdDTO dto){
userService.modifyPassword(dto);
return ResultUtils.success();
}
}

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

@ -1,11 +1,11 @@
package com.bx.implatform.controller;
import com.bx.imcommon.model.PrivateMessageInfo;
import com.bx.implatform.vo.PrivateMessageVO;
import com.bx.implatform.result.Result;
import com.bx.implatform.result.ResultUtils;
import com.bx.implatform.service.IPrivateMessageService;
import com.bx.implatform.vo.PrivateMessageVO;
import com.bx.implatform.dto.PrivateMessageDTO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
@ -25,7 +25,7 @@ public class PrivateMessageController {
@PostMapping("/send")
@ApiOperation(value = "发送消息",notes="发送私聊消息")
public Result<Long> sendMessage(@Valid @RequestBody PrivateMessageVO vo){
public Result<Long> sendMessage(@Valid @RequestBody PrivateMessageDTO vo){
return ResultUtils.success(privateMessageService.sendMessage(vo));
}
@ -48,7 +48,7 @@ public class PrivateMessageController {
@GetMapping("/history")
@ApiOperation(value = "查询聊天记录",notes="查询聊天记录")
public Result<List<PrivateMessageInfo>> recallMessage(@NotNull(message = "好友id不能为空") @RequestParam Long friendId,
public Result<List<PrivateMessageVO>> recallMessage(@NotNull(message = "好友id不能为空") @RequestParam Long friendId,
@NotNull(message = "页码不能为空") @RequestParam Long page,
@NotNull(message = "size不能为空") @RequestParam Long size){
return ResultUtils.success( privateMessageService.findHistoryMessage(friendId,page,size));

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

@ -1,5 +1,6 @@
package com.bx.implatform.controller;
import com.bx.implatform.dto.ModifyPwdDTO;
import com.bx.implatform.entity.User;
import com.bx.implatform.result.Result;
import com.bx.implatform.result.ResultUtils;
@ -36,9 +37,9 @@ public class UserController {
@GetMapping("/self")
@ApiOperation(value = "获取当前用户信息",notes="获取当前用户信息")
public Result findSelfInfo(){
public Result<UserVO> findSelfInfo(){
UserSession session = SessionContext.getSession();
User user = userService.getById(session.getId());
User user = userService.getById(session.getUserId());
UserVO userVO = BeanUtils.copyProperties(user,UserVO.class);
return ResultUtils.success(userVO);
}
@ -61,11 +62,16 @@ public class UserController {
@GetMapping("/findByNickName")
@ApiOperation(value = "查找用户",notes="根据昵称查找用户")
public Result findByNickName(@NotEmpty(message = "用户昵称不可为空") @RequestParam("nickName") String nickName){
public Result<List<UserVO>> findByNickName(@NotEmpty(message = "用户昵称不可为空") @RequestParam("nickName") String nickName){
return ResultUtils.success( userService.findUserByNickName(nickName));
}
@GetMapping("/findByName")
@ApiOperation(value = "查找用户",notes="根据用户名或昵称查找用户")
public Result<List<UserVO>> findByName(@NotEmpty(message = "用户名称不可为空") @RequestParam("name") String name){
return ResultUtils.success( userService.findUserByName(name));
}
}

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

@ -1,54 +1,35 @@
package com.bx.implatform.controller;
import com.bx.imclient.IMClient;
import com.bx.imcommon.model.PrivateMessageInfo;
import com.bx.implatform.config.ICEServerConfig;
import com.bx.implatform.enums.MessageType;
import com.bx.implatform.config.ICEServer;
import com.bx.implatform.result.Result;
import com.bx.implatform.result.ResultUtils;
import com.bx.implatform.session.SessionContext;
import com.bx.implatform.service.IWebrtcService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Api(tags = "webrtc视频单人通话")
@RestController
@RequestMapping("/webrtc/private")
public class WebrtcController {
@Autowired
private IMClient imClient;
@Autowired
private ICEServerConfig iceServerConfig;
private IWebrtcService webrtcService;
@ApiOperation(httpMethod = "POST", value = "呼叫视频通话")
@PostMapping("/call")
public Result call(@RequestParam Long uid, @RequestBody String offer) {
Long userId = SessionContext.getSession().getId();
PrivateMessageInfo message = new PrivateMessageInfo();
message.setType(MessageType.RTC_CALL.code());
message.setRecvId(uid);
message.setSendId(userId);
message.setContent(offer);
imClient.sendPrivateMessage(uid,message);
webrtcService.call(uid,offer);
return ResultUtils.success();
}
@ApiOperation(httpMethod = "POST", value = "接受视频通话")
@PostMapping("/accept")
public Result accept(@RequestParam Long uid,@RequestBody String answer) {
Long userId = SessionContext.getSession().getId();
PrivateMessageInfo message = new PrivateMessageInfo();
message.setType(MessageType.RTC_ACCEPT.code());
message.setRecvId(uid);
message.setSendId(userId);
message.setContent(answer);
imClient.sendPrivateMessage(uid,message);
webrtcService.accept(uid,answer);
return ResultUtils.success();
}
@ -56,71 +37,43 @@ public class WebrtcController {
@ApiOperation(httpMethod = "POST", value = "拒绝视频通话")
@PostMapping("/reject")
public Result reject(@RequestParam Long uid) {
Long userId = SessionContext.getSession().getId();
PrivateMessageInfo message = new PrivateMessageInfo();
message.setType(MessageType.RTC_REJECT.code());
message.setRecvId(uid);
message.setSendId(userId);
imClient.sendPrivateMessage(uid,message);
webrtcService.reject(uid);
return ResultUtils.success();
}
@ApiOperation(httpMethod = "POST", value = "取消呼叫")
@PostMapping("/cancel")
public Result cancel(@RequestParam Long uid) {
Long userId = SessionContext.getSession().getId();
PrivateMessageInfo message = new PrivateMessageInfo();
message.setType(MessageType.RTC_CANCEL.code());
message.setRecvId(uid);
message.setSendId(userId);
imClient.sendPrivateMessage(uid,message);
webrtcService.cancel(uid);
return ResultUtils.success();
}
@ApiOperation(httpMethod = "POST", value = "呼叫失败")
@PostMapping("/failed")
public Result failed(@RequestParam Long uid,@RequestParam String reason) {
Long userId = SessionContext.getSession().getId();
PrivateMessageInfo message = new PrivateMessageInfo();
message.setType(MessageType.RTC_FAILED.code());
message.setRecvId(uid);
message.setSendId(userId);
message.setContent(reason);
imClient.sendPrivateMessage(uid,message);
webrtcService.failed(uid,reason);
return ResultUtils.success();
}
@ApiOperation(httpMethod = "POST", value = "挂断")
@PostMapping("/handup")
public Result leave(@RequestParam Long uid) {
Long userId = SessionContext.getSession().getId();
PrivateMessageInfo message = new PrivateMessageInfo();
message.setType(MessageType.RTC_HANDUP.code());
message.setRecvId(uid);
message.setSendId(userId);
imClient.sendPrivateMessage(uid,message);
webrtcService.leave(uid);
return ResultUtils.success();
}
@PostMapping("/candidate")
@ApiOperation(httpMethod = "POST", value = "同步candidate")
public Result candidate(@RequestParam Long uid,@RequestBody String candidate ) {
Long userId = SessionContext.getSession().getId();
PrivateMessageInfo message = new PrivateMessageInfo();
message.setType(MessageType.RTC_CANDIDATE.code());
message.setRecvId(uid);
message.setSendId(userId);
message.setContent(candidate);
imClient.sendPrivateMessage(uid,message);
public Result forwardCandidate(@RequestParam Long uid,@RequestBody String candidate ) {
webrtcService.candidate(uid,candidate);
return ResultUtils.success();
}
@GetMapping("/iceservers")
@ApiOperation(httpMethod = "GET", value = "获取iceservers")
public Result iceservers() {
return ResultUtils.success(iceServerConfig.getIceServers());
public Result<List<ICEServer>> iceservers() {
return ResultUtils.success(webrtcService.getIceServers());
}
}

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

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

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

@ -5,17 +5,26 @@ import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
@Data
@ApiModel("用户登录VO")
@ApiModel("用户登录DTO")
public class LoginDTO {
//@NotEmpty(message="用户名不可为空")
@Max(value = 1,message = "登录终端类型取值范围:0,1")
@Min(value = 0,message = "登录终端类型取值范围:0,1")
@NotNull(message="登录终端类型不可为空")
@ApiModelProperty(value = "登录终端 0:web 1:app")
private Integer terminal;
@NotEmpty(message="用户名不可为空")
@ApiModelProperty(value = "用户名")
private String userName;
// @NotEmpty(message="用户密码不可为空")
@NotEmpty(message="用户密码不可为空")
@ApiModelProperty(value = "用户密码")
private String password;

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

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

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

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

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

@ -8,7 +8,7 @@ import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotEmpty;
@Data
@ApiModel("用户注册VO")
@ApiModel("用户注册DTO")
public class RegisterDTO {
@Length(max = 64,message = "用户名不能大于64字符")

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

@ -62,6 +62,11 @@ public class User extends Model<User> {
@TableField("head_image_thumb")
private String headImageThumb;
/**
* 用户类型 1:普通用户 2:审核专用账户
*/
@TableField("type")
private Integer type;
/**
* 个性签名

13
im-platform/src/main/java/com/bx/implatform/enums/FileType.java

@ -9,24 +9,15 @@ public enum FileType {
private Integer code;
private final Integer code;
private String desc;
private final String desc;
FileType(Integer index, String desc) {
this.code =index;
this.desc=desc;
}
public static FileType fromCode(Integer code){
for (FileType typeEnum:values()) {
if (typeEnum.code.equals(code)) {
return typeEnum;
}
}
return null;
}
public String description() {
return desc;

14
im-platform/src/main/java/com/bx/implatform/enums/MessageStatus.java

@ -7,25 +7,15 @@ public enum MessageStatus {
ALREADY_READ(1,"已读"),
RECALL(2,"已撤回");
private Integer code;
private final Integer code;
private String desc;
private final String desc;
MessageStatus(Integer index, String desc) {
this.code =index;
this.desc=desc;
}
public static MessageStatus fromCode(Integer code){
for (MessageStatus typeEnum:values()) {
if (typeEnum.code.equals(code)) {
return typeEnum;
}
}
return null;
}
public String description() {
return desc;
}

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

@ -4,10 +4,11 @@ package com.bx.implatform.enums;
public enum MessageType {
TEXT(0,"文字"),
FILE(1,"文件"),
IMAGE(2,"图片"),
VIDEO(3,"视频"),
TIP(10,"系统提示"),
IMAGE(1,"图片"),
FILE(2,"文件"),
AUDIO(3,"音频"),
VIDEO(4,"视频"),
RECALL(10,"撤回"),
RTC_CALL(101,"呼叫"),
RTC_ACCEPT(102,"接受"),
@ -17,16 +18,15 @@ public enum MessageType {
RTC_HANDUP(106,"挂断"),
RTC_CANDIDATE(107,"同步candidate");
private Integer code;
private final Integer code;
private String desc;
private final String desc;
MessageType(Integer index, String desc) {
this.code =index;
this.desc=desc;
}
public String description() {
return desc;
}

1
im-platform/src/main/java/com/bx/implatform/enums/ResultCode.java

@ -21,7 +21,6 @@ public enum ResultCode {
private int code;
private String msg;
// 构造方法
ResultCode(int code, String msg) {
this.code = code;
this.msg = msg;

2
im-platform/src/main/java/com/bx/implatform/exception/GlobalExceptionHandler.java

@ -71,7 +71,7 @@ public class GlobalExceptionHandler {
public Result handleValidationExceptionHandler(MethodArgumentNotValidException exception) {
BindingResult bindResult = exception.getBindingResult();
String msg;
if (bindResult != null && bindResult.hasErrors()) {
if (bindResult.hasErrors()) {
msg = bindResult.getAllErrors().get(0).getDefaultMessage();
if (msg.contains("NumberFormatException")) {
msg = "参数类型错误!";

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

@ -7,7 +7,6 @@ import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.List;
@Component
@ServletComponentScan

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

@ -10,9 +10,9 @@ import java.io.*;
public class CacheHttpServletRequestWrapper extends HttpServletRequestWrapper {
private byte[] requestBody;
private HttpServletRequest request;
private final HttpServletRequest request;
public CacheHttpServletRequestWrapper(HttpServletRequest request) throws IOException {
public CacheHttpServletRequestWrapper(HttpServletRequest request) {
super(request);
this.request = request;
}
@ -48,10 +48,6 @@ public class CacheHttpServletRequestWrapper extends HttpServletRequestWrapper {
};
}
public byte[] getRequestBody() {
return requestBody;
}
@Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream()));

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

@ -14,7 +14,6 @@ import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
;
@Slf4j
public class AuthInterceptor implements HandlerInterceptor {

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

@ -17,7 +17,7 @@ public class XssInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// 检查参数
Map<String, String[]> paramMap = request.getParameterMap();
for(String[] values:paramMap.values()){

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

@ -4,10 +4,9 @@ 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.GroupMessageInfo;
import com.bx.imcommon.model.SendResult;
import com.bx.implatform.vo.GroupMessageVO;
import com.bx.imcommon.model.IMSendResult;
import com.bx.implatform.contant.RedisKey;
import com.bx.implatform.enums.MessageType;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
@ -15,22 +14,17 @@ import org.springframework.data.redis.core.RedisTemplate;
@Slf4j
@IMListener(type = IMListenerType.GROUP_MESSAGE)
public class GroupMessageListener implements MessageListener {
public class GroupMessageListener implements MessageListener<GroupMessageVO> {
@Autowired
private RedisTemplate<String,Object> redisTemplate;
@Override
public void process(SendResult result){
GroupMessageInfo messageInfo = (GroupMessageInfo) result.getMessageInfo();
// 提示类数据不记录
if(messageInfo.getType().equals(MessageType.TIP)){
return;
}
public void process(IMSendResult<GroupMessageVO> result){
GroupMessageVO messageInfo = result.getData();
// 保存该用户已拉取的最大消息id
if(result.getCode().equals(IMSendCode.SUCCESS)) {
String key = RedisKey.IM_GROUP_READED_POSITION + messageInfo.getGroupId() + ":" + result.getRecvId();
if(result.getCode().equals(IMSendCode.SUCCESS.code())) {
String key = String.join(":",RedisKey.IM_GROUP_READED_POSITION,messageInfo.getGroupId().toString(),result.getReceiver().getId().toString());
redisTemplate.opsForValue().set(key, messageInfo.getId());
}
}

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

@ -1,63 +1,38 @@
package com.bx.implatform.listener;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.bx.imclient.IMClient;
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.PrivateMessageInfo;
import com.bx.imcommon.model.SendResult;
import com.bx.implatform.vo.PrivateMessageVO;
import com.bx.imcommon.model.IMSendResult;
import com.bx.implatform.entity.PrivateMessage;
import com.bx.implatform.enums.MessageStatus;
import com.bx.implatform.enums.MessageType;
import com.bx.implatform.service.IPrivateMessageService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Date;
@Slf4j
@IMListener(type = IMListenerType.PRIVATE_MESSAGE)
public class PrivateMessageListener implements MessageListener {
public class PrivateMessageListener implements MessageListener<PrivateMessageVO> {
@Autowired
private IPrivateMessageService privateMessageService;
@Autowired
private IMClient imClient;
@Override
public void process(SendResult result){
PrivateMessageInfo messageInfo = (PrivateMessageInfo) result.getMessageInfo();
// 提示类数据不记录
if(messageInfo.getType().equals(MessageType.TIP.code())){
return;
}
// 视频通话信令不记录
if(messageInfo.getType() >= MessageType.RTC_CALL.code() && messageInfo.getType()< MessageType.RTC_CANDIDATE.code()){
// 通知用户呼叫失败了
if(messageInfo.getType().equals(MessageType.RTC_CALL.code())
&& !result.getCode().equals(IMSendCode.SUCCESS)){
PrivateMessageInfo sendMessage = new PrivateMessageInfo();
sendMessage.setRecvId(messageInfo.getSendId());
sendMessage.setSendId(messageInfo.getRecvId());
sendMessage.setType(MessageType.RTC_FAILED.code());
sendMessage.setContent(result.getCode().description());
sendMessage.setSendTime(new Date());
imClient.sendPrivateMessage(sendMessage.getRecvId(),sendMessage);
}
return;
}
// 更新消息状态
if(result.getCode().equals(IMSendCode.SUCCESS)){
public void process(IMSendResult<PrivateMessageVO> result){
PrivateMessageVO messageInfo = result.getData();
// 更新消息状态,这里只处理成功消息,失败的消息继续保持未读状态
if(result.getCode().equals(IMSendCode.SUCCESS.code())){
UpdateWrapper<PrivateMessage> updateWrapper = new UpdateWrapper<>();
updateWrapper.lambda().eq(PrivateMessage::getId,messageInfo.getId())
.eq(PrivateMessage::getStatus, MessageStatus.UNREAD.code())
.set(PrivateMessage::getStatus, MessageStatus.ALREADY_READ.code());
privateMessageService.update(updateWrapper);
log.info("消息已读,消息id:{},发送者:{},接收者:{}",messageInfo.getId(),messageInfo.getSendId(),messageInfo.getRecvId());
log.info("消息已读,消息id:{},发送者:{},接收者:{},终端:{}",messageInfo.getId(),result.getSender().getId(),result.getReceiver().getId(),result.getReceiver().getTerminal());
}
}

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

@ -11,7 +11,7 @@ public interface IFriendService extends IService<Friend> {
Boolean isFriend(Long userId1, Long userId2);
List<Friend> findFriendByUserId(Long UserId);
List<Friend> findFriendByUserId(Long userId);
void addFriend(Long friendId);

1
im-platform/src/main/java/com/bx/implatform/service/IGroupMemberService.java

@ -18,6 +18,7 @@ public interface IGroupMemberService extends IService<GroupMember> {
List<Long> findUserIdsByGroupId(Long groupId);
boolean saveOrUpdateBatch(Long groupId,List<GroupMember> members);
void removeByGroupId(Long groupId);

8
im-platform/src/main/java/com/bx/implatform/service/IGroupMessageService.java

@ -1,9 +1,9 @@
package com.bx.implatform.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.bx.imcommon.model.GroupMessageInfo;
import com.bx.implatform.entity.GroupMessage;
import com.bx.implatform.vo.GroupMessageVO;
import com.bx.implatform.entity.GroupMessage;
import com.bx.implatform.dto.GroupMessageDTO;
import java.util.List;
@ -11,11 +11,11 @@ import java.util.List;
public interface IGroupMessageService extends IService<GroupMessage> {
Long sendMessage(GroupMessageVO vo);
Long sendMessage(GroupMessageDTO vo);
void recallMessage(Long id);
void pullUnreadMessage();
List<GroupMessageInfo> findHistoryMessage(Long groupId, Long page, Long size);
List<GroupMessageVO> findHistoryMessage(Long groupId, Long page, Long size);
}

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

@ -12,7 +12,7 @@ import java.util.List;
public interface IGroupService extends IService<Group> {
GroupVO createGroup(String groupName);
GroupVO createGroup(GroupVO vo);
GroupVO modifyGroup(GroupVO vo);
@ -26,7 +26,7 @@ public interface IGroupService extends IService<Group> {
void invite(GroupInviteVO vo);
Group GetById(Long groupId);
Group getById(Long groupId);
GroupVO findById(Long groupId);

8
im-platform/src/main/java/com/bx/implatform/service/IPrivateMessageService.java

@ -1,20 +1,20 @@
package com.bx.implatform.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.bx.imcommon.model.PrivateMessageInfo;
import com.bx.implatform.entity.PrivateMessage;
import com.bx.implatform.vo.PrivateMessageVO;
import com.bx.implatform.entity.PrivateMessage;
import com.bx.implatform.dto.PrivateMessageDTO;
import java.util.List;
public interface IPrivateMessageService extends IService<PrivateMessage> {
Long sendMessage(PrivateMessageVO vo);
Long sendMessage(PrivateMessageDTO vo);
void recallMessage(Long id);
List<PrivateMessageInfo> findHistoryMessage(Long friendId, Long page,Long size);
List<PrivateMessageVO> findHistoryMessage(Long friendId, Long page,Long size);
void pullUnreadMessage();

7
im-platform/src/main/java/com/bx/implatform/service/IUserService.java

@ -1,6 +1,7 @@
package com.bx.implatform.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.bx.implatform.dto.ModifyPwdDTO;
import com.bx.implatform.entity.User;
import com.bx.implatform.dto.LoginDTO;
import com.bx.implatform.dto.RegisterDTO;
@ -14,16 +15,20 @@ public interface IUserService extends IService<User> {
LoginVO login(LoginDTO dto);
void modifyPassword(ModifyPwdDTO dto);
LoginVO refreshToken(String refreshToken);
void register(RegisterDTO dto);
User findUserByName(String username);
User findUserByUserName(String username);
void update(UserVO vo);
List<UserVO> findUserByNickName(String nickname);
List<UserVO> findUserByName(String name);
List<Long> checkOnline(String userIds);
}

32
im-platform/src/main/java/com/bx/implatform/service/IWebrtcService.java

@ -0,0 +1,32 @@
package com.bx.implatform.service;
import com.bx.implatform.config.ICEServer;
import org.springframework.web.bind.annotation.RequestBody;
import java.util.List;
/**
* webrtc 通信服务
* @author
*/
public interface IWebrtcService {
void call(Long uid, String offer);
void accept( Long uid,@RequestBody String answer);
void reject( Long uid);
void cancel( Long uid);
void failed( Long uid, String reason);
void leave( Long uid) ;
void candidate( Long uid, String candidate);
List<ICEServer> getIceServers();
}

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

@ -36,13 +36,13 @@ public class FriendServiceImpl extends ServiceImpl<FriendMapper, Friend> impleme
/**
* 查询用户的所有好友
*
* @param UserId 用户id
* @param userId 用户id
* @return
*/
@Override
public List<Friend> findFriendByUserId(Long UserId) {
public List<Friend> findFriendByUserId(Long userId) {
QueryWrapper<Friend> queryWrapper = new QueryWrapper();
queryWrapper.lambda().eq(Friend::getUserId,UserId);
queryWrapper.lambda().eq(Friend::getUserId,userId);
List<Friend> friends = this.list(queryWrapper);
return friends;
}
@ -57,7 +57,7 @@ public class FriendServiceImpl extends ServiceImpl<FriendMapper, Friend> impleme
@Transactional
@Override
public void addFriend(Long friendId) {
long userId = SessionContext.getSession().getId();
long userId = SessionContext.getSession().getUserId();
if(userId == friendId){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"不允许添加自己为好友");
}
@ -78,7 +78,7 @@ public class FriendServiceImpl extends ServiceImpl<FriendMapper, Friend> impleme
@Transactional
@Override
public void delFriend(Long friendId) {
long userId = SessionContext.getSession().getId();
long userId = SessionContext.getSession().getUserId();
// 互相解除好友关系
FriendServiceImpl proxy = (FriendServiceImpl)AopContext.currentProxy();
proxy.unbindFriend(userId,friendId);
@ -113,7 +113,7 @@ public class FriendServiceImpl extends ServiceImpl<FriendMapper, Friend> impleme
*/
@Override
public void update(FriendVO vo) {
long userId = SessionContext.getSession().getId();
long userId = SessionContext.getSession().getUserId();
QueryWrapper<Friend> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda()
.eq(Friend::getUserId,userId)
@ -186,7 +186,7 @@ public class FriendServiceImpl extends ServiceImpl<FriendMapper, Friend> impleme
UserSession session = SessionContext.getSession();
QueryWrapper<Friend> wrapper = new QueryWrapper<>();
wrapper.lambda()
.eq(Friend::getUserId,session.getId())
.eq(Friend::getUserId,session.getUserId())
.eq(Friend::getFriendId,friendId);
Friend friend = this.getOne(wrapper);
if(friend == null){

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

@ -1,7 +1,9 @@
package com.bx.implatform.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bx.implatform.contant.RedisKey;
import com.bx.implatform.entity.GroupMember;
@ -71,8 +73,8 @@ public class GroupMemberServiceImpl extends ServiceImpl<GroupMemberMapper, Group
*/
@Override
public List<GroupMember> findByUserId(Long userId) {
QueryWrapper<GroupMember> memberWrapper = new QueryWrapper();
memberWrapper.lambda().eq(GroupMember::getUserId, userId)
LambdaQueryWrapper<GroupMember> memberWrapper = Wrappers.lambdaQuery();
memberWrapper.eq(GroupMember::getUserId, userId)
.eq(GroupMember::getQuit,false);
return this.list(memberWrapper);
}
@ -86,8 +88,8 @@ public class GroupMemberServiceImpl extends ServiceImpl<GroupMemberMapper, Group
*/
@Override
public List<GroupMember> findByGroupId(Long groupId) {
QueryWrapper<GroupMember> memberWrapper = new QueryWrapper();
memberWrapper.lambda().eq(GroupMember::getGroupId, groupId);
LambdaQueryWrapper<GroupMember> memberWrapper = Wrappers.lambdaQuery();
memberWrapper.eq(GroupMember::getGroupId, groupId);
return this.list(memberWrapper);
}
@ -101,11 +103,11 @@ public class GroupMemberServiceImpl extends ServiceImpl<GroupMemberMapper, Group
@Cacheable(key="#groupId")
@Override
public List<Long> findUserIdsByGroupId(Long groupId) {
QueryWrapper<GroupMember> memberWrapper = new QueryWrapper();
memberWrapper.lambda().eq(GroupMember::getGroupId, groupId)
LambdaQueryWrapper<GroupMember> memberWrapper = Wrappers.lambdaQuery();
memberWrapper.eq(GroupMember::getGroupId, groupId)
.eq(GroupMember::getQuit,false);
List<GroupMember> members = this.list(memberWrapper);
return members.stream().map(m->m.getUserId()).collect(Collectors.toList());
return members.stream().map(GroupMember::getUserId).collect(Collectors.toList());
}
@ -118,8 +120,8 @@ public class GroupMemberServiceImpl extends ServiceImpl<GroupMemberMapper, Group
@CacheEvict(key = "#groupId")
@Override
public void removeByGroupId(Long groupId) {
UpdateWrapper<GroupMember> wrapper = new UpdateWrapper();
wrapper.lambda().eq(GroupMember::getGroupId,groupId)
LambdaUpdateWrapper<GroupMember> wrapper = Wrappers.lambdaUpdate();
wrapper.eq(GroupMember::getGroupId,groupId)
.set(GroupMember::getQuit,true);
this.update(wrapper);
}
@ -134,8 +136,8 @@ public class GroupMemberServiceImpl extends ServiceImpl<GroupMemberMapper, Group
@CacheEvict(key = "#groupId")
@Override
public void removeByGroupAndUserId(Long groupId, Long userId) {
UpdateWrapper<GroupMember> wrapper = new UpdateWrapper<>();
wrapper.lambda().eq(GroupMember::getGroupId,groupId)
LambdaUpdateWrapper<GroupMember> wrapper = Wrappers.lambdaUpdate();
wrapper.eq(GroupMember::getGroupId,groupId)
.eq(GroupMember::getUserId,userId)
.set(GroupMember::getQuit,true);
this.update(wrapper);

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

@ -1,10 +1,14 @@
package com.bx.implatform.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bx.imclient.IMClient;
import com.bx.imcommon.contant.Constant;
import com.bx.imcommon.model.GroupMessageInfo;
import com.bx.imcommon.contant.IMConstant;
import com.bx.implatform.vo.GroupMessageVO;
import com.bx.imcommon.model.IMGroupMessage;
import com.bx.imcommon.model.IMUserInfo;
import com.bx.implatform.contant.RedisKey;
import com.bx.implatform.entity.Group;
import com.bx.implatform.entity.GroupMember;
@ -18,8 +22,9 @@ import com.bx.implatform.service.IGroupMemberService;
import com.bx.implatform.service.IGroupMessageService;
import com.bx.implatform.service.IGroupService;
import com.bx.implatform.session.SessionContext;
import com.bx.implatform.session.UserSession;
import com.bx.implatform.util.BeanUtils;
import com.bx.implatform.vo.GroupMessageVO;
import com.bx.implatform.dto.GroupMessageDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
@ -27,37 +32,31 @@ import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j
@Service
public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, GroupMessage> implements IGroupMessageService {
@Autowired
private IGroupService groupService;
@Autowired
private IGroupMemberService groupMemberService;
@Autowired
private RedisTemplate<String,Object> redisTemplate;
@Autowired
private IMClient imClient;
/**
* 发送群聊消息(与mysql所有交换都要进行缓存)
*
* @param vo
* @param dto 群聊消息
* @return 群聊id
*/
@Override
public Long sendMessage(GroupMessageVO vo) {
Long userId = SessionContext.getSession().getId();
Group group = groupService.getById(vo.getGroupId());
public Long sendMessage(GroupMessageDTO dto) {
UserSession session = SessionContext.getSession();
Group group = groupService.getById(dto.getGroupId());
if(group == null){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"群聊不存在");
}
@ -66,20 +65,24 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
}
// 判断是否在群里
List<Long> userIds = groupMemberService.findUserIdsByGroupId(group.getId());
if(!userIds.contains(userId)){
if(!userIds.contains(session.getUserId())){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"您已不在群聊里面,无法发送消息");
}
// 保存消息
GroupMessage msg = BeanUtils.copyProperties(vo, GroupMessage.class);
msg.setSendId(userId);
GroupMessage msg = BeanUtils.copyProperties(dto, GroupMessage.class);
msg.setSendId(session.getUserId());
msg.setSendTime(new Date());
this.save(msg);
// 不用发给自己
userIds = userIds.stream().filter(id->!userId.equals(id)).collect(Collectors.toList());
userIds = userIds.stream().filter(id->!session.getUserId().equals(id)).collect(Collectors.toList());
// 群发
GroupMessageInfo msgInfo = BeanUtils.copyProperties(msg, GroupMessageInfo.class);
imClient.sendGroupMessage(userIds,msgInfo);
log.info("发送群聊消息,发送id:{},群聊id:{},内容:{}",userId,vo.getGroupId(),vo.getContent());
GroupMessageVO msgInfo = BeanUtils.copyProperties(msg, GroupMessageVO.class);
IMGroupMessage<GroupMessageVO> sendMessage = new IMGroupMessage<>();
sendMessage.setSender(new IMUserInfo(session.getUserId(),session.getTerminal()));
sendMessage.setRecvIds(userIds);
sendMessage.setData(msgInfo);
imClient.sendGroupMessage(sendMessage);
log.info("发送群聊消息,发送id:{},群聊id:{},内容:{}",session.getUserId(),dto.getGroupId(),dto.getContent());
return msg.getId();
}
@ -93,19 +96,19 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
*/
@Override
public void recallMessage(Long id) {
Long userId = SessionContext.getSession().getId();
UserSession session = SessionContext.getSession();
GroupMessage msg = this.getById(id);
if(msg == null){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"消息不存在");
}
if(!msg.getSendId().equals(userId)){
if(!msg.getSendId().equals(session.getUserId())){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"这条消息不是由您发送,无法撤回");
}
if(System.currentTimeMillis() - msg.getSendTime().getTime() > Constant.ALLOW_RECALL_SECOND * 1000){
if(System.currentTimeMillis() - msg.getSendTime().getTime() > IMConstant.ALLOW_RECALL_SECOND * 1000){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"消息已发送超过5分钟,无法撤回");
}
// 判断是否在群里
GroupMember member = groupMemberService.findByGroupAndUserId(msg.getGroupId(),userId);
GroupMember member = groupMemberService.findByGroupAndUserId(msg.getGroupId(),session.getUserId());
if(member == null){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"您已不在群聊里面,无法撤回消息");
}
@ -115,54 +118,69 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
// 群发
List<Long> userIds = groupMemberService.findUserIdsByGroupId(msg.getGroupId());
// 不用发给自己
userIds = userIds.stream().filter(uid->userId.equals(uid)).collect(Collectors.toList());
GroupMessageInfo msgInfo = BeanUtils.copyProperties(msg, GroupMessageInfo.class);
msgInfo.setType(MessageType.TIP.code());
userIds = userIds.stream().filter(uid->!session.getUserId().equals(uid)).collect(Collectors.toList());
GroupMessageVO msgInfo = BeanUtils.copyProperties(msg, GroupMessageVO.class);
msgInfo.setType(MessageType.RECALL.code());
String content = String.format("'%s'撤回了一条消息",member.getAliasName());
msgInfo.setContent(content);
msgInfo.setSendTime(new Date());
imClient.sendGroupMessage(userIds,msgInfo);
log.info("撤回群聊消息,发送id:{},群聊id:{},内容:{}",userId,msg.getGroupId(),msg.getContent());
IMGroupMessage<GroupMessageVO> sendMessage = new IMGroupMessage<>();
sendMessage.setSender(new IMUserInfo(session.getUserId(),session.getTerminal()));
sendMessage.setRecvIds(userIds);
sendMessage.setData(msgInfo);
sendMessage.setSendResult(false);
sendMessage.setSendToSelf(false);
imClient.sendGroupMessage(sendMessage);
// 推给自己其他终端
msgInfo.setContent("你撤回了一条消息");
sendMessage.setSendToSelf(true);
sendMessage.setRecvIds(Collections.emptyList());
sendMessage.setRecvTerminals(Collections.emptyList());
imClient.sendGroupMessage(sendMessage);
log.info("撤回群聊消息,发送id:{},群聊id:{},内容:{}",session.getUserId(),msg.getGroupId(),msg.getContent());
}
/**
* 异步拉取群聊消息通过websocket异步推送
*
* @return
*/
@Override
public void pullUnreadMessage() {
Long userId = SessionContext.getSession().getId();
List<Long> recvIds = new LinkedList();
recvIds.add(userId);
List<GroupMember> members = groupMemberService.findByUserId(userId);
UserSession session = SessionContext.getSession();
List<GroupMember> members = groupMemberService.findByUserId(session.getUserId());
for(GroupMember member:members){
// 获取群聊已读的最大消息id,只推送未读消息
String key = RedisKey.IM_GROUP_READED_POSITION + member.getGroupId()+":"+userId;
String key = String.join(":",RedisKey.IM_GROUP_READED_POSITION,member.getGroupId().toString(),session.getUserId().toString());
Integer maxReadedId = (Integer)redisTemplate.opsForValue().get(key);
QueryWrapper<GroupMessage> wrapper = new QueryWrapper();
wrapper.lambda().eq(GroupMessage::getGroupId,member.getGroupId())
LambdaQueryWrapper<GroupMessage> wrapper = Wrappers.lambdaQuery();
wrapper.eq(GroupMessage::getGroupId,member.getGroupId())
.gt(GroupMessage::getSendTime,member.getCreatedTime())
.ne(GroupMessage::getSendId, userId)
.ne(GroupMessage::getSendId, session.getUserId())
.ne(GroupMessage::getStatus, MessageStatus.RECALL.code());
if(maxReadedId!=null){
wrapper.lambda().gt(GroupMessage::getId,maxReadedId);
wrapper.gt(GroupMessage::getId,maxReadedId);
}
wrapper.last("limit 100");
List<GroupMessage> messages = this.list(wrapper);
if(messages.isEmpty()){
continue;
}
// 组装消息,准备推送
List<GroupMessageInfo> messageInfos = messages.stream().map(m->{
GroupMessageInfo msgInfo = BeanUtils.copyProperties(m, GroupMessageInfo.class);
return msgInfo;
}).collect(Collectors.toList());
// 推送
for (GroupMessage message:messages ){
GroupMessageVO msgInfo = BeanUtils.copyProperties(message, GroupMessageVO.class);
IMGroupMessage<GroupMessageVO> sendMessage = new IMGroupMessage<>();
sendMessage.setSender(new IMUserInfo(session.getUserId(),session.getTerminal()));
// 只推给自己当前终端
sendMessage.setRecvIds(Collections.singletonList(session.getUserId()));
sendMessage.setRecvTerminals(Collections.singletonList(session.getTerminal()));
sendMessage.setData(msgInfo);
imClient.sendGroupMessage(sendMessage);
}
// 发送消息
GroupMessageInfo[] infoArr = messageInfos.toArray(new GroupMessageInfo[messageInfos.size()]);
imClient.sendGroupMessage(Collections.singletonList(userId), infoArr);
log.info("拉取未读群聊消息,用户id:{},群聊id:{},数量:{}",userId,member.getGroupId(),messageInfos.size());
log.info("拉取未读群聊消息,用户id:{},群聊id:{},数量:{}",session.getUserId(),member.getGroupId(),messages.size());
}
}
@ -176,11 +194,11 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
* @return 聊天记录列表
*/
@Override
public List<GroupMessageInfo> findHistoryMessage(Long groupId, Long page, Long size) {
public List<GroupMessageVO> findHistoryMessage(Long groupId, Long page, Long size) {
page = page > 0 ? page:1;
size = size > 0 ? size:10;
Long userId = SessionContext.getSession().getId();
Long stIdx = (page-1)* size;
Long userId = SessionContext.getSession().getUserId();
long stIdx = (page-1)* size;
// 群聊成员信息
GroupMember member = groupMemberService.findByGroupAndUserId(groupId,userId);
if(member == null || member.getQuit()){
@ -195,10 +213,7 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
.last("limit "+stIdx + ","+size);
List<GroupMessage> messages = this.list(wrapper);
List<GroupMessageInfo> messageInfos = messages.stream().map(m->{
GroupMessageInfo info = BeanUtils.copyProperties(m, GroupMessageInfo.class);
return info;
}).collect(Collectors.toList());
List<GroupMessageVO> messageInfos = messages.stream().map(m->BeanUtils.copyProperties(m, GroupMessageVO.class)).collect(Collectors.toList());
log.info("拉取群聊记录,用户id:{},群聊id:{},数量:{}",userId,groupId,messageInfos.size());
return messageInfos;
}

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

@ -1,6 +1,7 @@
package com.bx.implatform.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bx.implatform.contant.Constant;
import com.bx.implatform.contant.RedisKey;
@ -30,8 +31,8 @@ import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@ -51,46 +52,41 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
@Autowired
private IFriendService friendsService;
/**
* 创建新群聊
*
* @Param groupName 群聊名称
* @return
* @param vo 群聊信息
* @return 群聊信息
**/
@Transactional
@Override
public GroupVO createGroup(String groupName) {
public GroupVO createGroup(GroupVO vo) {
UserSession session = SessionContext.getSession();
User user = userService.getById(session.getId());
User user = userService.getById(session.getUserId());
// 保存群组数据
Group group = new Group();
group.setName(groupName);
Group group = BeanUtils.copyProperties(vo,Group.class);
group.setOwnerId(user.getId());
group.setHeadImage(user.getHeadImage());
group.setHeadImageThumb(user.getHeadImageThumb());
this.save(group);
// 把群主加入群
GroupMember groupMember = new GroupMember();
groupMember.setGroupId(group.getId());
groupMember.setUserId(user.getId());
groupMember.setAliasName(user.getNickName());
groupMember.setRemark(groupName);
groupMember.setAliasName(StringUtils.isEmpty(vo.getAliasName())?session.getNickName():vo.getAliasName());
groupMember.setRemark(StringUtils.isEmpty(vo.getRemark())?group.getName():vo.getRemark());
groupMember.setHeadImage(user.getHeadImageThumb());
groupMemberService.save(groupMember);
GroupVO vo = BeanUtils.copyProperties(group, GroupVO.class);
vo.setAliasName(user.getNickName());
vo.setRemark(groupName);
vo.setId(group.getId());
vo.setAliasName(groupMember.getAliasName());
vo.setRemark(groupMember.getRemark());
log.info("创建群聊,群聊id:{},群聊名称:{}",group.getId(),group.getName());
return vo;
}
/**
* 修改群聊信息
*
* @Param GroupVO 群聊信息
* @return
* @param vo 群聊信息
* @return 群聊信息
**/
@CacheEvict(value = "#vo.getId()")
@Transactional
@ -100,12 +96,12 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
// 校验是不是群主,只有群主能改信息
Group group = this.getById(vo.getId());
// 群主有权修改群基本信息
if(group.getOwnerId().equals(session.getId())){
if(group.getOwnerId().equals(session.getUserId()) ){
group = BeanUtils.copyProperties(vo,Group.class);
this.updateById(group);
}
// 更新成员信息
GroupMember member = groupMemberService.findByGroupAndUserId(vo.getId(),session.getId());
GroupMember member = groupMemberService.findByGroupAndUserId(vo.getId(),session.getUserId());
if(member == null){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"您不是群聊的成员");
}
@ -120,8 +116,7 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
/**
* 删除群聊
*
* @Param groupId 群聊id
* @return
* @param groupId 群聊id
**/
@Transactional
@CacheEvict(value = "#groupId")
@ -129,7 +124,7 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
public void deleteGroup(Long groupId) {
UserSession session = SessionContext.getSession();
Group group = this.getById(groupId);
if(!group.getOwnerId().equals(session.getId())){
if(!group.getOwnerId().equals(session.getUserId())){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"只有群主才有权限解除群聊");
}
// 逻辑删除群数据
@ -145,11 +140,10 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
* 退出群聊
*
* @param groupId 群聊id
* @return
*/
@Override
public void quitGroup(Long groupId) {
Long userId = SessionContext.getSession().getId();
Long userId = SessionContext.getSession().getUserId();
Group group = this.getById(groupId);
if(group.getOwnerId().equals(userId)){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"您是群主,不可退出群聊");
@ -165,16 +159,15 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
*
* @param groupId 群聊id
* @param userId 用户id
* @return
*/
@Override
public void kickGroup(Long groupId, Long userId) {
UserSession session = SessionContext.getSession();
Group group = this.getById(groupId);
if(!group.getOwnerId().equals(session.getId())){
if(!group.getOwnerId().equals(session.getUserId()) ){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"您不是群主,没有权限踢人");
}
if(userId.equals(session.getId())){
if(userId.equals(session.getUserId())){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"亲,不能自己踢自己哟");
}
// 删除群聊成员
@ -186,7 +179,7 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
public GroupVO findById(Long groupId) {
UserSession session = SessionContext.getSession();
Group group = this.getById(groupId);
GroupMember member = groupMemberService.findByGroupAndUserId(groupId,session.getId());
GroupMember member = groupMemberService.findByGroupAndUserId(groupId,session.getUserId());
if(member == null){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"您未加入群聊");
}
@ -200,11 +193,11 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
* 根据id查找群聊并进行缓存
*
* @param groupId 群聊id
* @return
* @return 群聊实体
*/
@Cacheable(value = "#groupId")
@Override
public Group GetById(Long groupId){
public Group getById(Long groupId){
Group group = super.getById(groupId);
if(group == null){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"群组不存在");
@ -220,37 +213,35 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
/**
* 查询当前用户的所有群聊
*
* @return
* @return 群聊信息列表
**/
@Override
public List<GroupVO> findGroups() {
UserSession session = SessionContext.getSession();
// 查询当前用户的群id列表
List<GroupMember> groupMembers = groupMemberService.findByUserId(session.getId());
List<GroupMember> groupMembers = groupMemberService.findByUserId(session.getUserId());
if(groupMembers.isEmpty()){
return Collections.EMPTY_LIST;
return new LinkedList<>();
}
// 拉取群列表
List<Long> ids = groupMembers.stream().map((gm -> gm.getGroupId())).collect(Collectors.toList());
QueryWrapper<Group> groupWrapper = new QueryWrapper();
groupWrapper.lambda().in(Group::getId, ids);
List<Long> ids = groupMembers.stream().map((GroupMember::getGroupId)).collect(Collectors.toList());
LambdaQueryWrapper<Group> groupWrapper = Wrappers.lambdaQuery();
groupWrapper.in(Group::getId, ids);
List<Group> groups = this.list(groupWrapper);
// 转vo
List<GroupVO> vos = groups.stream().map(g -> {
return groups.stream().map(g -> {
GroupVO vo = BeanUtils.copyProperties(g, GroupVO.class);
GroupMember member = groupMembers.stream().filter(m -> g.getId().equals(m.getGroupId())).findFirst().get();
vo.setAliasName(member.getAliasName());
vo.setRemark(member.getRemark());
return vo;
}).collect(Collectors.toList());
return vos;
}
/**
* 邀请好友进群
*
* @Param GroupInviteVO 群id好友id列表
* @return
**/
@Override
public void invite(GroupInviteVO vo) {
@ -265,9 +256,8 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
if(vo.getFriendIds().size() + size > Constant.MAX_GROUP_MEMBER){
throw new GlobalException(ResultCode.PROGRAM_ERROR, "群聊人数不能大于"+Constant.MAX_GROUP_MEMBER+"人");
}
// 找出好友信息
List<Friend> friends = friendsService.findFriendByUserId(session.getId());
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()).collect(Collectors.toList());
if (friendsList.size() != vo.getFriendIds().size()) {
@ -277,7 +267,7 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
List<GroupMember> groupMembers = friendsList.stream()
.map(f -> {
Optional<GroupMember> optional = members.stream().filter(m->m.getUserId().equals(f.getFriendId())).findFirst();
GroupMember groupMember = optional.isPresent()? optional.get():new GroupMember();
GroupMember groupMember = optional.orElseGet(GroupMember::new);
groupMember.setGroupId(vo.getGroupId());
groupMember.setUserId(f.getFriendId());
groupMember.setAliasName(f.getFriendNickName());
@ -302,11 +292,7 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
@Override
public List<GroupMemberVO> findGroupMembers(Long groupId) {
List<GroupMember> members = groupMemberService.findByGroupId(groupId);
List<GroupMemberVO> vos = members.stream().map(m->{
GroupMemberVO vo = BeanUtils.copyProperties(m,GroupMemberVO.class);
return vo;
}).collect(Collectors.toList());
return vos;
return members.stream().map(m->BeanUtils.copyProperties(m,GroupMemberVO.class)).collect(Collectors.toList());
}
}

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

@ -1,11 +1,14 @@
package com.bx.implatform.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bx.imclient.IMClient;
import com.bx.imcommon.contant.Constant;
import com.bx.imcommon.contant.RedisKey;
import com.bx.imcommon.model.PrivateMessageInfo;
import com.bx.imcommon.contant.IMConstant;
import com.bx.imcommon.model.IMPrivateMessage;
import com.bx.imcommon.model.IMUserInfo;
import com.bx.implatform.vo.PrivateMessageVO;
import com.bx.implatform.entity.PrivateMessage;
import com.bx.implatform.enums.MessageStatus;
import com.bx.implatform.enums.MessageType;
@ -15,13 +18,14 @@ import com.bx.implatform.mapper.PrivateMessageMapper;
import com.bx.implatform.service.IFriendService;
import com.bx.implatform.service.IPrivateMessageService;
import com.bx.implatform.session.SessionContext;
import com.bx.implatform.session.UserSession;
import com.bx.implatform.util.BeanUtils;
import com.bx.implatform.vo.PrivateMessageVO;
import com.bx.implatform.dto.PrivateMessageDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
@ -32,33 +36,37 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
@Autowired
private IFriendService friendService;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private IMClient imClient;
/**
* 发送私聊消息
*
* @param vo 私聊消息vo
* @param dto 私聊消息
* @return 消息id
*/
@Override
public Long sendMessage(PrivateMessageVO vo) {
Long userId = SessionContext.getSession().getId();
Boolean isFriends = friendService.isFriend(userId, vo.getRecvId());
public Long sendMessage(PrivateMessageDTO dto) {
UserSession session = SessionContext.getSession();
Boolean isFriends = friendService.isFriend(session.getUserId(), dto.getRecvId());
if (!isFriends) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "您已不是对方好友,无法发送消息");
}
// 保存消息
PrivateMessage msg = BeanUtils.copyProperties(vo, PrivateMessage.class);
msg.setSendId(userId);
PrivateMessage msg = BeanUtils.copyProperties(dto, PrivateMessage.class);
msg.setSendId(session.getUserId());
msg.setStatus(MessageStatus.UNREAD.code());
msg.setSendTime(new Date());
this.save(msg);
// 推送消息
PrivateMessageInfo msgInfo = BeanUtils.copyProperties(msg, PrivateMessageInfo.class);
imClient.sendPrivateMessage(vo.getRecvId(),msgInfo);
log.info("发送私聊消息,发送id:{},接收id:{},内容:{}", userId, vo.getRecvId(), vo.getContent());
PrivateMessageVO msgInfo = BeanUtils.copyProperties(msg, PrivateMessageVO.class);
IMPrivateMessage<PrivateMessageVO> sendMessage = new IMPrivateMessage<>();
sendMessage.setSender(new IMUserInfo(session.getUserId(),session.getTerminal()));
sendMessage.setRecvId(msgInfo.getRecvId());
sendMessage.setSendToSelf(true);
sendMessage.setData(msgInfo);
imClient.sendPrivateMessage(sendMessage);
log.info("发送私聊消息,发送id:{},接收id:{},内容:{}", session.getUserId(), dto.getRecvId(), dto.getContent());
return msg.getId();
}
@ -69,26 +77,39 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
*/
@Override
public void recallMessage(Long id) {
Long userId = SessionContext.getSession().getId();
UserSession session = SessionContext.getSession();
PrivateMessage msg = this.getById(id);
if (msg == null) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "消息不存在");
}
if (!msg.getSendId().equals(userId)) {
if (!msg.getSendId().equals(session.getUserId())) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "这条消息不是由您发送,无法撤回");
}
if (System.currentTimeMillis() - msg.getSendTime().getTime() > Constant.ALLOW_RECALL_SECOND * 1000) {
if (System.currentTimeMillis() - msg.getSendTime().getTime() > IMConstant.ALLOW_RECALL_SECOND * 1000) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "消息已发送超过5分钟,无法撤回");
}
// 修改消息状态
msg.setStatus(MessageStatus.RECALL.code());
this.updateById(msg);
// 推送消息
PrivateMessageInfo msgInfo = BeanUtils.copyProperties(msg, PrivateMessageInfo.class);
msgInfo.setType(MessageType.TIP.code());
PrivateMessageVO msgInfo = BeanUtils.copyProperties(msg, PrivateMessageVO.class);
msgInfo.setType(MessageType.RECALL.code());
msgInfo.setSendTime(new Date());
msgInfo.setContent("对方撤回了一条消息");
imClient.sendPrivateMessage(msgInfo.getRecvId(),msgInfo);
IMPrivateMessage<PrivateMessageVO> sendMessage = new IMPrivateMessage<>();
sendMessage.setSender(new IMUserInfo(session.getUserId(),session.getTerminal()));
sendMessage.setRecvId(msgInfo.getRecvId());
sendMessage.setSendToSelf(false);
sendMessage.setData(msgInfo);
sendMessage.setSendResult(false);
imClient.sendPrivateMessage(sendMessage);
// 推给自己其他终端
msgInfo.setContent("你撤回了一条消息");
sendMessage.setSendToSelf(true);
sendMessage.setRecvTerminals(Collections.emptyList());
imClient.sendPrivateMessage(sendMessage);
log.info("撤回私聊消息,发送id:{},接收id:{},内容:{}", msg.getSendId(), msg.getRecvId(), msg.getContent());
}
@ -102,11 +123,11 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
* @return 聊天记录列表
*/
@Override
public List<PrivateMessageInfo> findHistoryMessage(Long friendId, Long page, Long size) {
public List<PrivateMessageVO> findHistoryMessage(Long friendId, Long page, Long size) {
page = page > 0 ? page : 1;
size = size > 0 ? size : 10;
Long userId = SessionContext.getSession().getId();
Long stIdx = (page - 1) * size;
Long userId = SessionContext.getSession().getUserId();
long stIdx = (page - 1) * size;
QueryWrapper<PrivateMessage> wrapper = new QueryWrapper<>();
wrapper.lambda().and(wrap -> wrap.and(
wp -> wp.eq(PrivateMessage::getSendId, userId)
@ -118,10 +139,7 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
.last("limit " + stIdx + "," + size);
List<PrivateMessage> messages = this.list(wrapper);
List<PrivateMessageInfo> messageInfos = messages.stream().map(m -> {
PrivateMessageInfo info = BeanUtils.copyProperties(m, PrivateMessageInfo.class);
return info;
}).collect(Collectors.toList());
List<PrivateMessageVO> messageInfos = messages.stream().map(m -> BeanUtils.copyProperties(m, PrivateMessageVO.class)).collect(Collectors.toList());
log.info("拉取聊天记录,用户id:{},好友id:{},数量:{}", userId, friendId, messageInfos.size());
return messageInfos;
}
@ -129,30 +147,32 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
/**
* 异步拉取私聊消息通过websocket异步推送
*
* @return
*/
@Override
public void pullUnreadMessage() {
UserSession session = SessionContext.getSession();
// 获取当前连接的channelId
Long userId = SessionContext.getSession().getId();
if (!imClient.isOnline(userId)) {
if (!imClient.isOnline(session.getUserId())) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "用户未建立连接");
}
// 获取当前用户所有未读消息
QueryWrapper<PrivateMessage> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().eq(PrivateMessage::getRecvId, userId)
LambdaQueryWrapper<PrivateMessage> queryWrapper = Wrappers.lambdaQuery();
queryWrapper.eq(PrivateMessage::getRecvId, session.getUserId())
.eq(PrivateMessage::getStatus, MessageStatus.UNREAD);
List<PrivateMessage> messages = this.list(queryWrapper);
// 上传至redis,等待推送
if (!messages.isEmpty()) {
List<PrivateMessageInfo> messageInfos = messages.stream().map(m -> {
PrivateMessageInfo msgInfo = BeanUtils.copyProperties(m, PrivateMessageInfo.class);
return msgInfo;
}).collect(Collectors.toList());
for(PrivateMessage message:messages){
PrivateMessageVO msgInfo = BeanUtils.copyProperties(message, PrivateMessageVO.class);
// 推送消息
PrivateMessageInfo[] infoArr = messageInfos.toArray(new PrivateMessageInfo[messageInfos.size()]);
imClient.sendPrivateMessage(userId,infoArr);
log.info("拉取未读私聊消息,用户id:{},数量:{}", userId, infoArr.length);
IMPrivateMessage<PrivateMessageVO> sendMessage = new IMPrivateMessage<>();
sendMessage.setSender(new IMUserInfo(session.getUserId(),session.getTerminal()));
sendMessage.setRecvId(session.getUserId());
sendMessage.setRecvTerminals(Collections.singletonList(session.getTerminal()));
sendMessage.setSendToSelf(false);
sendMessage.setData(msgInfo);
imClient.sendPrivateMessage(sendMessage);
}
log.info("拉取未读私聊消息,用户id:{},数量:{}", session.getUserId(), messages.size());
}
}

74
im-platform/src/main/java/com/bx/implatform/service/impl/UserServiceImpl.java

@ -1,11 +1,13 @@
package com.bx.implatform.service.impl;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bx.imclient.IMClient;
import com.bx.imcommon.contant.RedisKey;
import com.bx.implatform.config.JwtProperties;
import com.bx.implatform.dto.ModifyPwdDTO;
import com.bx.implatform.entity.Friend;
import com.bx.implatform.entity.GroupMember;
import com.bx.implatform.entity.User;
@ -65,7 +67,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IU
@Override
public LoginVO login(LoginDTO dto) {
User user = findUserByName(dto.getUserName());
User user = this.findUserByUserName(dto.getUserName());
if(null == user){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"用户不存在");
}
@ -74,6 +76,8 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IU
}
// 生成token
UserSession session = BeanUtils.copyProperties(user,UserSession.class);
session.setUserId(user.getId());
session.setTerminal(dto.getTerminal());
String strJson = JSON.toJSONString(session);
String accessToken = JwtUtil.sign(user.getId(),strJson,jwtProperties.getAccessTokenExpireIn(),jwtProperties.getAccessTokenSecret());
String refreshToken = JwtUtil.sign(user.getId(),strJson,jwtProperties.getRefreshTokenExpireIn(),jwtProperties.getRefreshTokenSecret());
@ -85,6 +89,9 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IU
return vo;
}
/**
* 用refreshToken换取新 token
*
@ -94,7 +101,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IU
@Override
public LoginVO refreshToken(String refreshToken) {
//验证 token
if(JwtUtil.checkSign(refreshToken, jwtProperties.getRefreshTokenSecret())){
if(!JwtUtil.checkSign(refreshToken, jwtProperties.getRefreshTokenSecret())){
throw new GlobalException("refreshToken无效或已过期");
}
String strJson = JwtUtil.getInfo(refreshToken);
@ -113,11 +120,10 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IU
* 用户注册
*
* @param dto 注册dto
* @return
*/
@Override
public void register(RegisterDTO dto) {
User user = findUserByName(dto.getUserName());
User user = this.findUserByUserName(dto.getUserName());
if(null != user){
throw new GlobalException(ResultCode.USERNAME_ALREADY_REGISTER);
}
@ -127,6 +133,19 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IU
log.info("注册用户,用户id:{},用户名:{},昵称:{}",user.getId(),dto.getUserName(),dto.getNickName());
}
@Override
public void modifyPassword(ModifyPwdDTO dto) {
UserSession session = SessionContext.getSession();
User user = this.getById(session.getUserId());
if(!passwordEncoder.matches(dto.getOldPassword(),user.getPassword())){
throw new GlobalException("旧密码不正确");
}
user.setPassword(passwordEncoder.encode(dto.getNewPassword()));
this.updateById(user);
log.info("用户修改密码,用户id:{},用户名:{},昵称:{}",user.getId(),user.getUserName(),user.getNickName());
}
/**
* 根据用户名查询用户
*
@ -134,23 +153,23 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IU
* @return
*/
@Override
public User findUserByName(String username) {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().eq(User::getUserName,username);
public User findUserByUserName(String username) {
LambdaQueryWrapper<User> queryWrapper = Wrappers.lambdaQuery();
queryWrapper.eq(User::getUserName,username);
return this.getOne(queryWrapper);
}
/**
* 更新用户信息好友昵称和群聊昵称等冗余信息也会更新
*
* @param vo 用户信息vo
* @return
*/
@Transactional
@Override
public void update(UserVO vo) {
UserSession session = SessionContext.getSession();
if(!session.getId().equals(vo.getId()) ){
if(!session.getUserId().equals(vo.getId()) ){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"不允许修改其他用户的信息!");
}
User user = this.getById(vo.getId());
@ -160,7 +179,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IU
// 更新好友昵称和头像
if(!user.getNickName().equals(vo.getNickName()) || !user.getHeadImageThumb().equals(vo.getHeadImageThumb())){
QueryWrapper<Friend> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().eq(Friend::getFriendId,session.getId());
queryWrapper.lambda().eq(Friend::getFriendId,session.getUserId());
List<Friend> friends = friendService.list(queryWrapper);
for(Friend friend: friends){
friend.setFriendNickName(vo.getNickName());
@ -170,7 +189,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IU
}
// 更新群聊中的头像
if(!user.getHeadImageThumb().equals(vo.getHeadImageThumb())){
List<GroupMember> members = groupMemberService.findByUserId(session.getId());
List<GroupMember> members = groupMemberService.findByUserId(session.getUserId());
for(GroupMember member:members){
member.setHeadImage(vo.getHeadImageThumb());
}
@ -183,7 +202,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IU
user.setHeadImage(vo.getHeadImage());
user.setHeadImageThumb(vo.getHeadImageThumb());
this.updateById(user);
log.info("用户信息更新,用户:{}}",user.toString());
log.info("用户信息更新,用户:{}}", user);
}
@ -195,19 +214,36 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IU
*/
@Override
public List<UserVO> findUserByNickName(String nickname) {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda()
.like(User::getNickName,nickname)
.last("limit 20");
LambdaQueryWrapper<User> queryWrapper = Wrappers.lambdaQuery();
queryWrapper.like(User::getNickName,nickname).last("limit 20");
List<User> users = this.list(queryWrapper);
List<UserVO> vos = users.stream().map(u-> {
return users.stream().map(u-> {
UserVO vo = BeanUtils.copyProperties(u,UserVO.class);
vo.setOnline(imClient.isOnline(u.getId()));
return vo;
}).collect(Collectors.toList());
return vos;
}
/**
* 根据用户昵称查询用户最多返回20条数据
*
* @param name 用户名或昵称
* @return
*/
@Override
public List<UserVO> findUserByName(String name) {
LambdaQueryWrapper<User> queryWrapper = Wrappers.lambdaQuery();
queryWrapper.like(User::getUserName,name)
.or()
.like(User::getNickName,name)
.last("limit 20");
List<User> users = this.list(queryWrapper);
return users.stream().map(u-> {
UserVO vo = BeanUtils.copyProperties(u,UserVO.class);
vo.setOnline(imClient.isOnline(u.getId()));
return vo;
}).collect(Collectors.toList());
}
/**
* 判断用户是否在线返回在线的用户id列表

245
im-platform/src/main/java/com/bx/implatform/service/impl/WebrtcServiceImpl.java

@ -0,0 +1,245 @@
package com.bx.implatform.service.impl;
import com.bx.imclient.IMClient;
import com.bx.imcommon.model.IMPrivateMessage;
import com.bx.imcommon.model.IMUserInfo;
import com.bx.implatform.vo.PrivateMessageVO;
import com.bx.implatform.config.ICEServer;
import com.bx.implatform.config.ICEServerConfig;
import com.bx.implatform.contant.RedisKey;
import com.bx.implatform.enums.MessageType;
import com.bx.implatform.exception.GlobalException;
import com.bx.implatform.service.IWebrtcService;
import com.bx.implatform.session.SessionContext;
import com.bx.implatform.session.UserSession;
import com.bx.implatform.session.WebrtcSession;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestBody;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
@Slf4j
@Service
public class WebrtcServiceImpl implements IWebrtcService {
@Autowired
private IMClient imClient;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private ICEServerConfig iceServerConfig;
@Override
public void call(Long uid, String offer) {
UserSession session = SessionContext.getSession();
if (!imClient.isOnline(uid)) {
throw new GlobalException("对方目前不在线");
}
// 创建webrtc会话
WebrtcSession webrtcSession = new WebrtcSession();
webrtcSession.setCallerId(session.getUserId());
webrtcSession.setCallerTerminal(session.getTerminal());
String key = getSessionKey(session.getUserId(), uid);
redisTemplate.opsForValue().set(key, webrtcSession, 12, TimeUnit.HOURS);
// 向对方所有终端发起呼叫
PrivateMessageVO messageInfo = new PrivateMessageVO();
messageInfo.setType(MessageType.RTC_CALL.code());
messageInfo.setRecvId(uid);
messageInfo.setSendId(session.getUserId());
messageInfo.setContent(offer);
IMPrivateMessage<PrivateMessageVO> sendMessage = new IMPrivateMessage<>();
sendMessage.setSender(new IMUserInfo(session.getUserId(),session.getTerminal()));
sendMessage.setRecvId(uid);
sendMessage.setSendToSelf(false);
sendMessage.setSendResult(false);
sendMessage.setData(messageInfo);
imClient.sendPrivateMessage(sendMessage);
}
@Override
public void accept(Long uid, @RequestBody String answer) {
UserSession session = SessionContext.getSession();
// 查询webrtc会话
WebrtcSession webrtcSession = getWebrtcSession(session.getUserId(), uid);
// 更新接受者信息
webrtcSession.setAcceptorId(session.getUserId());
webrtcSession.setAcceptorTerminal(session.getTerminal());
String key = getSessionKey(session.getUserId(), uid);
redisTemplate.opsForValue().set(key, webrtcSession, 12, TimeUnit.HOURS);
// 向发起人推送接受通话信令
PrivateMessageVO messageInfo = new PrivateMessageVO();
messageInfo.setType(MessageType.RTC_ACCEPT.code());
messageInfo.setRecvId(uid);
messageInfo.setSendId(session.getUserId());
messageInfo.setContent(answer);
IMPrivateMessage<PrivateMessageVO> sendMessage = new IMPrivateMessage<>();
sendMessage.setSender(new IMUserInfo(session.getUserId(),session.getTerminal()));
sendMessage.setRecvId(uid);
// 告知其他终端已经接受会话,中止呼叫
sendMessage.setSendToSelf(true);
sendMessage.setSendResult(false);
sendMessage.setRecvTerminals((Collections.singletonList(webrtcSession.getCallerTerminal())));
sendMessage.setData(messageInfo);
imClient.sendPrivateMessage(sendMessage);
}
@Override
public void reject(Long uid) {
UserSession session = SessionContext.getSession();
// 查询webrtc会话
WebrtcSession webrtcSession = getWebrtcSession(session.getUserId(), uid);
// 删除会话信息
removeWebrtcSession(uid, session.getUserId());
// 向发起人推送拒绝通话信令
PrivateMessageVO messageInfo = new PrivateMessageVO();
messageInfo.setType(MessageType.RTC_REJECT.code());
messageInfo.setRecvId(uid);
messageInfo.setSendId(session.getUserId());
IMPrivateMessage<PrivateMessageVO> sendMessage = new IMPrivateMessage<>();
sendMessage.setSender(new IMUserInfo(session.getUserId(),session.getTerminal()));
sendMessage.setRecvId(uid);
// 告知其他终端已经拒绝会话,中止呼叫
sendMessage.setSendToSelf(true);
sendMessage.setSendResult(false);
sendMessage.setRecvTerminals(Collections.singletonList(webrtcSession.getCallerTerminal()));
sendMessage.setData(messageInfo);
imClient.sendPrivateMessage(sendMessage);
}
@Override
public void cancel(Long uid) {
UserSession session = SessionContext.getSession();
// 删除会话信息
removeWebrtcSession(session.getUserId(), uid);
// 向对方所有终端推送取消通话信令
PrivateMessageVO messageInfo = new PrivateMessageVO();
messageInfo.setType(MessageType.RTC_ACCEPT.code());
messageInfo.setRecvId(uid);
messageInfo.setSendId(session.getUserId());
IMPrivateMessage<PrivateMessageVO> sendMessage = new IMPrivateMessage<>();
sendMessage.setSender(new IMUserInfo(session.getUserId(),session.getTerminal()));
sendMessage.setRecvId(uid);
sendMessage.setSendToSelf(false);
sendMessage.setSendResult(false);
sendMessage.setData(messageInfo);
// 通知对方取消会话
imClient.sendPrivateMessage(sendMessage);
}
@Override
public void failed(Long uid, String reason) {
UserSession session = SessionContext.getSession();
// 查询webrtc会话
WebrtcSession webrtcSession = getWebrtcSession(session.getUserId(), uid);
// 删除会话信息
removeWebrtcSession(uid, session.getUserId());
// 向发起方推送通话失败信令
PrivateMessageVO messageInfo = new PrivateMessageVO();
messageInfo.setType(MessageType.RTC_FAILED.code());
messageInfo.setRecvId(uid);
messageInfo.setSendId(session.getUserId());
IMPrivateMessage<PrivateMessageVO> sendMessage = new IMPrivateMessage<>();
sendMessage.setSender(new IMUserInfo(session.getUserId(),session.getTerminal()));
sendMessage.setRecvId(uid);
// 告知其他终端已经会话失败,中止呼叫
sendMessage.setSendToSelf(true);
sendMessage.setSendResult(false);
sendMessage.setRecvTerminals(Collections.singletonList(webrtcSession.getCallerTerminal()));
sendMessage.setData(messageInfo);
// 通知对方取消会话
imClient.sendPrivateMessage(sendMessage);
}
@Override
public void leave(Long uid) {
UserSession session = SessionContext.getSession();
// 查询webrtc会话
WebrtcSession webrtcSession = getWebrtcSession(session.getUserId(), uid);
// 删除会话信息
removeWebrtcSession(uid, session.getUserId());
// 向对方推送挂断通话信令
PrivateMessageVO messageInfo = new PrivateMessageVO();
messageInfo.setType(MessageType.RTC_HANDUP.code());
messageInfo.setRecvId(uid);
messageInfo.setSendId(session.getUserId());
IMPrivateMessage<PrivateMessageVO> sendMessage = new IMPrivateMessage<>();
sendMessage.setSender(new IMUserInfo(session.getUserId(),session.getTerminal()));
sendMessage.setRecvId(uid);
sendMessage.setSendToSelf(false);
sendMessage.setSendResult(false);
Integer terminal = getTerminalType(uid, webrtcSession);
sendMessage.setRecvTerminals(Collections.singletonList(terminal));
sendMessage.setData(messageInfo);
// 通知对方取消会话
imClient.sendPrivateMessage(sendMessage);
}
@Override
public void candidate(Long uid, String candidate) {
UserSession session = SessionContext.getSession();
// 查询webrtc会话
WebrtcSession webrtcSession = getWebrtcSession(session.getUserId(), uid);
// 向发起方推送同步candidate信令
PrivateMessageVO messageInfo = new PrivateMessageVO();
messageInfo.setType(MessageType.RTC_CANDIDATE.code());
messageInfo.setRecvId(uid);
messageInfo.setSendId(session.getUserId());
messageInfo.setContent(candidate);
IMPrivateMessage<PrivateMessageVO> sendMessage = new IMPrivateMessage<>();
sendMessage.setSender(new IMUserInfo(session.getUserId(),session.getTerminal()));
sendMessage.setRecvId(uid);
sendMessage.setSendToSelf(false);
sendMessage.setSendResult(false);
Integer terminal = getTerminalType(uid, webrtcSession);
sendMessage.setRecvTerminals(Collections.singletonList(terminal));
sendMessage.setData(messageInfo);
imClient.sendPrivateMessage(sendMessage);
}
@Override
public List<ICEServer> getIceServers() {
return iceServerConfig.getIceServers();
}
private WebrtcSession getWebrtcSession(Long userId, Long uid) {
String key = getSessionKey(userId, uid);
WebrtcSession webrtcSession = (WebrtcSession)redisTemplate.opsForValue().get(key);
if (webrtcSession == null) {
throw new GlobalException("视频通话已结束");
}
return webrtcSession;
}
private void removeWebrtcSession(Long userId, Long uid) {
String key = getSessionKey(userId, uid);
redisTemplate.delete(key);
}
private String getSessionKey(Long id1, Long id2) {
Long minId = id1 > id2 ? id2 : id1;
Long maxId = id1 > id2 ? id1 : id2;
return String.join(":", RedisKey.IM_WEBRTC_SESSION, minId.toString(), maxId.toString());
}
private Integer getTerminalType(Long uid, WebrtcSession webrtcSession) {
if (uid.equals(webrtcSession.getCallerId())) {
return webrtcSession.getCallerTerminal();
}
return webrtcSession.getAcceptorTerminal();
}
}

4
im-platform/src/main/java/com/bx/implatform/service/thirdparty/FileService.java

@ -54,7 +54,7 @@ public class FileService {
public String uploadFile(MultipartFile file){
Long userId = SessionContext.getSession().getId();
Long userId = SessionContext.getSession().getUserId();
// 大小校验
if(file.getSize() > Constant.MAX_FILE_SIZE){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"文件大小不能超过10M");
@ -71,7 +71,7 @@ public class FileService {
public UploadImageVO uploadImage(MultipartFile file){
try {
Long userId = SessionContext.getSession().getId();
Long userId = SessionContext.getSession().getUserId();
// 大小校验
if(file.getSize() > Constant.MAX_IMAGE_SIZE){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"图片大小不能超过5M");

13
im-platform/src/main/java/com/bx/implatform/session/UserSession.java

@ -1,11 +1,20 @@
package com.bx.implatform.session;
import com.bx.imcommon.model.IMSessionInfo;
import lombok.Data;
@Data
public class UserSession {
public class UserSession extends IMSessionInfo {
private Long id;
/*
* 用户名称
*/
private String userName;
/*
* 用户昵称
*/
private String nickName;
}

32
im-platform/src/main/java/com/bx/implatform/session/WebrtcSession.java

@ -0,0 +1,32 @@
package com.bx.implatform.session;
import com.bx.imcommon.enums.IMTerminalType;
import io.swagger.models.auth.In;
import lombok.Data;
/*
* webrtc 会话信息
* @Author Blue
* @Date 2022/10/21
*/
@Data
public class WebrtcSession {
/**
* 发起者id
*/
private Long callerId;
/**
* 发起者终端类型
*/
private Integer callerTerminal;
/**
* 接受者id
*/
private Long acceptorId;
/**
* 接受者终端类型
*/
private Integer acceptorTerminal;
}

1081
im-platform/src/main/java/com/bx/implatform/util/DateTimeUtils.java

File diff suppressed because it is too large

40
im-platform/src/main/java/com/bx/implatform/vo/GroupMessageVO.java

@ -1,28 +1,42 @@
package com.bx.implatform.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import com.bx.imcommon.serializer.DateToLongSerializer;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.Date;
@Data
@ApiModel("群聊消息VO")
public class GroupMessageVO {
@NotNull(message="群聊id不可为空")
@ApiModelProperty(value = "群聊id")
/*
* 消息id
*/
private Long id;
/*
* 群聊id
*/
private Long groupId;
/*
* 发送者id
*/
private Long sendId;
@Length(max=1024,message = "内容长度不得大于1024")
@NotEmpty(message="发送内容不可为空")
@ApiModelProperty(value = "发送内容")
/*
* 消息内容
*/
private String content;
@NotNull(message="消息类型不可为空")
@ApiModelProperty(value = "消息类型")
/*
* 消息内容类型 具体枚举值由应用层定义
*/
private Integer type;
/**
* 发送时间
*/
@JsonSerialize(using = DateToLongSerializer.class)
private Date sendTime;
}

3
im-platform/src/main/java/com/bx/implatform/vo/GroupVO.java

@ -12,7 +12,7 @@ import javax.validation.constraints.NotNull;
@ApiModel("群信息VO")
public class GroupVO {
@NotNull(message = "群id不可为空")
@ApiModelProperty(value = "群id")
private Long id;
@ -21,7 +21,6 @@ public class GroupVO {
@ApiModelProperty(value = "群名称")
private String name;
@NotNull(message = "群主id不可为空")
@ApiModelProperty(value = "群主id")
private Long ownerId;

1
im-platform/src/main/java/com/bx/implatform/vo/LoginVO.java

@ -19,4 +19,5 @@ public class LoginVO {
@ApiModelProperty(value = "refreshToken过期时间(秒)")
private Integer refreshTokenExpiresIn;
}

41
im-platform/src/main/java/com/bx/implatform/vo/PrivateMessageVO.java

@ -1,31 +1,42 @@
package com.bx.implatform.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import com.bx.imcommon.serializer.DateToLongSerializer;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.Date;
@Data
@ApiModel("私聊消息VO")
public class PrivateMessageVO {
/*
* 消息id
*/
private long id;
@NotNull(message="接收用户id不可为空")
@ApiModelProperty(value = "接收用户id")
private Long recvId;
/*
* 发送者id
*/
private Long sendId;
/*
* 接收者id
*/
private Long recvId;
@Length(max=1024,message = "内容长度不得大于1024")
@NotEmpty(message="发送内容不可为空")
@ApiModelProperty(value = "发送内容")
/*
* 发送内容
*/
private String content;
@NotNull(message="消息类型不可为空")
@ApiModelProperty(value = "消息类型")
/*
* 消息内容类型 IMCmdType
*/
private Integer type;
/**
* 发送时间
*/
@JsonSerialize(using = DateToLongSerializer.class)
private Date sendTime;
}

6
im-platform/src/main/java/com/bx/implatform/vo/UserVO.java

@ -30,7 +30,10 @@ public class UserVO {
@ApiModelProperty(value = "性别")
private Integer sex;
@Length(max = 64,message = "个性签名不能大于1024个字符")
@ApiModelProperty(value = "用户类型 1:普通用户 2:审核账户")
private Integer type;
@Length(max = 1024,message = "个性签名不能大于1024个字符")
@ApiModelProperty(value = "个性签名")
private String signature;
@ -40,7 +43,6 @@ public class UserVO {
@ApiModelProperty(value = "头像缩略图")
private String headImageThumb;
@ApiModelProperty(value = "是否在线")
private Boolean online;

7
im-platform/src/main/resources/db/db.sql

@ -6,7 +6,8 @@ create table `im_user`(
`head_image` varchar(255) default '' comment '用户头像',
`head_image_thumb` varchar(255) default '' comment '用户头像缩略图',
`password` varchar(255) not null comment '密码(明文)',
`sex` tinyint(1) default 0 comment '性别 0:男 1::女',
`sex` tinyint(1) default 0 comment '性别 0:男 1:女',
`type` smallint default 1 comment '用户类型 1:普通用户 2:审核账户',
`signature` varchar(1024) default '' comment '个性签名',
`last_login_time` datetime DEFAULT null comment '最后登录时间',
`created_time` datetime DEFAULT CURRENT_TIMESTAMP comment '创建时间',
@ -45,8 +46,8 @@ create table `im_group`(
`head_image_thumb` varchar(255) default '' comment '群头像缩略图',
`notice` varchar(1024) default '' comment '群公告',
`remark` varchar(255) default '' comment '群备注',
`deleted` tinyint(1) DEFAULT 0 comment '是否已删除',
`created_time` datetime DEFAULT CURRENT_TIMESTAMP comment '创建时间'
`deleted` tinyint(1) default 0 comment '是否已删除',
`created_time` datetime default CURRENT_TIMESTAMP comment '创建时间'
)ENGINE=InnoDB CHARSET=utf8mb3 comment '';
create table `im_group_member`(

1
im-server/src/main/java/com/bx/imserver/IMServerApp.java

@ -19,5 +19,4 @@ public class IMServerApp {
SpringApplication.run(IMServerApp.class,args);
}
}

23
im-server/src/main/java/com/bx/imserver/config/RedisConfig.java

@ -1,5 +1,6 @@
package com.bx.imserver.config;
import com.alibaba.fastjson.support.spring.FastJsonRedisSerializer;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
@ -26,9 +27,9 @@ public class RedisConfig {
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate();
redisTemplate.setConnectionFactory(redisConnectionFactory);
// 设置值(value)的序列化采用jackson2JsonRedisSerializer
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer());
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer());
// 设置值(value)的序列化采用FastJsonRedisSerializer
redisTemplate.setValueSerializer(fastJsonRedisSerializer());
redisTemplate.setHashValueSerializer(fastJsonRedisSerializer());
// 设置键(key)的序列化采用StringRedisSerializer。
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
@ -36,17 +37,11 @@ public class RedisConfig {
return redisTemplate;
}
@Bean
public Jackson2JsonRedisSerializer jackson2JsonRedisSerializer(){
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 解决jackson2无法反序列化LocalDateTime的问题
om.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
om.registerModule(new JavaTimeModule());
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
jackson2JsonRedisSerializer.setObjectMapper(om);
return jackson2JsonRedisSerializer;
public FastJsonRedisSerializer fastJsonRedisSerializer(){
FastJsonRedisSerializer <Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
return fastJsonRedisSerializer;
}
}

12
im-server/src/main/java/com/bx/imserver/constant/ChannelAttrKey.java

@ -0,0 +1,12 @@
package com.bx.imserver.constant;
public class ChannelAttrKey {
// 用户ID
public static final String USER_ID = "USER_ID";
// 终端类型
public static final String TERMINAL_TYPE = "TERMINAL_TYPE";
// 心跳次数
public static final String HEARTBEAT_TIMES = "HEARTBEAt_TIMES";
}

25
im-server/src/main/java/com/bx/imserver/netty/IMChannelHandler.java

@ -1,9 +1,10 @@
package com.bx.imserver.netty;
import com.bx.imcommon.contant.RedisKey;
import com.bx.imcommon.contant.IMRedisKey;
import com.bx.imcommon.enums.IMCmdType;
import com.bx.imcommon.model.IMSendInfo;
import com.bx.imserver.netty.processor.MessageProcessor;
import com.bx.imserver.constant.ChannelAttrKey;
import com.bx.imserver.netty.processor.AbstractMessageProcessor;
import com.bx.imserver.netty.processor.ProcessorFactory;
import com.bx.imserver.util.SpringContextHolder;
import io.netty.channel.ChannelHandlerContext;
@ -33,7 +34,7 @@ public class IMChannelHandler extends SimpleChannelInboundHandler<IMSendInfo> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, IMSendInfo sendInfo) throws Exception {
// 创建处理器进行处理
MessageProcessor processor = ProcessorFactory.createProcessor(IMCmdType.fromCode(sendInfo.getCmd()));
AbstractMessageProcessor processor = ProcessorFactory.createProcessor(IMCmdType.fromCode(sendInfo.getCmd()));
processor.process(ctx,processor.transForm(sendInfo.getData()));
}
@ -64,18 +65,20 @@ public class IMChannelHandler extends SimpleChannelInboundHandler<IMSendInfo> {
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
AttributeKey<Long> attr = AttributeKey.valueOf("USER_ID");
Long userId = ctx.channel().attr(attr).get();
ChannelHandlerContext context = UserChannelCtxMap.getChannelCtx(userId);
AttributeKey<Long> userIdAttr = AttributeKey.valueOf(ChannelAttrKey.USER_ID);
Long userId = ctx.channel().attr(userIdAttr).get();
AttributeKey<Integer> terminalAttr = AttributeKey.valueOf(ChannelAttrKey.TERMINAL_TYPE);
Integer terminal = ctx.channel().attr(terminalAttr).get();
ChannelHandlerContext context = UserChannelCtxMap.getChannelCtx(userId,terminal);
// 判断一下,避免异地登录导致的误删
if(context != null && ctx.channel().id().equals(context.channel().id())){
// 移除channel
UserChannelCtxMap.removeChannelCtx(userId);
UserChannelCtxMap.removeChannelCtx(userId,terminal);
// 用户下线
RedisTemplate redisTemplate = SpringContextHolder.getBean("redisTemplate");
String key = RedisKey.IM_USER_SERVER_ID + userId;
String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID,userId.toString(), terminal.toString());
redisTemplate.delete(key);
log.info("断开连接,userId:{}",userId);
log.info("断开连接,userId:{},终端类型:{}",userId,terminal);
}
}
@ -87,7 +90,9 @@ public class IMChannelHandler extends SimpleChannelInboundHandler<IMSendInfo> {
// 在规定时间内没有收到客户端的上行数据, 主动断开连接
AttributeKey<Long> attr = AttributeKey.valueOf("USER_ID");
Long userId = ctx.channel().attr(attr).get();
log.info("心跳超时,即将断开连接,用户id:{} ",userId);
AttributeKey<Integer> terminalAttr = AttributeKey.valueOf(ChannelAttrKey.TERMINAL_TYPE);
Integer ternimal = ctx.channel().attr(terminalAttr).get();
log.info("心跳超时,即将断开连接,用户id:{},终端类型:{} ",userId,ternimal);
ctx.channel().close();
}
} else {

4
im-server/src/main/java/com/bx/imserver/netty/IMServerGroup.java

@ -1,6 +1,6 @@
package com.bx.imserver.netty;
import com.bx.imcommon.contant.RedisKey;
import com.bx.imcommon.contant.IMRedisKey;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
@ -39,7 +39,7 @@ public class IMServerGroup implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
// 初始化SERVER_ID
String key = RedisKey.IM_MAX_SERVER_ID;
String key = IMRedisKey.IM_MAX_SERVER_ID;
serverId = redisTemplate.opsForValue().increment(key,1);
// 启动服务
for(IMServer imServer:imServers){

30
im-server/src/main/java/com/bx/imserver/netty/UserChannelCtxMap.java

@ -2,6 +2,7 @@ package com.bx.imserver.netty;
import io.netty.channel.ChannelHandlerContext;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@ -9,21 +10,34 @@ import java.util.concurrent.ConcurrentHashMap;
public class UserChannelCtxMap {
/*
* 维护userId和ctx的关联关系格式:Map<userId,ctx>
* 维护userId和ctx的关联关系格式:Map<userId,map<terminalctx>>
*/
private static Map<Long, ChannelHandlerContext> channelMap = new ConcurrentHashMap();
private static Map<Long, Map<Integer,ChannelHandlerContext>> channelMap = new ConcurrentHashMap();
public static void addChannelCtx(Long userId,ChannelHandlerContext ctx){
channelMap.put(userId,ctx);
public static void addChannelCtx(Long userId,Integer channel,ChannelHandlerContext ctx){
channelMap.computeIfAbsent(userId,key -> new ConcurrentHashMap()).put(channel,ctx);
}
public static void removeChannelCtx(Long userId){
if(userId != null){
channelMap.remove(userId);
public static void removeChannelCtx(Long userId,Integer terminal){
if(userId != null && terminal != null && channelMap.containsKey(userId)){
Map<Integer,ChannelHandlerContext> userChannelMap = channelMap.get(userId);
if(userChannelMap.containsKey(terminal)){
userChannelMap.remove(terminal);
}
}
}
public static ChannelHandlerContext getChannelCtx(Long userId,Integer terminal){
if(userId != null && terminal != null && channelMap.containsKey(userId)){
Map<Integer,ChannelHandlerContext> userChannelMap = channelMap.get(userId);
if(userChannelMap.containsKey(terminal)){
return userChannelMap.get(terminal);
}
}
return null;
}
public static ChannelHandlerContext getChannelCtx(Long userId){
public static Map<Integer,ChannelHandlerContext> getChannelCtx(Long userId){
if(userId == null){
return null;
}

2
im-server/src/main/java/com/bx/imserver/netty/processor/MessageProcessor.java → im-server/src/main/java/com/bx/imserver/netty/processor/AbstractMessageProcessor.java

@ -3,7 +3,7 @@ package com.bx.imserver.netty.processor;
import io.netty.channel.ChannelHandlerContext;
public abstract class MessageProcessor<T> {
public abstract class AbstractMessageProcessor<T> {
public void process(ChannelHandlerContext ctx,T data){}

62
im-server/src/main/java/com/bx/imserver/netty/processor/GroupMessageProcessor.java

@ -1,12 +1,12 @@
package com.bx.imserver.netty.processor;
import com.bx.imcommon.contant.RedisKey;
import com.bx.imcommon.contant.IMRedisKey;
import com.bx.imcommon.enums.IMCmdType;
import com.bx.imcommon.enums.IMSendCode;
import com.bx.imcommon.model.GroupMessageInfo;
import com.bx.imcommon.model.IMRecvInfo;
import com.bx.imcommon.model.IMSendInfo;
import com.bx.imcommon.model.SendResult;
import com.bx.imcommon.model.IMUserInfo;
import com.bx.imcommon.model.IMSendResult;
import com.bx.imserver.netty.UserChannelCtxMap;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;
@ -19,55 +19,53 @@ import java.util.List;
@Slf4j
@Component
public class GroupMessageProcessor extends MessageProcessor<IMRecvInfo<GroupMessageInfo>> {
public class GroupMessageProcessor extends AbstractMessageProcessor<IMRecvInfo> {
@Autowired
private RedisTemplate<String,Object> redisTemplate;
@Async
@Override
public void process(IMRecvInfo<GroupMessageInfo> recvInfo) {
GroupMessageInfo messageInfo = recvInfo.getData();
List<Long> recvIds = recvInfo.getRecvIds();
log.info("接收到群消息,发送者:{},群id:{},接收id:{},内容:{}",messageInfo.getSendId(),messageInfo.getGroupId(),recvIds,messageInfo.getContent());
for(Long recvId:recvIds){
public void process(IMRecvInfo recvInfo) {
IMUserInfo sender = recvInfo.getSender();
List<IMUserInfo> receivers = recvInfo.getReceivers();
log.info("接收到群消息,发送者:{},接收用户数量:{},内容:{}",sender.getId(),receivers.size(),recvInfo.getData());
for(IMUserInfo receiver:receivers){
try {
ChannelHandlerContext channelCtx = UserChannelCtxMap.getChannelCtx(recvId);
ChannelHandlerContext channelCtx = UserChannelCtxMap.getChannelCtx(receiver.getId(),receiver.getTerminal());
if(channelCtx != null){
// 推送消息到用户
IMSendInfo sendInfo = new IMSendInfo();
sendInfo.setCmd(IMCmdType.GROUP_MESSAGE.code());
sendInfo.setData(messageInfo);
sendInfo.setData(recvInfo.getData());
channelCtx.channel().writeAndFlush(sendInfo);
// 消息发送成功确认
String key = RedisKey.IM_RESULT_GROUP_QUEUE;
SendResult sendResult = new SendResult();
sendResult.setRecvId(recvId);
sendResult.setCode(IMSendCode.SUCCESS);
sendResult.setMessageInfo(messageInfo);
redisTemplate.opsForList().rightPush(key,sendResult);
sendResult(recvInfo,receiver,IMSendCode.SUCCESS);
}else {
// 消息发送失败确认
String key = RedisKey.IM_RESULT_GROUP_QUEUE;
SendResult sendResult = new SendResult();
sendResult.setRecvId(recvId);
sendResult.setCode(IMSendCode.NOT_FIND_CHANNEL);
sendResult.setMessageInfo(messageInfo);
redisTemplate.opsForList().rightPush(key,sendResult);
log.error("未找到WS连接,发送者:{},群id:{},接收id:{},内容:{}",messageInfo.getSendId(),messageInfo.getGroupId(),recvIds,messageInfo.getContent());
// 消息发送成功确认
sendResult(recvInfo,receiver,IMSendCode.NOT_FIND_CHANNEL);
log.error("未找到channel,发送者:{},接收id:{},内容:{}",sender.getId(),receiver.getId(),recvInfo.getData());
}
}catch (Exception e){
// 消息发送失败确认
String key = RedisKey.IM_RESULT_GROUP_QUEUE;
SendResult sendResult = new SendResult();
sendResult.setRecvId(recvId);
sendResult.setCode(IMSendCode.UNKONW_ERROR);
sendResult.setMessageInfo(messageInfo);
redisTemplate.opsForList().rightPush(key,sendResult);
log.error("发送消息异常,发送者:{},群id:{},接收id:{},内容:{}",messageInfo.getSendId(),messageInfo.getGroupId(),recvIds,messageInfo.getContent());
sendResult(recvInfo,receiver,IMSendCode.UNKONW_ERROR);
log.error("发送消息异常,发送者:{},接收id:{},内容:{}",sender.getId(),receiver.getId(),recvInfo.getData());
}
}
}
private void sendResult(IMRecvInfo recvInfo,IMUserInfo receiver,IMSendCode sendCode){
if(recvInfo.getSendResult()) {
IMSendResult result = new IMSendResult();
result.setSender(recvInfo.getSender());
result.setReceiver(receiver);
result.setCode(sendCode.code());
result.setData(recvInfo.getData());
// 推送到结果队列
String key = IMRedisKey.IM_RESULT_GROUP_QUEUE;
redisTemplate.opsForList().rightPush(key, result);
}
}
}

33
im-server/src/main/java/com/bx/imserver/netty/processor/HeartbeatProcessor.java

@ -1,11 +1,12 @@
package com.bx.imserver.netty.processor;
import cn.hutool.core.bean.BeanUtil;
import com.bx.imcommon.contant.Constant;
import com.bx.imcommon.contant.RedisKey;
import com.bx.imcommon.contant.IMConstant;
import com.bx.imcommon.contant.IMRedisKey;
import com.bx.imcommon.enums.IMCmdType;
import com.bx.imcommon.model.HeartbeatInfo;
import com.bx.imcommon.model.IMHeartbeatInfo;
import com.bx.imcommon.model.IMSendInfo;
import com.bx.imserver.constant.ChannelAttrKey;
import com.bx.imserver.netty.ws.WebSocketServer;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.AttributeKey;
@ -19,40 +20,42 @@ import java.util.concurrent.TimeUnit;
@Slf4j
@Component
public class HeartbeatProcessor extends MessageProcessor<HeartbeatInfo> {
public class HeartbeatProcessor extends AbstractMessageProcessor<IMHeartbeatInfo> {
@Autowired
private WebSocketServer WSServer;
private WebSocketServer wsServer;
@Autowired
RedisTemplate<String,Object> redisTemplate;
@Override
public void process(ChannelHandlerContext ctx, HeartbeatInfo beatInfo) {
public void process(ChannelHandlerContext ctx, IMHeartbeatInfo beatInfo) {
// 响应ws
IMSendInfo sendInfo = new IMSendInfo();
sendInfo.setCmd(IMCmdType.HEART_BEAT.code());
ctx.channel().writeAndFlush(sendInfo);
// 设置属性
AttributeKey<Long> attr = AttributeKey.valueOf("HEARTBEAt_TIMES");
Long heartbeatTimes = ctx.channel().attr(attr).get();
ctx.channel().attr(attr).set(++heartbeatTimes);
AttributeKey<Long> heartBeatAttr = AttributeKey.valueOf(ChannelAttrKey.HEARTBEAT_TIMES);
Long heartbeatTimes = ctx.channel().attr(heartBeatAttr).get();
ctx.channel().attr(heartBeatAttr).set(++heartbeatTimes);
if(heartbeatTimes%10 == 0){
// 每心跳10次,用户在线状态续一次命
attr = AttributeKey.valueOf("USER_ID");
Long userId = ctx.channel().attr(attr).get();
String key = RedisKey.IM_USER_SERVER_ID+userId;
redisTemplate.expire(key, Constant.ONLINE_TIMEOUT_SECOND, TimeUnit.SECONDS);
AttributeKey<Long> userIdAttr = AttributeKey.valueOf(ChannelAttrKey.USER_ID);
Long userId = ctx.channel().attr(userIdAttr).get();
AttributeKey<Integer> terminalAttr = AttributeKey.valueOf(ChannelAttrKey.TERMINAL_TYPE);
Integer ternimal = ctx.channel().attr(terminalAttr).get();
String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID,userId.toString(),ternimal.toString());
redisTemplate.expire(key, IMConstant.ONLINE_TIMEOUT_SECOND, TimeUnit.SECONDS);
}
}
@Override
public HeartbeatInfo transForm(Object o) {
public IMHeartbeatInfo transForm(Object o) {
HashMap map = (HashMap)o;
HeartbeatInfo heartbeatInfo = BeanUtil.fillBeanWithMap(map, new HeartbeatInfo(), false);
IMHeartbeatInfo heartbeatInfo = BeanUtil.fillBeanWithMap(map, new IMHeartbeatInfo(), false);
return heartbeatInfo;
}
}

48
im-server/src/main/java/com/bx/imserver/netty/processor/LoginProcessor.java

@ -1,15 +1,17 @@
package com.bx.imserver.netty.processor;
import cn.hutool.core.bean.BeanUtil;
import com.bx.imcommon.contant.Constant;
import com.bx.imcommon.contant.RedisKey;
import com.alibaba.fastjson.JSON;
import com.bx.imcommon.contant.IMConstant;
import com.bx.imcommon.contant.IMRedisKey;
import com.bx.imcommon.enums.IMCmdType;
import com.bx.imcommon.model.IMSendInfo;
import com.bx.imcommon.model.LoginInfo;
import com.bx.imcommon.model.IMSessionInfo;
import com.bx.imcommon.model.IMLoginInfo;
import com.bx.imcommon.util.JwtUtil;
import com.bx.imserver.constant.ChannelAttrKey;
import com.bx.imserver.netty.IMServerGroup;
import com.bx.imserver.netty.UserChannelCtxMap;
import com.bx.imserver.netty.ws.WebSocketServer;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.AttributeKey;
import lombok.extern.slf4j.Slf4j;
@ -23,11 +25,7 @@ import java.util.concurrent.TimeUnit;
@Slf4j
@Component
public class LoginProcessor extends MessageProcessor<LoginInfo> {
@Autowired
private WebSocketServer WSServer;
public class LoginProcessor extends AbstractMessageProcessor<IMLoginInfo> {
@Autowired
RedisTemplate<String,Object> redisTemplate;
@ -36,14 +34,17 @@ public class LoginProcessor extends MessageProcessor<LoginInfo> {
private String accessTokenSecret;
@Override
synchronized public void process(ChannelHandlerContext ctx, LoginInfo loginInfo) {
synchronized public void process(ChannelHandlerContext ctx, IMLoginInfo loginInfo) {
if(!JwtUtil.checkSign(loginInfo.getAccessToken(),accessTokenSecret)){
ctx.channel().close();
log.warn("用户token校验不通过,强制下线,token:{}",loginInfo.getAccessToken());
}
Long userId = JwtUtil.getUserId(loginInfo.getAccessToken());
String strInfo = JwtUtil.getInfo(loginInfo.getAccessToken());
IMSessionInfo sessionInfo = JSON.parseObject(strInfo,IMSessionInfo.class);
Long userId = sessionInfo.getUserId();
Integer terminal = sessionInfo.getTerminal();
log.info("用户登录,userId:{}",userId);
ChannelHandlerContext context = UserChannelCtxMap.getChannelCtx(userId);
ChannelHandlerContext context = UserChannelCtxMap.getChannelCtx(userId,terminal);
if(context != null && !ctx.channel().id().equals(context.channel().id())){
// 不允许多地登录,强制下线
IMSendInfo sendInfo = new IMSendInfo();
@ -53,16 +54,19 @@ public class LoginProcessor extends MessageProcessor<LoginInfo> {
log.info("异地登录,强制下线,userId:{}",userId);
}
// 绑定用户和channel
UserChannelCtxMap.addChannelCtx(userId,ctx);
UserChannelCtxMap.addChannelCtx(userId,terminal,ctx);
// 设置用户id属性
AttributeKey<Long> attr = AttributeKey.valueOf("USER_ID");
ctx.channel().attr(attr).set(userId);
// 心跳次数
attr = AttributeKey.valueOf("HEARTBEAt_TIMES");
ctx.channel().attr(attr).set(0L);
AttributeKey<Long> userIdAttr = AttributeKey.valueOf(ChannelAttrKey.USER_ID);
ctx.channel().attr(userIdAttr).set(userId);
// 设置用户终端类型
AttributeKey<Integer> terminalAttr = AttributeKey.valueOf(ChannelAttrKey.TERMINAL_TYPE);
ctx.channel().attr(terminalAttr).set(terminal);
// 初始化心跳次数
AttributeKey<Long> heartBeatAttr = AttributeKey.valueOf("HEARTBEAt_TIMES");
ctx.channel().attr(heartBeatAttr).set(0L);
// 在redis上记录每个user的channelId,15秒没有心跳,则自动过期
String key = RedisKey.IM_USER_SERVER_ID+userId;
redisTemplate.opsForValue().set(key, IMServerGroup.serverId, Constant.ONLINE_TIMEOUT_SECOND, TimeUnit.SECONDS);
String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID,userId.toString(), terminal.toString());
redisTemplate.opsForValue().set(key, IMServerGroup.serverId, IMConstant.ONLINE_TIMEOUT_SECOND, TimeUnit.SECONDS);
// 响应ws
IMSendInfo sendInfo = new IMSendInfo();
sendInfo.setCmd(IMCmdType.LOGIN.code());
@ -71,9 +75,9 @@ public class LoginProcessor extends MessageProcessor<LoginInfo> {
@Override
public LoginInfo transForm(Object o) {
public IMLoginInfo transForm(Object o) {
HashMap map = (HashMap)o;
LoginInfo loginInfo = BeanUtil.fillBeanWithMap(map, new LoginInfo(), false);
IMLoginInfo loginInfo = BeanUtil.fillBeanWithMap(map, new IMLoginInfo(), false);
return loginInfo;
}
}

57
im-server/src/main/java/com/bx/imserver/netty/processor/PrivateMessageProcessor.java

@ -1,12 +1,12 @@
package com.bx.imserver.netty.processor;
import com.bx.imcommon.contant.RedisKey;
import com.bx.imcommon.contant.IMRedisKey;
import com.bx.imcommon.enums.IMCmdType;
import com.bx.imcommon.enums.IMSendCode;
import com.bx.imcommon.model.IMRecvInfo;
import com.bx.imcommon.model.IMSendInfo;
import com.bx.imcommon.model.PrivateMessageInfo;
import com.bx.imcommon.model.SendResult;
import com.bx.imcommon.model.IMUserInfo;
import com.bx.imcommon.model.IMSendResult;
import com.bx.imserver.netty.UserChannelCtxMap;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;
@ -16,52 +16,49 @@ import org.springframework.stereotype.Component;
@Slf4j
@Component
public class PrivateMessageProcessor extends MessageProcessor<IMRecvInfo<PrivateMessageInfo>> {
public class PrivateMessageProcessor extends AbstractMessageProcessor<IMRecvInfo> {
@Autowired
private RedisTemplate<String,Object> redisTemplate;
@Override
public void process(IMRecvInfo<PrivateMessageInfo> recvInfo) {
PrivateMessageInfo messageInfo = recvInfo.getData();
Long recvId = recvInfo.getRecvIds().get(0);
log.info("接收到消息,发送者:{},接收者:{},内容:{}",messageInfo.getSendId(),recvId,messageInfo.getContent());
public void process(IMRecvInfo recvInfo) {
IMUserInfo sender = recvInfo.getSender();
IMUserInfo receiver = recvInfo.getReceivers().get(0);
log.info("接收到消息,发送者:{},接收者:{},内容:{}",sender.getId(),receiver.getId(),recvInfo.getData());
try{
ChannelHandlerContext channelCtx = UserChannelCtxMap.getChannelCtx(recvId);
ChannelHandlerContext channelCtx = UserChannelCtxMap.getChannelCtx(receiver.getId(),receiver.getTerminal());
if(channelCtx != null ){
// 推送消息到用户
IMSendInfo sendInfo = new IMSendInfo();
sendInfo.setCmd(IMCmdType.PRIVATE_MESSAGE.code());
sendInfo.setData(messageInfo);
sendInfo.setData(recvInfo.getData());
channelCtx.channel().writeAndFlush(sendInfo);
// 消息发送成功确认
String key = RedisKey.IM_RESULT_PRIVATE_QUEUE;
SendResult sendResult = new SendResult();
sendResult.setRecvId(recvId);
sendResult.setCode(IMSendCode.SUCCESS);
sendResult.setMessageInfo(messageInfo);
redisTemplate.opsForList().rightPush(key,sendResult);
sendResult(recvInfo,IMSendCode.SUCCESS);
}else{
// 消息推送失败确认
String key = RedisKey.IM_RESULT_PRIVATE_QUEUE;
SendResult sendResult = new SendResult();
sendResult.setRecvId(recvId);
sendResult.setCode(IMSendCode.NOT_FIND_CHANNEL);
sendResult.setMessageInfo(messageInfo);
redisTemplate.opsForList().rightPush(key,sendResult);
log.error("未找到WS连接,发送者:{},接收者:{},内容:{}",messageInfo.getSendId(),recvId,messageInfo.getContent());
sendResult(recvInfo,IMSendCode.NOT_FIND_CHANNEL);
log.error("未找到channel,发送者:{},接收者:{},内容:{}",sender.getId(),receiver.getId(),recvInfo.getData());
}
}catch (Exception e){
// 消息推送失败确认
String key = RedisKey.IM_RESULT_PRIVATE_QUEUE;
SendResult sendResult = new SendResult();
sendResult.setRecvId(recvId);
sendResult.setCode(IMSendCode.UNKONW_ERROR);
sendResult.setMessageInfo(messageInfo);
redisTemplate.opsForList().rightPush(key,sendResult);
log.error("发送异常,发送者:{},接收者:{},内容:{}",messageInfo.getSendId(),recvId,messageInfo.getContent(),e);
sendResult(recvInfo,IMSendCode.UNKONW_ERROR);
log.error("发送异常,发送者:{},接收者:{},内容:{}",sender.getId(),receiver.getId(),recvInfo.getData(),e);
}
}
private void sendResult(IMRecvInfo recvInfo,IMSendCode sendCode){
if(recvInfo.getSendResult()) {
IMSendResult result = new IMSendResult();
result.setSender(recvInfo.getSender());
result.setReceiver(recvInfo.getReceivers().get(0));
result.setCode(sendCode.code());
result.setData(recvInfo.getData());
// 推送到结果队列
String key = IMRedisKey.IM_RESULT_PRIVATE_QUEUE;
redisTemplate.opsForList().rightPush(key, result);
}
}
}

12
im-server/src/main/java/com/bx/imserver/netty/processor/ProcessorFactory.java

@ -5,20 +5,20 @@ import com.bx.imserver.util.SpringContextHolder;
public class ProcessorFactory {
public static MessageProcessor createProcessor(IMCmdType cmd){
MessageProcessor processor = null;
public static AbstractMessageProcessor createProcessor(IMCmdType cmd){
AbstractMessageProcessor processor = null;
switch (cmd){
case LOGIN:
processor = (MessageProcessor) SpringContextHolder.getApplicationContext().getBean(LoginProcessor.class);
processor = (AbstractMessageProcessor) SpringContextHolder.getApplicationContext().getBean(LoginProcessor.class);
break;
case HEART_BEAT:
processor = (MessageProcessor) SpringContextHolder.getApplicationContext().getBean(HeartbeatProcessor.class);
processor = (AbstractMessageProcessor) SpringContextHolder.getApplicationContext().getBean(HeartbeatProcessor.class);
break;
case PRIVATE_MESSAGE:
processor = (MessageProcessor)SpringContextHolder.getApplicationContext().getBean(PrivateMessageProcessor.class);
processor = (AbstractMessageProcessor)SpringContextHolder.getApplicationContext().getBean(PrivateMessageProcessor.class);
break;
case GROUP_MESSAGE:
processor = (MessageProcessor)SpringContextHolder.getApplicationContext().getBean(GroupMessageProcessor.class);
processor = (AbstractMessageProcessor)SpringContextHolder.getApplicationContext().getBean(GroupMessageProcessor.class);
break;
default:
break;

2
im-server/src/main/java/com/bx/imserver/netty/tcp/TcpSocketServer.java

@ -59,7 +59,7 @@ public class TcpSocketServer implements IMServer {
protected void initChannel(Channel ch) throws Exception {
// 获取职责链
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new IdleStateHandler(15, 0, 0, TimeUnit.SECONDS));
pipeline.addLast(new IdleStateHandler(30, 0, 0, TimeUnit.SECONDS));
pipeline.addLast("encode",new MessageProtocolEncoder());
pipeline.addLast("decode",new MessageProtocolDecoder());
pipeline.addLast("handler", new IMChannelHandler());

2
im-server/src/main/java/com/bx/imserver/netty/ws/WebSocketServer.java

@ -63,7 +63,7 @@ public class WebSocketServer implements IMServer {
protected void initChannel(Channel ch) throws Exception {
// 获取职责链
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new IdleStateHandler(15, 0, 0, TimeUnit.SECONDS));
pipeline.addLast(new IdleStateHandler(30, 0, 0, TimeUnit.SECONDS));
pipeline.addLast("http-codec", new HttpServerCodec());
pipeline.addLast("aggregator", new HttpObjectAggregator(65535));
pipeline.addLast("http-chunked", new ChunkedWriteHandler());

4
im-server/src/main/java/com/bx/imserver/task/AbstractPullMessageTask.java

@ -7,8 +7,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.*;
@Slf4j
public abstract class AbstractPullMessageTask {
@ -41,7 +40,6 @@ public abstract class AbstractPullMessageTask{
if (serverGroup.isReady()) {
pullMessage();
}
Thread.sleep(100);
} catch (Exception e) {
log.error("任务调度异常", e);
Thread.sleep(200);

28
im-server/src/main/java/com/bx/imserver/task/PullUnreadGroupMessageTask.java

@ -1,45 +1,35 @@
package com.bx.imserver.task;
import com.bx.imcommon.contant.RedisKey;
import com.alibaba.fastjson.JSONObject;
import com.bx.imcommon.contant.IMRedisKey;
import com.bx.imcommon.enums.IMCmdType;
import com.bx.imcommon.model.GroupMessageInfo;
import com.bx.imcommon.model.IMRecvInfo;
import com.bx.imserver.netty.IMServerGroup;
import com.bx.imserver.netty.processor.MessageProcessor;
import com.bx.imserver.netty.processor.AbstractMessageProcessor;
import com.bx.imserver.netty.processor.ProcessorFactory;
import com.bx.imserver.netty.ws.WebSocketServer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.concurrent.TimeUnit;
@Slf4j
@Component
public class PullUnreadGroupMessageTask extends AbstractPullMessageTask {
@Autowired
private WebSocketServer WSServer;
@Autowired
private RedisTemplate<String,Object> redisTemplate;
@Override
public void pullMessage() {
// 从redis拉取未读消息
String key = RedisKey.IM_UNREAD_GROUP_QUEUE + IMServerGroup.serverId;
List messageInfos = redisTemplate.opsForList().range(key,0,-1);
for(Object o: messageInfos){
redisTemplate.opsForList().leftPop(key);
IMRecvInfo<GroupMessageInfo> recvInfo = (IMRecvInfo)o;
MessageProcessor processor = ProcessorFactory.createProcessor(IMCmdType.GROUP_MESSAGE);
String key = String.join(":", IMRedisKey.IM_UNREAD_GROUP_QUEUE,IMServerGroup.serverId+"");
JSONObject jsonObject = (JSONObject)redisTemplate.opsForList().leftPop(key,10, TimeUnit.SECONDS);
if(jsonObject != null){
IMRecvInfo recvInfo = jsonObject.toJavaObject(IMRecvInfo.class);
AbstractMessageProcessor processor = ProcessorFactory.createProcessor(IMCmdType.GROUP_MESSAGE);
processor.process(recvInfo);
}
}
}

25
im-server/src/main/java/com/bx/imserver/task/PullUnreadPrivateMessageTask.java

@ -1,28 +1,23 @@
package com.bx.imserver.task;
import com.bx.imcommon.contant.RedisKey;
import com.alibaba.fastjson.JSONObject;
import com.bx.imcommon.contant.IMRedisKey;
import com.bx.imcommon.enums.IMCmdType;
import com.bx.imcommon.model.IMRecvInfo;
import com.bx.imcommon.model.PrivateMessageInfo;
import com.bx.imserver.netty.IMServerGroup;
import com.bx.imserver.netty.processor.MessageProcessor;
import com.bx.imserver.netty.processor.AbstractMessageProcessor;
import com.bx.imserver.netty.processor.ProcessorFactory;
import com.bx.imserver.netty.ws.WebSocketServer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.concurrent.TimeUnit;
@Slf4j
@Component
public class PullUnreadPrivateMessageTask extends AbstractPullMessageTask {
@Autowired
private WebSocketServer WSServer;
@Autowired
private RedisTemplate<String,Object> redisTemplate;
@ -30,14 +25,12 @@ public class PullUnreadPrivateMessageTask extends AbstractPullMessageTask {
@Override
public void pullMessage() {
// 从redis拉取未读消息
String key = RedisKey.IM_UNREAD_PRIVATE_QUEUE + IMServerGroup.serverId;
List messageInfos = redisTemplate.opsForList().range(key,0,-1);
for(Object o: messageInfos){
redisTemplate.opsForList().leftPop(key);
IMRecvInfo<PrivateMessageInfo> recvInfo = (IMRecvInfo)o;
MessageProcessor processor = ProcessorFactory.createProcessor(IMCmdType.PRIVATE_MESSAGE);
String key = String.join(":", IMRedisKey.IM_UNREAD_PRIVATE_QUEUE ,IMServerGroup.serverId+"");
JSONObject jsonObject = (JSONObject)redisTemplate.opsForList().leftPop(key,10, TimeUnit.SECONDS);
if(jsonObject!=null){
IMRecvInfo recvInfo = jsonObject.toJavaObject(IMRecvInfo.class);
AbstractMessageProcessor processor = ProcessorFactory.createProcessor(IMCmdType.PRIVATE_MESSAGE);
processor.process(recvInfo);
}
}

BIN
im-ui/public/favicon.ico

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

2
im-ui/public/index.html

@ -4,7 +4,7 @@
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<link rel="icon" href="<%= BASE_URL %>logo.png">
<title>盒子IM</title>
</head>
<body>

BIN
im-ui/public/logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

6
im-ui/src/api/enums.js

@ -1,5 +1,11 @@
const MESSAGE_TYPE = {
TEXT: 0,
IMAGE:1,
FILE:2,
AUDIO:3,
VIDEO:4,
RECALL:10,
RTC_CALL: 101,
RTC_ACCEPT: 102,
RTC_REJECT: 103,

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

Loading…
Cancel
Save