Browse Source

封装im-client

master
xie.bx 3 years ago
parent
commit
8cfeffae50
  1. 20
      commom/pom.xml
  2. 7
      commom/src/main/java/com/bx/common/contant/Constant.java
  3. 21
      commom/src/main/java/com/bx/common/contant/RedisKey.java
  4. 8
      commom/src/main/java/com/bx/common/enums/FileType.java
  5. 8
      commom/src/main/java/com/bx/common/enums/IMCmdType.java
  6. 24
      commom/src/main/java/com/bx/common/enums/ListenerType.java
  7. 8
      commom/src/main/java/com/bx/common/enums/MessageStatus.java
  8. 13
      commom/src/main/java/com/bx/common/enums/MessageType.java
  9. 25
      commom/src/main/java/com/bx/common/enums/SendResultType.java
  10. 3
      commom/src/main/java/com/bx/common/model/im/GroupMessageInfo.java
  11. 17
      commom/src/main/java/com/bx/common/model/im/IMRecvInfo.java
  12. 17
      commom/src/main/java/com/bx/common/model/im/SendResult.java
  13. 26
      im-client/pom.xml
  14. 12
      im-client/src/main/java/com/bx/imclient/IMAutoConfiguration.java
  15. 30
      im-client/src/main/java/com/bx/imclient/IMClient.java
  16. 18
      im-client/src/main/java/com/bx/imclient/annotation/IMListener.java
  17. 49
      im-client/src/main/java/com/bx/imclient/config/RedisConfig.java
  18. 10
      im-client/src/main/java/com/bx/imclient/listener/MessageListener.java
  19. 28
      im-client/src/main/java/com/bx/imclient/listener/MessageListenerMulticaster.java
  20. 112
      im-client/src/main/java/com/bx/imclient/sender/IMSender.java
  21. 47
      im-client/src/main/java/com/bx/imclient/task/AbstractPullMessageTask.java
  22. 33
      im-client/src/main/java/com/bx/imclient/task/PullSendResultGroupMessageTask.java
  23. 38
      im-client/src/main/java/com/bx/imclient/task/PullSendResultPrivateMessageTask.java
  24. 2
      im-client/src/main/resources/META-INF/spring.factories
  25. 7
      im-platform/pom.xml
  26. 2
      im-platform/src/main/java/com/bx/implatform/ImplatformApp.java
  27. 6
      im-platform/src/main/java/com/bx/implatform/config/WebSecurityConfg.java
  28. 12
      im-platform/src/main/java/com/bx/implatform/contant/Constant.java
  29. 16
      im-platform/src/main/java/com/bx/implatform/contant/RedisKey.java
  30. 4
      im-platform/src/main/java/com/bx/implatform/controller/FileController.java
  31. 5
      im-platform/src/main/java/com/bx/implatform/controller/FriendController.java
  32. 4
      im-platform/src/main/java/com/bx/implatform/controller/GroupController.java
  33. 4
      im-platform/src/main/java/com/bx/implatform/controller/GroupMessageController.java
  34. 4
      im-platform/src/main/java/com/bx/implatform/controller/PrivateMessageController.java
  35. 4
      im-platform/src/main/java/com/bx/implatform/controller/RegisterController.java
  36. 7
      im-platform/src/main/java/com/bx/implatform/controller/UserController.java
  37. 2
      im-platform/src/main/java/com/bx/implatform/enums/ResultCode.java
  38. 2
      im-platform/src/main/java/com/bx/implatform/exception/GlobalException.java
  39. 6
      im-platform/src/main/java/com/bx/implatform/exception/GlobalExceptionHandler.java
  40. 2
      im-platform/src/main/java/com/bx/implatform/generator/CodeGenerator.java
  41. 34
      im-platform/src/main/java/com/bx/implatform/listener/GroupMessageListener.java
  42. 40
      im-platform/src/main/java/com/bx/implatform/listener/PrivateMessageListener.java
  43. 2
      im-platform/src/main/java/com/bx/implatform/result/Result.java
  44. 4
      im-platform/src/main/java/com/bx/implatform/result/ResultUtils.java
  45. 4
      im-platform/src/main/java/com/bx/implatform/service/impl/FriendServiceImpl.java
  46. 2
      im-platform/src/main/java/com/bx/implatform/service/impl/GroupMemberServiceImpl.java
  47. 66
      im-platform/src/main/java/com/bx/implatform/service/impl/GroupMessageServiceImpl.java
  48. 8
      im-platform/src/main/java/com/bx/implatform/service/impl/GroupServiceImpl.java
  49. 46
      im-platform/src/main/java/com/bx/implatform/service/impl/PrivateMessageServiceImpl.java
  50. 2
      im-platform/src/main/java/com/bx/implatform/service/impl/SecurityUserDetailsServiceImpl.java
  51. 4
      im-platform/src/main/java/com/bx/implatform/service/impl/UserServiceImpl.java
  52. 14
      im-platform/src/main/java/com/bx/implatform/service/thirdparty/FileService.java
  53. 74
      im-platform/src/main/java/com/bx/implatform/task/PullAlreadyReadMessageTask.java
  54. 3
      im-platform/src/main/java/com/bx/implatform/util/BeanUtils.java
  55. 2
      im-platform/src/main/java/com/bx/implatform/util/DateTimeUtils.java
  56. 1
      im-platform/src/main/java/com/bx/implatform/util/MinioUtil.java
  57. 2
      im-platform/src/main/resources/application.yml
  58. 11
      im-server/src/main/java/com/bx/imserver/task/PullUnreadGroupMessageTask.java
  59. 11
      im-server/src/main/java/com/bx/imserver/task/PullUnreadPrivateMessageTask.java
  60. 4
      im-server/src/main/java/com/bx/imserver/websocket/WebSocketHandler.java
  61. 58
      im-server/src/main/java/com/bx/imserver/websocket/processor/GroupMessageProcessor.java
  62. 4
      im-server/src/main/java/com/bx/imserver/websocket/processor/HeartbeatProcessor.java
  63. 6
      im-server/src/main/java/com/bx/imserver/websocket/processor/LoginProcessor.java
  64. 56
      im-server/src/main/java/com/bx/imserver/websocket/processor/PrivateMessageProcessor.java
  65. 4
      im-server/src/main/java/com/bx/imserver/websocket/processor/ProcessorFactory.java
  66. 1
      pom.xml

20
commom/pom.xml

@ -34,25 +34,11 @@
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
</dependency>
<!-- 引入redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity</artifactId>
@ -63,5 +49,9 @@
<artifactId>jackson-datatype-joda</artifactId>
<version>2.9.10</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
</dependencies>
</project>

7
commom/src/main/java/com/bx/common/contant/Constant.java

@ -1,15 +1,8 @@
package com.bx.common.contant;
public class Constant {
// 最大图片上传大小
public static final long MAX_IMAGE_SIZE = 5*1024*1024;
// 最大上传文件大小
public static final long MAX_FILE_SIZE = 10*1024*1024;
// 群聊最大人数
public static final long MAX_GROUP_MEMBER = 500;
// 在线状态过期时间 600s
public static final long ONLINE_TIMEOUT_SECOND = 600;
// 消息允许撤回时间 300s

21
commom/src/main/java/com/bx/common/contant/RedisKey.java

@ -7,20 +7,13 @@ public class RedisKey {
// 用户ID所连接的IM-server的ID
public final static String IM_USER_SERVER_ID = "im:user:server_id:";
// 未读私聊消息队列
public final static String IM_UNREAD_PRIVATE_MESSAGE = "im:unread:private:";
public final static String IM_UNREAD_PRIVATE_QUEUE = "im:unread:private:";
// 未读群聊消息队列
public final static String IM_UNREAD_GROUP_MESSAGE = "im:unread:group:";
// 已读私聊消息id队列
public final static String IM_READED_PRIVATE_MESSAGE_ID = "im:readed:private:id";
// 已读群聊消息位置(已读最大id)
public final static String IM_GROUP_READED_POSITION = "im:readed:group:position:";
// 缓存前缀
public final static String IM_CACHE = "im:cache:";
// 缓存是否好友:bool
public final static String IM_CACHE_FRIEND = IM_CACHE+"friend";
// 缓存群聊信息
public final static String IM_CACHE_GROUP = IM_CACHE+"group";
// 缓存群聊成员id
public final static String IM_CACHE_GROUP_MEMBER_ID = IM_CACHE+"group_member_ids";
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";
}

8
commom/src/main/java/com/bx/common/enums/FileTypeEnum.java → commom/src/main/java/com/bx/common/enums/FileType.java

@ -1,6 +1,6 @@
package com.bx.common.enums;
public enum FileTypeEnum {
public enum FileType {
FILE(0,"文件"),
IMAGE(1,"图片"),
@ -13,13 +13,13 @@ public enum FileTypeEnum {
private String desc;
FileTypeEnum(Integer index, String desc) {
FileType(Integer index, String desc) {
this.code =index;
this.desc=desc;
}
public static FileTypeEnum fromCode(Integer code){
for (FileTypeEnum typeEnum:values()) {
public static FileType fromCode(Integer code){
for (FileType typeEnum:values()) {
if (typeEnum.code.equals(code)) {
return typeEnum;
}

8
commom/src/main/java/com/bx/common/enums/WSCmdEnum.java → commom/src/main/java/com/bx/common/enums/IMCmdType.java

@ -1,6 +1,6 @@
package com.bx.common.enums;
public enum WSCmdEnum {
public enum IMCmdType {
LOGIN(0,"登陆"),
HEART_BEAT(1,"心跳"),
@ -13,13 +13,13 @@ public enum WSCmdEnum {
private String desc;
WSCmdEnum(Integer index, String desc) {
IMCmdType(Integer index, String desc) {
this.code =index;
this.desc=desc;
}
public static WSCmdEnum fromCode(Integer code){
for (WSCmdEnum typeEnum:values()) {
public static IMCmdType fromCode(Integer code){
for (IMCmdType typeEnum:values()) {
if (typeEnum.code.equals(code)) {
return typeEnum;
}

24
commom/src/main/java/com/bx/common/enums/ListenerType.java

@ -0,0 +1,24 @@
package com.bx.common.enums;
public enum ListenerType {
PRIVATE_MESSAGE(0,"私聊消息"),
GROUP_MESSAGE(1,"群聊消息");
private Integer code;
private String desc;
ListenerType(Integer index, String desc) {
this.code =index;
this.desc=desc;
}
public String getDesc() {
return desc;
}
public Integer getCode(){
return this.code;
}
}

8
commom/src/main/java/com/bx/common/enums/MessageStatusEnum.java → commom/src/main/java/com/bx/common/enums/MessageStatus.java

@ -1,7 +1,7 @@
package com.bx.common.enums;
public enum MessageStatusEnum {
public enum MessageStatus {
UNREAD(0,"未读"),
ALREADY_READ(1,"已读"),
@ -11,13 +11,13 @@ public enum MessageStatusEnum {
private String desc;
MessageStatusEnum(Integer index, String desc) {
MessageStatus(Integer index, String desc) {
this.code =index;
this.desc=desc;
}
public static MessageStatusEnum fromCode(Integer code){
for (MessageStatusEnum typeEnum:values()) {
public static MessageStatus fromCode(Integer code){
for (MessageStatus typeEnum:values()) {
if (typeEnum.code.equals(code)) {
return typeEnum;
}

13
commom/src/main/java/com/bx/common/enums/MessageTypeEnum.java → commom/src/main/java/com/bx/common/enums/MessageType.java

@ -1,7 +1,7 @@
package com.bx.common.enums;
public enum MessageTypeEnum {
public enum MessageType {
TEXT(0,"文字"),
FILE(1,"文件"),
@ -13,20 +13,11 @@ public enum MessageTypeEnum {
private String desc;
MessageTypeEnum(Integer index, String desc) {
MessageType(Integer index, String desc) {
this.code =index;
this.desc=desc;
}
public static MessageTypeEnum fromCode(Integer code){
for (MessageTypeEnum typeEnum:values()) {
if (typeEnum.code.equals(code)) {
return typeEnum;
}
}
return null;
}
public String getDesc() {
return desc;

25
commom/src/main/java/com/bx/common/enums/SendResultType.java

@ -0,0 +1,25 @@
package com.bx.common.enums;
public enum SendResultType {
SUCCESS(0,"发送成功"),
FAIL(1,"发送失败");
private int code;
private String msg;
// 构造方法
SendResultType(int code, String msg) {
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
}

3
commom/src/main/java/com/bx/common/model/im/GroupMessageInfo.java

@ -5,7 +5,6 @@ import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import lombok.Data;
import java.util.Date;
import java.util.List;
@Data
public class GroupMessageInfo {
@ -16,8 +15,6 @@ public class GroupMessageInfo {
private Long sendId;
private List<Long> recvIds;
private String content;
private Integer type;

17
commom/src/main/java/com/bx/common/model/im/IMRecvInfo.java

@ -0,0 +1,17 @@
package com.bx.common.model.im;
import lombok.Data;
import java.util.List;
@Data
public class IMRecvInfo<T> {
private Integer cmd;
private List<Long> recvIds;
private T data;
}

17
commom/src/main/java/com/bx/common/model/im/SendResult.java

@ -0,0 +1,17 @@
package com.bx.common.model.im;
import com.bx.common.enums.SendResultType;
import lombok.Data;
@Data
public class SendResult<T> {
private Long recvId;
private SendResultType result;
private String failReason="";
private T messageInfo;
}

26
im-client/pom.xml

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>box-im</artifactId>
<groupId>com.bx</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>im-client</artifactId>
<dependencies>
<dependency>
<groupId>com.bx</groupId>
<artifactId>commom</artifactId>
<version>1.0.0</version>
</dependency>
<!-- 引入redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
</project>

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

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

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

@ -0,0 +1,30 @@
package com.bx.imclient;
import com.bx.common.model.im.GroupMessageInfo;
import com.bx.common.model.im.PrivateMessageInfo;
import com.bx.imclient.listener.MessageListenerMulticaster;
import com.bx.imclient.sender.IMSender;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import java.util.List;
@Configuration
public class IMClient {
@Autowired
private MessageListenerMulticaster listenerMulticaster;
@Autowired
private IMSender imSender;
public void sendPrivateMessage(Long userId, PrivateMessageInfo... messageInfo){
imSender.sendPrivateMessage(userId,messageInfo);
}
public void sendGroupMessage(List<Long> userTokens, GroupMessageInfo... messageInfo){
imSender.sendGroupMessage(userTokens,messageInfo);
}
}

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

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

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

@ -0,0 +1,49 @@
package com.bx.imclient.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import 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.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
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());
// 设置键(key)的序列化采用StringRedisSerializer。
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.afterPropertiesSet();
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;
}
}

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

@ -0,0 +1,10 @@
package com.bx.imclient.listener;
import com.bx.common.model.im.SendResult;
public interface MessageListener {
void process(SendResult result);
}

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

@ -0,0 +1,28 @@
package com.bx.imclient.listener;
import com.bx.common.enums.ListenerType;
import com.bx.common.model.im.SendResult;
import com.bx.imclient.annotation.IMListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.List;
@Component
public class MessageListenerMulticaster {
@Autowired(required = false)
private List<MessageListener> messageListeners = Collections.emptyList();
public void multicast(ListenerType type, SendResult result){
for(MessageListener listener:messageListeners){
IMListener annotation = listener.getClass().getAnnotation(IMListener.class);
if(annotation.type().equals(type)){
listener.process(result);
}
}
}
}

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

@ -0,0 +1,112 @@
package com.bx.imclient.sender;
import com.bx.common.contant.RedisKey;
import com.bx.common.enums.IMCmdType;
import com.bx.common.enums.ListenerType;
import com.bx.common.enums.SendResultType;
import com.bx.common.model.im.GroupMessageInfo;
import com.bx.common.model.im.IMRecvInfo;
import com.bx.common.model.im.PrivateMessageInfo;
import com.bx.common.model.im.SendResult;
import com.bx.imclient.listener.MessageListenerMulticaster;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Service
public class IMSender {
@Autowired
@Qualifier("IMRedisTemplate")
private RedisTemplate redisTemplate;
@Autowired
private MessageListenerMulticaster listenerMulticaster;
public void sendPrivateMessage(Long recvId, PrivateMessageInfo... messageInfos){
// 获取对方连接的channelId
String key = RedisKey.IM_USER_SERVER_ID + recvId;
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<>();
recvInfo.setCmd(IMCmdType.PRIVATE_MESSAGE.getCode());
List recvIds = new LinkedList();
recvIds.add(recvId);
recvInfo.setRecvIds(recvIds);
recvInfo.setData(messageInfos[i]);
recvInfos[i] = recvInfo;
}
redisTemplate.opsForList().rightPushAll(sendKey, recvInfos);
}else{
// 回复消息状态
for(PrivateMessageInfo messageInfo : messageInfos ) {
SendResult result = new SendResult();
result.setMessageInfo(messageInfo);
result.setRecvId(recvId);
result.setResult(SendResultType.FAIL);
result.setFailReason("用户不在线");
listenerMulticaster.multicast(ListenerType.PRIVATE_MESSAGE, result);
}
}
}
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;
Integer serverId = (Integer)redisTemplate.opsForValue().get(key);
if(serverId != null){
if(serverMap.containsKey(serverId)){
serverMap.get(serverId).add(id);
}else {
// 此处需要加锁,否则list可以会被覆盖
synchronized(serverMap){
List<Long> list = Collections.synchronizedList(new LinkedList<Long>());
list.add(id);
serverMap.put(serverId,list);
}
}
}else{
offLineIds.add(id);
}
});
// 逐个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<>();
recvInfo.setCmd(IMCmdType.GROUP_MESSAGE.getCode());
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);
}
// 不在线的用户,回复消息状态
for(GroupMessageInfo messageInfo:messageInfos ){
for(Long id : offLineIds){
// 回复消息状态
SendResult result = new SendResult();
result.setMessageInfo(messageInfo);
result.setRecvId(id);
result.setResult(SendResultType.FAIL);
result.setFailReason("用户不在线");
listenerMulticaster.multicast(ListenerType.GROUP_MESSAGE,result);
}
}
}
}

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

@ -0,0 +1,47 @@
package com.bx.imclient.task;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Slf4j
public abstract class AbstractPullMessageTask {
private int threadNum = 8;
private ExecutorService executorService = Executors.newFixedThreadPool(threadNum);
@PostConstruct
public void init(){
// 初始化定时器
for(int i=0;i<threadNum;i++){
executorService.execute(new Runnable() {
@SneakyThrows
@Override
public void run() {
try{
pullMessage();
}catch (Exception e){
log.error("任务调度异常",e);
Thread.sleep(200);
}
if(!executorService.isShutdown()){
executorService.execute(this);
}
}
});
}
}
@PreDestroy
public void destroy(){
log.info("{}线程任务关闭",this.getClass().getSimpleName());
executorService.shutdown();
}
public abstract void pullMessage();
}

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

@ -0,0 +1,33 @@
package com.bx.imclient.task;
import com.bx.common.contant.RedisKey;
import com.bx.common.enums.ListenerType;
import com.bx.common.model.im.SendResult;
import com.bx.imclient.listener.MessageListenerMulticaster;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
public class PullSendResultGroupMessageTask extends AbstractPullMessageTask{
@Qualifier("IMRedisTemplate")
@Autowired
private RedisTemplate<String,Object> redisTemplate;
@Autowired
private MessageListenerMulticaster listenerMulticaster;
@Override
public void pullMessage() {
String key = RedisKey.IM_RESULT_GROUP_QUEUE;
SendResult result = (SendResult)redisTemplate.opsForList().leftPop(key,10, TimeUnit.SECONDS);
if(result != null) {
listenerMulticaster.multicast(ListenerType.GROUP_MESSAGE,result);
}
}
}

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

@ -0,0 +1,38 @@
package com.bx.imclient.task;
import com.bx.common.contant.RedisKey;
import com.bx.common.enums.ListenerType;
import com.bx.common.model.im.SendResult;
import com.bx.imclient.listener.MessageListenerMulticaster;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
;
@Slf4j
@Component
public class PullSendResultPrivateMessageTask extends AbstractPullMessageTask{
@Qualifier("IMRedisTemplate")
@Autowired
private RedisTemplate<String,Object> redisTemplate;
@Autowired
private MessageListenerMulticaster listenerMulticaster;
@Override
public void pullMessage() {
String key = RedisKey.IM_RESULT_PRIVATE_QUEUE;
SendResult result = (SendResult)redisTemplate.opsForList().leftPop(key,10, TimeUnit.SECONDS);
if(result != null) {
listenerMulticaster.multicast(ListenerType.PRIVATE_MESSAGE, result);
}
}
}

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

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

7
im-platform/pom.xml

@ -15,7 +15,7 @@
<dependencies>
<dependency>
<groupId>com.bx</groupId>
<artifactId>commom</artifactId>
<artifactId>im-client</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
@ -101,6 +101,11 @@
<artifactId>thumbnailator</artifactId>
<version>0.4.8</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.3.2</version>
</dependency>
</dependencies>
<build>

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

@ -4,14 +4,12 @@ import lombok.extern.slf4j.Slf4j;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Slf4j
@EnableAspectJAutoProxy(exposeProxy = true)
@MapperScan(basePackages = {"com.bx.implatform.mapper"})
@ComponentScan(basePackages={"com.bx"})
@SpringBootApplication
public class ImplatformApp {

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

@ -1,9 +1,9 @@
package com.bx.implatform.config;
import com.alibaba.fastjson.JSON;
import com.bx.common.enums.ResultCode;
import com.bx.common.result.Result;
import com.bx.common.result.ResultUtils;
import com.bx.implatform.enums.ResultCode;
import com.bx.implatform.result.Result;
import com.bx.implatform.result.ResultUtils;
import com.bx.implatform.service.IUserService;
import com.bx.implatform.session.UserSession;
import com.fasterxml.jackson.databind.ObjectMapper;

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

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

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

@ -0,0 +1,16 @@
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_CACHE = "im:cache:";
// 缓存是否好友:bool
public final static String IM_CACHE_FRIEND = IM_CACHE+"friend";
// 缓存群聊信息
public final static String IM_CACHE_GROUP = IM_CACHE+"group";
// 缓存群聊成员id
public final static String IM_CACHE_GROUP_MEMBER_ID = IM_CACHE+"group_member_ids";
}

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

@ -1,7 +1,7 @@
package com.bx.implatform.controller;
import com.bx.common.result.Result;
import com.bx.common.result.ResultUtils;
import com.bx.implatform.result.Result;
import com.bx.implatform.result.ResultUtils;
import com.bx.implatform.service.thirdparty.FileService;
import com.bx.implatform.vo.UploadImageVO;
import io.swagger.annotations.Api;

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

@ -1,9 +1,8 @@
package com.bx.implatform.controller;
import com.bx.common.result.Result;
import com.bx.common.result.ResultUtils;
import com.bx.implatform.entity.Friend;
import com.bx.implatform.result.Result;
import com.bx.implatform.result.ResultUtils;
import com.bx.implatform.service.IFriendService;
import com.bx.implatform.session.SessionContext;
import com.bx.implatform.vo.FriendVO;

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

@ -1,8 +1,8 @@
package com.bx.implatform.controller;
import com.bx.common.result.Result;
import com.bx.common.result.ResultUtils;
import com.bx.implatform.result.Result;
import com.bx.implatform.result.ResultUtils;
import com.bx.implatform.service.IGroupService;
import com.bx.implatform.vo.GroupInviteVO;
import com.bx.implatform.vo.GroupMemberVO;

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

@ -2,8 +2,8 @@ package com.bx.implatform.controller;
import com.bx.common.model.im.GroupMessageInfo;
import com.bx.common.result.Result;
import com.bx.common.result.ResultUtils;
import com.bx.implatform.result.Result;
import com.bx.implatform.result.ResultUtils;
import com.bx.implatform.service.IGroupMessageService;
import com.bx.implatform.vo.GroupMessageVO;
import io.swagger.annotations.Api;

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

@ -2,8 +2,8 @@ package com.bx.implatform.controller;
import com.bx.common.model.im.PrivateMessageInfo;
import com.bx.common.result.Result;
import com.bx.common.result.ResultUtils;
import com.bx.implatform.result.Result;
import com.bx.implatform.result.ResultUtils;
import com.bx.implatform.service.IPrivateMessageService;
import com.bx.implatform.vo.PrivateMessageVO;
import io.swagger.annotations.Api;

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

@ -1,8 +1,8 @@
package com.bx.implatform.controller;
import com.bx.common.result.Result;
import com.bx.common.result.ResultUtils;
import com.bx.implatform.result.Result;
import com.bx.implatform.result.ResultUtils;
import com.bx.implatform.service.IUserService;
import com.bx.implatform.vo.RegisterVO;
import io.swagger.annotations.Api;

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

@ -1,13 +1,12 @@
package com.bx.implatform.controller;
import com.bx.common.result.Result;
import com.bx.common.result.ResultUtils;
import com.bx.common.util.BeanUtils;
import com.bx.implatform.entity.User;
import com.bx.implatform.result.Result;
import com.bx.implatform.result.ResultUtils;
import com.bx.implatform.service.IUserService;
import com.bx.implatform.session.SessionContext;
import com.bx.implatform.session.UserSession;
import com.bx.implatform.util.BeanUtils;
import com.bx.implatform.vo.UserVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;

2
commom/src/main/java/com/bx/common/enums/ResultCode.java → im-platform/src/main/java/com/bx/implatform/enums/ResultCode.java

@ -1,4 +1,4 @@
package com.bx.common.enums;
package com.bx.implatform.enums;
/**
* 响应码枚举

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

@ -1,6 +1,6 @@
package com.bx.implatform.exception;
import com.bx.common.enums.ResultCode;
import com.bx.implatform.enums.ResultCode;
import lombok.Data;
import java.io.Serializable;

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

@ -1,9 +1,9 @@
package com.bx.implatform.exception;
import cn.hutool.json.JSONException;
import com.bx.common.enums.ResultCode;
import com.bx.common.result.Result;
import com.bx.common.result.ResultUtils;
import com.bx.implatform.enums.ResultCode;
import com.bx.implatform.result.Result;
import com.bx.implatform.result.ResultUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;

2
commom/src/main/java/com/bx/common/generator/CodeGenerator.java → im-platform/src/main/java/com/bx/implatform/generator/CodeGenerator.java

@ -1,4 +1,4 @@
package com.bx.common.generator;
package com.bx.implatform.generator;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.core.toolkit.StringPool;

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

@ -0,0 +1,34 @@
package com.bx.implatform.listener;
import com.bx.common.enums.ListenerType;
import com.bx.common.enums.MessageType;
import com.bx.common.model.im.GroupMessageInfo;
import com.bx.common.model.im.SendResult;
import com.bx.imclient.annotation.IMListener;
import com.bx.imclient.listener.MessageListener;
import com.bx.implatform.contant.RedisKey;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
@Slf4j
@IMListener(type = ListenerType.GROUP_MESSAGE)
public class GroupMessageListener implements MessageListener {
@Autowired
private RedisTemplate<String,Object> redisTemplate;
@Override
public void process(SendResult result){
GroupMessageInfo messageInfo = (GroupMessageInfo) result.getMessageInfo();
if(messageInfo.getType().equals(MessageType.TIP)){
// 提示类数据不记录
return;
}
// 保存该用户已拉取的最大消息id
String key = RedisKey.IM_GROUP_READED_POSITION + messageInfo.getGroupId()+":"+result.getRecvId();
redisTemplate.opsForValue().set(key,messageInfo.getId());
}
}

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

@ -0,0 +1,40 @@
package com.bx.implatform.listener;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.bx.common.enums.ListenerType;
import com.bx.common.enums.MessageStatus;
import com.bx.common.enums.MessageType;
import com.bx.common.model.im.PrivateMessageInfo;
import com.bx.common.model.im.SendResult;
import com.bx.imclient.annotation.IMListener;
import com.bx.imclient.listener.MessageListener;
import com.bx.implatform.entity.PrivateMessage;
import com.bx.implatform.service.IPrivateMessageService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@Slf4j
@IMListener(type = ListenerType.PRIVATE_MESSAGE)
public class PrivateMessageListener implements MessageListener {
@Autowired
private IPrivateMessageService privateMessageService;
@Override
public void process(SendResult result){
PrivateMessageInfo messageInfo = (PrivateMessageInfo) result.getMessageInfo();
if(messageInfo.getType().equals(MessageType.TIP)){
// 提示类数据不记录
return;
}
// 更新消息状态
UpdateWrapper<PrivateMessage> updateWrapper = new UpdateWrapper<>();
updateWrapper.lambda().eq(PrivateMessage::getId,messageInfo.getId())
.eq(PrivateMessage::getStatus, MessageStatus.UNREAD.getCode())
.set(PrivateMessage::getStatus, MessageStatus.ALREADY_READ.getCode());
privateMessageService.update(updateWrapper);
log.info("消息已读,消息id:{},发送者:{},接收者:{}",messageInfo.getId(),messageInfo.getSendId(),messageInfo.getRecvId());
}
}

2
commom/src/main/java/com/bx/common/result/Result.java → im-platform/src/main/java/com/bx/implatform/result/Result.java

@ -1,4 +1,4 @@
package com.bx.common.result;
package com.bx.implatform.result;
import lombok.Data;

4
commom/src/main/java/com/bx/common/result/ResultUtils.java → im-platform/src/main/java/com/bx/implatform/result/ResultUtils.java

@ -1,7 +1,7 @@
package com.bx.common.result;
package com.bx.implatform.result;
import com.bx.common.enums.ResultCode;
import com.bx.implatform.enums.ResultCode;
public class ResultUtils {

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

@ -2,10 +2,10 @@ package com.bx.implatform.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bx.common.contant.RedisKey;
import com.bx.common.enums.ResultCode;
import com.bx.implatform.contant.RedisKey;
import com.bx.implatform.entity.Friend;
import com.bx.implatform.entity.User;
import com.bx.implatform.enums.ResultCode;
import com.bx.implatform.exception.GlobalException;
import com.bx.implatform.mapper.FriendMapper;
import com.bx.implatform.service.IFriendService;

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

@ -3,7 +3,7 @@ package com.bx.implatform.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bx.common.contant.RedisKey;
import com.bx.implatform.contant.RedisKey;
import com.bx.implatform.entity.GroupMember;
import com.bx.implatform.mapper.GroupMemberMapper;
import com.bx.implatform.service.IGroupMemberService;

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

@ -3,29 +3,32 @@ package com.bx.implatform.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bx.common.contant.Constant;
import com.bx.common.contant.RedisKey;
import com.bx.common.enums.MessageStatusEnum;
import com.bx.common.enums.MessageTypeEnum;
import com.bx.common.enums.ResultCode;
import com.bx.common.enums.MessageStatus;
import com.bx.common.enums.MessageType;
import com.bx.common.model.im.GroupMessageInfo;
import com.bx.common.util.BeanUtils;
import com.bx.imclient.IMClient;
import com.bx.implatform.contant.RedisKey;
import com.bx.implatform.entity.Group;
import com.bx.implatform.entity.GroupMember;
import com.bx.implatform.entity.GroupMessage;
import com.bx.implatform.enums.ResultCode;
import com.bx.implatform.exception.GlobalException;
import com.bx.implatform.mapper.GroupMessageMapper;
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.util.BeanUtils;
import com.bx.implatform.vo.GroupMessageVO;
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.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j
@ -42,6 +45,9 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
@Autowired
private RedisTemplate<String,Object> redisTemplate;
@Autowired
private IMClient imClient;
/**
* 发送群聊消息(与mysql所有交换都要进行缓存)
*
@ -102,12 +108,12 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
throw new GlobalException(ResultCode.PROGRAM_ERROR,"您已不在群聊里面,无法撤回消息");
}
// 修改数据库
msg.setStatus(MessageStatusEnum.RECALL.getCode());
msg.setStatus(MessageStatus.RECALL.getCode());
this.updateById(msg);
// 群发
List<Long> userIds = groupMemberService.findUserIdsByGroupId(msg.getGroupId());
GroupMessageInfo msgInfo = BeanUtils.copyProperties(msg, GroupMessageInfo.class);
msgInfo.setType(MessageTypeEnum.TIP.getCode());
msgInfo.setType(MessageType.TIP.getCode());
String content = String.format("'%s'撤回了一条消息",member.getAliasName());
msgInfo.setContent(content);
msgInfo.setSendTime(new Date());
@ -124,22 +130,17 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
@Override
public void pullUnreadMessage() {
Long userId = SessionContext.getSession().getId();
String key = RedisKey.IM_USER_SERVER_ID+userId;
Integer serverId = (Integer)redisTemplate.opsForValue().get(key);
if(serverId == null){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"用户未建立连接");
}
List<Long> recvIds = new LinkedList();
recvIds.add(userId);
List<GroupMember> members = groupMemberService.findByUserId(userId);
for(GroupMember member:members){
// 获取群聊已读的最大消息id,只推送未读消息
key = RedisKey.IM_GROUP_READED_POSITION + member.getGroupId()+":"+userId;
String key = RedisKey.IM_GROUP_READED_POSITION + member.getGroupId()+":"+userId;
Integer maxReadedId = (Integer)redisTemplate.opsForValue().get(key);
QueryWrapper<GroupMessage> wrapper = new QueryWrapper();
wrapper.lambda().eq(GroupMessage::getGroupId,member.getGroupId())
.gt(GroupMessage::getSendTime,member.getCreatedTime())
.ne(GroupMessage::getStatus,MessageStatusEnum.RECALL.getCode());
.ne(GroupMessage::getStatus, MessageStatus.RECALL.getCode());
if(maxReadedId!=null){
wrapper.lambda().gt(GroupMessage::getId,maxReadedId);
}
@ -151,11 +152,11 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
// 组装消息,准备推送
List<GroupMessageInfo> messageInfos = messages.stream().map(m->{
GroupMessageInfo msgInfo = BeanUtils.copyProperties(m, GroupMessageInfo.class);
msgInfo.setRecvIds(recvIds);
return msgInfo;
}).collect(Collectors.toList());
key = RedisKey.IM_UNREAD_GROUP_MESSAGE + serverId;
redisTemplate.opsForList().rightPushAll(key,messageInfos.toArray());
// 发送消息
GroupMessageInfo[] infoArr = messageInfos.toArray(new GroupMessageInfo[messageInfos.size()]);
imClient.sendGroupMessage(Collections.singletonList(userId), infoArr);
log.info("拉取未读群聊消息,用户id:{},群聊id:{},数量:{}",userId,member.getGroupId(),messageInfos.size());
}
@ -184,7 +185,7 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
QueryWrapper<GroupMessage> wrapper = new QueryWrapper<>();
wrapper.lambda().eq(GroupMessage::getGroupId,groupId)
.gt(GroupMessage::getSendTime,member.getCreatedTime())
.ne(GroupMessage::getStatus,MessageStatusEnum.RECALL.getCode())
.ne(GroupMessage::getStatus, MessageStatus.RECALL.getCode())
.orderByDesc(GroupMessage::getId)
.last("limit "+stIdx + ","+size);
@ -198,29 +199,6 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
}
private void sendMessage(List<Long> userIds, GroupMessageInfo msgInfo){
// 根据群聊每个成员所连的IM-server,进行分组
Map<Integer,List<Long>> serverMap = new ConcurrentHashMap<>();
userIds.parallelStream().forEach(id->{
String key = RedisKey.IM_USER_SERVER_ID + id;
Integer serverId = (Integer)redisTemplate.opsForValue().get(key);
if(serverId != null){
if(serverMap.containsKey(serverId)){
serverMap.get(serverId).add(id);
}else {
// 此处需要加锁,否则list可以会被覆盖
synchronized(serverMap){
List<Long> list = Collections.synchronizedList(new LinkedList<Long>());
list.add(id);
serverMap.put(serverId,list);
}
}
}
});
// 逐个server发送
for (Map.Entry<Integer,List<Long>> entry : serverMap.entrySet()) {
msgInfo.setRecvIds(new LinkedList<>(entry.getValue()));
String key = RedisKey.IM_UNREAD_GROUP_MESSAGE +entry.getKey();
redisTemplate.opsForList().rightPush(key,msgInfo);
}
imClient.sendGroupMessage(userIds,msgInfo);
}
}

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

@ -2,14 +2,13 @@ package com.bx.implatform.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bx.common.contant.Constant;
import com.bx.common.contant.RedisKey;
import com.bx.common.enums.ResultCode;
import com.bx.common.util.BeanUtils;
import com.bx.implatform.contant.Constant;
import com.bx.implatform.contant.RedisKey;
import com.bx.implatform.entity.Friend;
import com.bx.implatform.entity.Group;
import com.bx.implatform.entity.GroupMember;
import com.bx.implatform.entity.User;
import com.bx.implatform.enums.ResultCode;
import com.bx.implatform.exception.GlobalException;
import com.bx.implatform.mapper.GroupMapper;
import com.bx.implatform.service.IFriendService;
@ -18,6 +17,7 @@ import com.bx.implatform.service.IGroupService;
import com.bx.implatform.service.IUserService;
import com.bx.implatform.session.SessionContext;
import com.bx.implatform.session.UserSession;
import com.bx.implatform.util.BeanUtils;
import com.bx.implatform.vo.GroupInviteVO;
import com.bx.implatform.vo.GroupMemberVO;
import com.bx.implatform.vo.GroupVO;

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

@ -4,17 +4,18 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bx.common.contant.Constant;
import com.bx.common.contant.RedisKey;
import com.bx.common.enums.MessageStatusEnum;
import com.bx.common.enums.MessageTypeEnum;
import com.bx.common.enums.ResultCode;
import com.bx.common.enums.MessageStatus;
import com.bx.common.enums.MessageType;
import com.bx.common.model.im.PrivateMessageInfo;
import com.bx.common.util.BeanUtils;
import com.bx.imclient.IMClient;
import com.bx.implatform.entity.PrivateMessage;
import com.bx.implatform.enums.ResultCode;
import com.bx.implatform.exception.GlobalException;
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.util.BeanUtils;
import com.bx.implatform.vo.PrivateMessageVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@ -33,7 +34,8 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
private IFriendService friendService;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private IMClient imClient;
/**
* 发送私聊消息
*
@ -50,18 +52,12 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
// 保存消息
PrivateMessage msg = BeanUtils.copyProperties(vo, PrivateMessage.class);
msg.setSendId(userId);
msg.setStatus(MessageStatusEnum.UNREAD.getCode());
msg.setStatus(MessageStatus.UNREAD.getCode());
msg.setSendTime(new Date());
this.save(msg);
// 获取对方连接的channelId
String key = RedisKey.IM_USER_SERVER_ID + msg.getRecvId();
Integer serverId = (Integer) redisTemplate.opsForValue().get(key);
// 如果对方在线,将数据存储至redis,等待拉取推送
if (serverId != null) {
String sendKey = RedisKey.IM_UNREAD_PRIVATE_MESSAGE + serverId;
// 推送消息
PrivateMessageInfo msgInfo = BeanUtils.copyProperties(msg, PrivateMessageInfo.class);
redisTemplate.opsForList().rightPush(sendKey, msgInfo);
}
imClient.sendPrivateMessage(vo.getRecvId(),msgInfo);
log.info("发送私聊消息,发送id:{},接收id:{},内容:{}", userId, vo.getRecvId(), vo.getContent());
return msg.getId();
}
@ -85,20 +81,14 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
throw new GlobalException(ResultCode.PROGRAM_ERROR, "消息已发送超过5分钟,无法撤回");
}
// 修改消息状态
msg.setStatus(MessageStatusEnum.RECALL.getCode());
msg.setStatus(MessageStatus.RECALL.getCode());
this.updateById(msg);
// 获取对方连接的channelId
String key = RedisKey.IM_USER_SERVER_ID + msg.getRecvId();
Integer serverId = (Integer) redisTemplate.opsForValue().get(key);
// 如果对方在线,将数据存储至redis,等待拉取推送
if (serverId != null) {
String sendKey = RedisKey.IM_UNREAD_PRIVATE_MESSAGE + serverId;
// 推送消息
PrivateMessageInfo msgInfo = BeanUtils.copyProperties(msg, PrivateMessageInfo.class);
msgInfo.setType(MessageTypeEnum.TIP.getCode());
msgInfo.setType(MessageType.TIP.getCode());
msgInfo.setSendTime(new Date());
msgInfo.setContent("对方撤回了一条消息");
redisTemplate.opsForList().rightPush(sendKey, msgInfo);
}
imClient.sendPrivateMessage(userId,msgInfo);
log.info("撤回私聊消息,发送id:{},接收id:{},内容:{}", msg.getSendId(), msg.getRecvId(), msg.getContent());
}
@ -123,7 +113,7 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
.eq(PrivateMessage::getRecvId, friendId))
.or(wp -> wp.eq(PrivateMessage::getRecvId, userId)
.eq(PrivateMessage::getSendId, friendId)))
.ne(PrivateMessage::getStatus, MessageStatusEnum.RECALL.getCode())
.ne(PrivateMessage::getStatus, MessageStatus.RECALL.getCode())
.orderByDesc(PrivateMessage::getId)
.last("limit " + stIdx + "," + size);
@ -154,7 +144,7 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
// 获取当前用户所有未读消息
QueryWrapper<PrivateMessage> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().eq(PrivateMessage::getRecvId, userId)
.eq(PrivateMessage::getStatus, MessageStatusEnum.UNREAD);
.eq(PrivateMessage::getStatus, MessageStatus.UNREAD);
List<PrivateMessage> messages = this.list(queryWrapper);
// 上传至redis,等待推送
if (!messages.isEmpty()) {
@ -162,8 +152,8 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
PrivateMessageInfo msgInfo = BeanUtils.copyProperties(m, PrivateMessageInfo.class);
return msgInfo;
}).collect(Collectors.toList());
String sendKey = RedisKey.IM_UNREAD_PRIVATE_MESSAGE + serverId;
redisTemplate.opsForList().rightPushAll(sendKey, infos.toArray());
// 推送消息
imClient.sendPrivateMessage(userId,(PrivateMessageInfo[]) infos.toArray());
log.info("拉取未读私聊消息,用户id:{},数量:{}", userId, infos.size());
}
}

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

@ -2,10 +2,10 @@ package com.bx.implatform.service.impl;
import com.alibaba.fastjson.JSON;
import com.bx.common.util.BeanUtils;
import com.bx.implatform.entity.User;
import com.bx.implatform.service.IUserService;
import com.bx.implatform.session.UserSession;
import com.bx.implatform.util.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;

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

@ -3,11 +3,10 @@ package com.bx.implatform.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bx.common.contant.RedisKey;
import com.bx.common.enums.ResultCode;
import com.bx.common.util.BeanUtils;
import com.bx.implatform.entity.Friend;
import com.bx.implatform.entity.GroupMember;
import com.bx.implatform.entity.User;
import com.bx.implatform.enums.ResultCode;
import com.bx.implatform.exception.GlobalException;
import com.bx.implatform.mapper.UserMapper;
import com.bx.implatform.service.IFriendService;
@ -15,6 +14,7 @@ import com.bx.implatform.service.IGroupMemberService;
import com.bx.implatform.service.IUserService;
import com.bx.implatform.session.SessionContext;
import com.bx.implatform.session.UserSession;
import com.bx.implatform.util.BeanUtils;
import com.bx.implatform.vo.RegisterVO;
import com.bx.implatform.vo.UserVO;
import lombok.extern.slf4j.Slf4j;

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

@ -1,8 +1,8 @@
package com.bx.implatform.service.thirdparty;
import com.bx.common.contant.Constant;
import com.bx.common.enums.FileTypeEnum;
import com.bx.common.enums.ResultCode;
import com.bx.common.enums.FileType;
import com.bx.implatform.contant.Constant;
import com.bx.implatform.enums.ResultCode;
import com.bx.implatform.exception.GlobalException;
import com.bx.implatform.session.SessionContext;
import com.bx.implatform.util.FileUtil;
@ -61,7 +61,7 @@ public class FileService {
if(StringUtils.isEmpty(fileName)){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"文件上传失败");
}
String url = generUrl(FileTypeEnum.FILE,fileName);
String url = generUrl(FileType.FILE,fileName);
log.info("文件文件成功,用户id:{},url:{}",userId,url);
return url;
}
@ -83,14 +83,14 @@ public class FileService {
if(StringUtils.isEmpty(fileName)){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"图片上传失败");
}
vo.setOriginUrl(generUrl(FileTypeEnum.IMAGE,fileName));
vo.setOriginUrl(generUrl(FileType.IMAGE,fileName));
// 上传缩略图
byte[] imageByte = ImageUtil.compressForScale(file.getBytes(),100);
fileName = minioUtil.upload(bucketName,imagePath,file.getOriginalFilename(),imageByte,file.getContentType());
if(StringUtils.isEmpty(fileName)){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"图片上传失败");
}
vo.setThumbUrl(generUrl(FileTypeEnum.IMAGE,fileName));
vo.setThumbUrl(generUrl(FileType.IMAGE,fileName));
log.info("文件图片成功,用户id:{},url:{}",userId,vo.getOriginUrl());
return vo;
} catch (IOException e) {
@ -100,7 +100,7 @@ public class FileService {
}
public String generUrl(FileTypeEnum fileTypeEnum, String fileName){
public String generUrl(FileType fileTypeEnum, String fileName){
String url = minIOServer+"/"+bucketName;
switch (fileTypeEnum){
case FILE:

74
im-platform/src/main/java/com/bx/implatform/task/PullAlreadyReadMessageTask.java

@ -1,74 +0,0 @@
package com.bx.implatform.task;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.bx.common.contant.RedisKey;
import com.bx.common.enums.MessageStatusEnum;
import com.bx.implatform.entity.PrivateMessage;
import com.bx.implatform.service.IPrivateMessageService;
import lombok.SneakyThrows;
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 javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@Slf4j
@Component
public class PullAlreadyReadMessageTask {
private int threadNum = 8;
private ExecutorService executorService = Executors.newFixedThreadPool(threadNum);
@Autowired
private RedisTemplate<String,Object> redisTemplate;
@Autowired
private IPrivateMessageService privateMessageService;
@PostConstruct
public void init(){
for(int i=0;i<threadNum;i++){
executorService.execute(new Task());
}
}
@PreDestroy
public void destroy(){
log.info("{}线程任务关闭",this.getClass().getSimpleName());
executorService.shutdown();
}
protected class Task implements Runnable{
@SneakyThrows
@Override
public void run() {
try {
String key = RedisKey.IM_READED_PRIVATE_MESSAGE_ID;
Integer msgId = (Integer)redisTemplate.opsForList().leftPop(key,10, TimeUnit.SECONDS);
if(msgId!=null){
UpdateWrapper<PrivateMessage> updateWrapper = new UpdateWrapper<>();
updateWrapper.lambda().eq(PrivateMessage::getId,msgId)
.eq(PrivateMessage::getStatus,MessageStatusEnum.UNREAD.getCode())
.set(PrivateMessage::getStatus, MessageStatusEnum.ALREADY_READ.getCode());
privateMessageService.update(updateWrapper);
log.info("消息已读,id:{}",msgId);
}
}catch (Exception e){
log.error(e.getMessage());
Thread.sleep(200);
}finally {
// 下一次循环
if(!executorService.isShutdown()){
executorService.submit(this);
}
}
}
}
}

3
commom/src/main/java/com/bx/common/util/BeanUtils.java → im-platform/src/main/java/com/bx/implatform/util/BeanUtils.java

@ -1,4 +1,4 @@
package com.bx.common.util;
package com.bx.implatform.util;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
@ -12,7 +12,6 @@ import java.util.Set;
public class BeanUtils {
private static void handleReflectionException(Exception e) {
ReflectionUtils.handleReflectionException(e);
}

2
commom/src/main/java/com/bx/common/util/DateTimeUtils.java → im-platform/src/main/java/com/bx/implatform/util/DateTimeUtils.java

@ -1,4 +1,4 @@
package com.bx.common.util;
package com.bx.implatform.util;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;

1
im-platform/src/main/java/com/bx/implatform/util/MinioUtil.java

@ -1,7 +1,6 @@
package com.bx.implatform.util;
import com.bx.common.util.DateTimeUtils;
import io.minio.*;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

2
im-platform/src/main/resources/application.yml

@ -23,7 +23,7 @@ mybatis-plus:
configuration:
# 是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN(下划线命名) 到经典 Java 属性名 aColumn(驼峰命名) 的类似映射
map-underscore-to-camel-case: false
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
#log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# mapper
mapper-locations:
# *.xml的具体路径

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

@ -1,8 +1,9 @@
package com.bx.imserver.task;
import com.bx.common.contant.RedisKey;
import com.bx.common.enums.WSCmdEnum;
import com.bx.common.enums.IMCmdType;
import com.bx.common.model.im.GroupMessageInfo;
import com.bx.common.model.im.IMRecvInfo;
import com.bx.imserver.websocket.WebsocketServer;
import com.bx.imserver.websocket.processor.MessageProcessor;
import com.bx.imserver.websocket.processor.ProcessorFactory;
@ -28,13 +29,13 @@ public class PullUnreadGroupMessageTask extends AbstractPullMessageTask {
@Override
public void pullMessage() {
// 从redis拉取未读消息
String key = RedisKey.IM_UNREAD_GROUP_MESSAGE + WSServer.getServerId();
String key = RedisKey.IM_UNREAD_GROUP_QUEUE + WSServer.getServerId();
List messageInfos = redisTemplate.opsForList().range(key,0,-1);
for(Object o: messageInfos){
redisTemplate.opsForList().leftPop(key);
GroupMessageInfo messageInfo = (GroupMessageInfo)o;
MessageProcessor processor = ProcessorFactory.createProcessor(WSCmdEnum.GROUP_MESSAGE);
processor.process(messageInfo);
IMRecvInfo<GroupMessageInfo> recvInfo = (IMRecvInfo)o;
MessageProcessor processor = ProcessorFactory.createProcessor(IMCmdType.GROUP_MESSAGE);
processor.process(recvInfo);
}
}

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

@ -2,7 +2,8 @@ package com.bx.imserver.task;
import com.bx.common.contant.RedisKey;
import com.bx.common.enums.WSCmdEnum;
import com.bx.common.enums.IMCmdType;
import com.bx.common.model.im.IMRecvInfo;
import com.bx.common.model.im.PrivateMessageInfo;
import com.bx.imserver.websocket.WebsocketServer;
import com.bx.imserver.websocket.processor.MessageProcessor;
@ -28,13 +29,13 @@ public class PullUnreadPrivateMessageTask extends AbstractPullMessageTask {
@Override
public void pullMessage() {
// 从redis拉取未读消息
String key = RedisKey.IM_UNREAD_PRIVATE_MESSAGE + WSServer.getServerId();
String key = RedisKey.IM_UNREAD_PRIVATE_QUEUE + WSServer.getServerId();
List messageInfos = redisTemplate.opsForList().range(key,0,-1);
for(Object o: messageInfos){
redisTemplate.opsForList().leftPop(key);
PrivateMessageInfo messageInfo = (PrivateMessageInfo)o;
MessageProcessor processor = ProcessorFactory.createProcessor(WSCmdEnum.PRIVATE_MESSAGE);
processor.process(messageInfo);
IMRecvInfo<PrivateMessageInfo> recvInfo = (IMRecvInfo)o;
MessageProcessor processor = ProcessorFactory.createProcessor(IMCmdType.PRIVATE_MESSAGE);
processor.process(recvInfo);
}
}

4
im-server/src/main/java/com/bx/imserver/websocket/WebSocketHandler.java

@ -1,7 +1,7 @@
package com.bx.imserver.websocket;
import com.bx.common.contant.RedisKey;
import com.bx.common.enums.WSCmdEnum;
import com.bx.common.enums.IMCmdType;
import com.bx.common.model.im.SendInfo;
import com.bx.common.util.SpringContextHolder;
import com.bx.imserver.websocket.processor.MessageProcessor;
@ -33,7 +33,7 @@ public class WebSocketHandler extends SimpleChannelInboundHandler<SendInfo> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, SendInfo sendInfo) throws Exception {
// 创建处理器进行处理
MessageProcessor processor = ProcessorFactory.createProcessor(WSCmdEnum.fromCode(sendInfo.getCmd()));
MessageProcessor processor = ProcessorFactory.createProcessor(IMCmdType.fromCode(sendInfo.getCmd()));
processor.process(ctx,processor.transForm(sendInfo.getData()));
}

58
im-server/src/main/java/com/bx/imserver/websocket/processor/GroupMessageProcessor.java

@ -1,10 +1,12 @@
package com.bx.imserver.websocket.processor;
import com.bx.common.contant.RedisKey;
import com.bx.common.enums.MessageTypeEnum;
import com.bx.common.enums.WSCmdEnum;
import com.bx.common.enums.IMCmdType;
import com.bx.common.enums.SendResultType;
import com.bx.common.model.im.GroupMessageInfo;
import com.bx.common.model.im.IMRecvInfo;
import com.bx.common.model.im.SendInfo;
import com.bx.common.model.im.SendResult;
import com.bx.imserver.websocket.WebsocketChannelCtxHolder;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;
@ -17,38 +19,58 @@ import java.util.List;
@Slf4j
@Component
public class GroupMessageProcessor extends MessageProcessor<GroupMessageInfo> {
public class GroupMessageProcessor extends MessageProcessor<IMRecvInfo<GroupMessageInfo>> {
@Autowired
private RedisTemplate<String,Object> redisTemplate;
@Async
@Override
public void process(GroupMessageInfo data) {
log.info("接收到群消息,发送者:{},群id:{},接收id:{},内容:{}",data.getSendId(),data.getGroupId(),data.getRecvIds(),data.getContent());
List<Long> recvIds = data.getRecvIds();
// 接收者id列表不需要传输,节省带宽
data.setRecvIds(null);
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){
try {
ChannelHandlerContext channelCtx = WebsocketChannelCtxHolder.getChannelCtx(recvId);
if(channelCtx != null){
// 自己发的消息不用推送
if(recvId != data.getSendId()){
if(recvId != messageInfo.getSendId()){
// 推送消息到用户
SendInfo sendInfo = new SendInfo();
sendInfo.setCmd(WSCmdEnum.GROUP_MESSAGE.getCode());
sendInfo.setData(data);
sendInfo.setCmd(IMCmdType.GROUP_MESSAGE.getCode());
sendInfo.setData(messageInfo);
channelCtx.channel().writeAndFlush(sendInfo);
}
if(data.getType() != MessageTypeEnum.TIP.getCode()){
// 设置已读最大id
String key = RedisKey.IM_GROUP_READED_POSITION + data.getGroupId()+":"+recvId;
redisTemplate.opsForValue().set(key,data.getId());
// 消息发送成功确认
String key = RedisKey.IM_RESULT_GROUP_QUEUE;
SendResult sendResult = new SendResult();
sendResult.setRecvId(recvId);
sendResult.setResult(SendResultType.SUCCESS);
sendResult.setMessageInfo(messageInfo);
redisTemplate.opsForList().rightPush(key,sendResult);
}
}else {
log.error("未找到WS连接,发送者:{},群id:{},接收id:{},内容:{}",data.getSendId(),data.getGroupId(),data.getRecvIds());
// 消息发送失败确认
String key = RedisKey.IM_RESULT_GROUP_QUEUE;
SendResult sendResult = new SendResult();
sendResult.setRecvId(recvId);
sendResult.setResult(SendResultType.FAIL);
sendResult.setFailReason("未找到WS连接");
sendResult.setMessageInfo(messageInfo);
redisTemplate.opsForList().rightPush(key,sendResult);
log.error("未找到WS连接,发送者:{},群id:{},接收id:{},内容:{}",messageInfo.getSendId(),messageInfo.getGroupId(),recvIds,messageInfo.getContent());
}
}catch (Exception e){
// 消息发送失败确认
String key = RedisKey.IM_RESULT_GROUP_QUEUE;
SendResult sendResult = new SendResult();
sendResult.setRecvId(recvId);
sendResult.setResult(SendResultType.FAIL);
sendResult.setFailReason("未知异常");
sendResult.setMessageInfo(messageInfo);
redisTemplate.opsForList().rightPush(key,sendResult);
log.error("发送消息异常,发送者:{},群id:{},接收id:{},内容:{}",messageInfo.getSendId(),messageInfo.getGroupId(),recvIds,messageInfo.getContent());
}
}
}

4
im-server/src/main/java/com/bx/imserver/websocket/processor/HeartbeatProcessor.java

@ -3,7 +3,7 @@ package com.bx.imserver.websocket.processor;
import cn.hutool.core.bean.BeanUtil;
import com.bx.common.contant.Constant;
import com.bx.common.contant.RedisKey;
import com.bx.common.enums.WSCmdEnum;
import com.bx.common.enums.IMCmdType;
import com.bx.common.model.im.HeartbeatInfo;
import com.bx.common.model.im.SendInfo;
import com.bx.imserver.websocket.WebsocketServer;
@ -32,7 +32,7 @@ public class HeartbeatProcessor extends MessageProcessor<HeartbeatInfo> {
public void process(ChannelHandlerContext ctx, HeartbeatInfo beatInfo) {
// 响应ws
SendInfo sendInfo = new SendInfo();
sendInfo.setCmd(WSCmdEnum.HEART_BEAT.getCode());
sendInfo.setCmd(IMCmdType.HEART_BEAT.getCode());
ctx.channel().writeAndFlush(sendInfo);
// 设置属性

6
im-server/src/main/java/com/bx/imserver/websocket/processor/LoginProcessor.java

@ -3,7 +3,7 @@ package com.bx.imserver.websocket.processor;
import cn.hutool.core.bean.BeanUtil;
import com.bx.common.contant.Constant;
import com.bx.common.contant.RedisKey;
import com.bx.common.enums.WSCmdEnum;
import com.bx.common.enums.IMCmdType;
import com.bx.common.model.im.LoginInfo;
import com.bx.common.model.im.SendInfo;
import com.bx.imserver.websocket.WebsocketChannelCtxHolder;
@ -36,7 +36,7 @@ public class LoginProcessor extends MessageProcessor<LoginInfo> {
if(context != null){
// 不允许多地登录,强制下线
SendInfo sendInfo = new SendInfo();
sendInfo.setCmd(WSCmdEnum.FORCE_LOGUT.getCode());
sendInfo.setCmd(IMCmdType.FORCE_LOGUT.getCode());
context.channel().writeAndFlush(sendInfo);
}
// 绑定用户和channel
@ -52,7 +52,7 @@ public class LoginProcessor extends MessageProcessor<LoginInfo> {
redisTemplate.opsForValue().set(key, WSServer.getServerId(), Constant.ONLINE_TIMEOUT_SECOND, TimeUnit.SECONDS);
// 响应ws
SendInfo sendInfo = new SendInfo();
sendInfo.setCmd(WSCmdEnum.LOGIN.getCode());
sendInfo.setCmd(IMCmdType.LOGIN.getCode());
ctx.channel().writeAndFlush(sendInfo);
}

56
im-server/src/main/java/com/bx/imserver/websocket/processor/PrivateMessageProcessor.java

@ -1,10 +1,12 @@
package com.bx.imserver.websocket.processor;
import com.bx.common.contant.RedisKey;
import com.bx.common.enums.MessageTypeEnum;
import com.bx.common.enums.WSCmdEnum;
import com.bx.common.enums.IMCmdType;
import com.bx.common.enums.SendResultType;
import com.bx.common.model.im.IMRecvInfo;
import com.bx.common.model.im.PrivateMessageInfo;
import com.bx.common.model.im.SendInfo;
import com.bx.common.model.im.SendResult;
import com.bx.imserver.websocket.WebsocketChannelCtxHolder;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;
@ -14,30 +16,52 @@ import org.springframework.stereotype.Component;
@Slf4j
@Component
public class PrivateMessageProcessor extends MessageProcessor<PrivateMessageInfo> {
public class PrivateMessageProcessor extends MessageProcessor<IMRecvInfo<PrivateMessageInfo>> {
@Autowired
private RedisTemplate<String,Object> redisTemplate;
@Override
public void process(PrivateMessageInfo data) {
log.info("接收到消息,发送者:{},接收者:{},内容:{}",data.getSendId(),data.getRecvId(),data.getContent());
// 一个用户可以同时登陆,所以有多个channel
ChannelHandlerContext channelCtx = WebsocketChannelCtxHolder.getChannelCtx(data.getRecvId());
public void process(IMRecvInfo<PrivateMessageInfo> recvInfo) {
PrivateMessageInfo messageInfo = recvInfo.getData();
Long recvId = recvInfo.getRecvIds().get(0);
log.info("接收到消息,发送者:{},接收者:{},内容:{}",messageInfo.getSendId(),recvId,messageInfo.getContent());
try{
ChannelHandlerContext channelCtx = WebsocketChannelCtxHolder.getChannelCtx(recvId);
if(channelCtx != null ){
// 推送消息到用户
SendInfo sendInfo = new SendInfo();
sendInfo.setCmd(WSCmdEnum.PRIVATE_MESSAGE.getCode());
sendInfo.setData(data);
sendInfo.setCmd(IMCmdType.PRIVATE_MESSAGE.getCode());
sendInfo.setData(messageInfo);
channelCtx.channel().writeAndFlush(sendInfo);
if(data.getType() != MessageTypeEnum.TIP.getCode()) {
// 已读消息推送至redis,等待更新数据库
String key = RedisKey.IM_READED_PRIVATE_MESSAGE_ID;
redisTemplate.opsForList().rightPush(key, data.getId());
}
// 消息发送成功确认
String key = RedisKey.IM_RESULT_PRIVATE_QUEUE;
SendResult sendResult = new SendResult();
sendResult.setRecvId(recvId);
sendResult.setResult(SendResultType.SUCCESS);
sendResult.setMessageInfo(messageInfo);
redisTemplate.opsForList().rightPush(key,sendResult);
}else{
log.error("未找到WS连接,发送者:{},接收者:{},内容:{}",data.getSendId(),data.getRecvId(),data.getContent());
// 消息推送失败确认
String key = RedisKey.IM_RESULT_PRIVATE_QUEUE;
SendResult sendResult = new SendResult();
sendResult.setRecvId(recvId);
sendResult.setResult(SendResultType.FAIL);
sendResult.setFailReason("未找到WS连接");
sendResult.setMessageInfo(messageInfo);
redisTemplate.opsForList().rightPush(key,sendResult);
log.error("未找到WS连接,发送者:{},接收者:{},内容:{}",messageInfo.getSendId(),recvId,messageInfo.getContent());
}
}catch (Exception e){
// 消息推送失败确认
String key = RedisKey.IM_RESULT_PRIVATE_QUEUE;
SendResult sendResult = new SendResult();
sendResult.setRecvId(recvId);
sendResult.setResult(SendResultType.FAIL);
sendResult.setFailReason("未知异常");
sendResult.setMessageInfo(messageInfo);
redisTemplate.opsForList().rightPush(key,sendResult);
log.error("发送异常,发送者:{},接收者:{},内容:{}",messageInfo.getSendId(),recvId,messageInfo.getContent(),e);
}
}

4
im-server/src/main/java/com/bx/imserver/websocket/processor/ProcessorFactory.java

@ -1,11 +1,11 @@
package com.bx.imserver.websocket.processor;
import com.bx.common.enums.WSCmdEnum;
import com.bx.common.enums.IMCmdType;
import com.bx.common.util.SpringContextHolder;
public class ProcessorFactory {
public static MessageProcessor createProcessor(WSCmdEnum cmd){
public static MessageProcessor createProcessor(IMCmdType cmd){
MessageProcessor processor = null;
switch (cmd){
case LOGIN:

1
pom.xml

@ -13,6 +13,7 @@
<module>im-platform</module>
<module>im-server</module>
<module>commom</module>
<module>im-client</module>
</modules>
<properties>

Loading…
Cancel
Save