Browse Source

!9 封装im-client

Merge pull request !9 from blue/v_1.1.0
master
blue 3 years ago
committed by Gitee
parent
commit
8c848ab6b7
No known key found for this signature in database GPG Key ID: 173E9B9CA92EEF8F
  1. 2
      .gitignore
  2. 26
      commom/src/main/java/com/bx/common/contant/RedisKey.java
  3. 38
      commom/src/main/java/com/bx/common/enums/MessageTypeEnum.java
  4. 24
      commom/src/main/java/com/bx/common/model/im/GroupMessageInfo.java
  5. 21
      commom/src/main/java/com/bx/common/model/im/PrivateMessageInfo.java
  6. 11
      commom/src/main/java/com/bx/common/model/im/SendInfo.java
  7. 26
      im-client/pom.xml
  8. 12
      im-client/src/main/java/com/bx/imclient/IMAutoConfiguration.java
  9. 41
      im-client/src/main/java/com/bx/imclient/IMClient.java
  10. 18
      im-client/src/main/java/com/bx/imclient/annotation/IMListener.java
  11. 49
      im-client/src/main/java/com/bx/imclient/config/RedisConfig.java
  12. 10
      im-client/src/main/java/com/bx/imclient/listener/MessageListener.java
  13. 27
      im-client/src/main/java/com/bx/imclient/listener/MessageListenerMulticaster.java
  14. 112
      im-client/src/main/java/com/bx/imclient/sender/IMSender.java
  15. 47
      im-client/src/main/java/com/bx/imclient/task/AbstractPullMessageTask.java
  16. 33
      im-client/src/main/java/com/bx/imclient/task/PullSendResultGroupMessageTask.java
  17. 38
      im-client/src/main/java/com/bx/imclient/task/PullSendResultPrivateMessageTask.java
  18. 2
      im-client/src/main/resources/META-INF/spring.factories
  19. 29
      im-commom/pom.xml
  20. 10
      im-commom/src/main/java/com/bx/imcommon/contant/Constant.java
  21. 19
      im-commom/src/main/java/com/bx/imcommon/contant/RedisKey.java
  22. 16
      im-commom/src/main/java/com/bx/imcommon/enums/IMCmdType.java
  23. 27
      im-commom/src/main/java/com/bx/imcommon/enums/IMListenerType.java
  24. 27
      im-commom/src/main/java/com/bx/imcommon/enums/IMSendStatus.java
  25. 42
      im-commom/src/main/java/com/bx/imcommon/model/GroupMessageInfo.java
  26. 2
      im-commom/src/main/java/com/bx/imcommon/model/HeartbeatInfo.java
  27. 26
      im-commom/src/main/java/com/bx/imcommon/model/IMRecvInfo.java
  28. 18
      im-commom/src/main/java/com/bx/imcommon/model/IMSendInfo.java
  29. 2
      im-commom/src/main/java/com/bx/imcommon/model/LoginInfo.java
  30. 42
      im-commom/src/main/java/com/bx/imcommon/model/PrivateMessageInfo.java
  31. 29
      im-commom/src/main/java/com/bx/imcommon/model/SendResult.java
  32. 28
      im-commom/src/main/java/com/bx/imcommon/serializer/DateToLongSerializer.java
  33. 11
      im-platform/pom.xml
  34. 2
      im-platform/src/main/java/com/bx/implatform/ImplatformApp.java
  35. 6
      im-platform/src/main/java/com/bx/implatform/config/WebSecurityConfg.java
  36. 9
      im-platform/src/main/java/com/bx/implatform/contant/Constant.java
  37. 16
      im-platform/src/main/java/com/bx/implatform/contant/RedisKey.java
  38. 4
      im-platform/src/main/java/com/bx/implatform/controller/FileController.java
  39. 5
      im-platform/src/main/java/com/bx/implatform/controller/FriendController.java
  40. 4
      im-platform/src/main/java/com/bx/implatform/controller/GroupController.java
  41. 13
      im-platform/src/main/java/com/bx/implatform/controller/GroupMessageController.java
  42. 16
      im-platform/src/main/java/com/bx/implatform/controller/PrivateMessageController.java
  43. 4
      im-platform/src/main/java/com/bx/implatform/controller/RegisterController.java
  44. 7
      im-platform/src/main/java/com/bx/implatform/controller/UserController.java
  45. 14
      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. 29
      im-platform/src/main/java/com/bx/implatform/enums/MessageType.java
  48. 2
      im-platform/src/main/java/com/bx/implatform/enums/ResultCode.java
  49. 2
      im-platform/src/main/java/com/bx/implatform/exception/GlobalException.java
  50. 6
      im-platform/src/main/java/com/bx/implatform/exception/GlobalExceptionHandler.java
  51. 2
      im-platform/src/main/java/com/bx/implatform/generator/CodeGenerator.java
  52. 38
      im-platform/src/main/java/com/bx/implatform/listener/GroupMessageListener.java
  53. 43
      im-platform/src/main/java/com/bx/implatform/listener/PrivateMessageListener.java
  54. 2
      im-platform/src/main/java/com/bx/implatform/result/Result.java
  55. 6
      im-platform/src/main/java/com/bx/implatform/result/ResultUtils.java
  56. 5
      im-platform/src/main/java/com/bx/implatform/service/IGroupMessageService.java
  57. 5
      im-platform/src/main/java/com/bx/implatform/service/IPrivateMessageService.java
  58. 8
      im-platform/src/main/java/com/bx/implatform/service/impl/FriendServiceImpl.java
  59. 2
      im-platform/src/main/java/com/bx/implatform/service/impl/GroupMemberServiceImpl.java
  60. 107
      im-platform/src/main/java/com/bx/implatform/service/impl/GroupMessageServiceImpl.java
  61. 22
      im-platform/src/main/java/com/bx/implatform/service/impl/GroupServiceImpl.java
  62. 136
      im-platform/src/main/java/com/bx/implatform/service/impl/PrivateMessageServiceImpl.java
  63. 2
      im-platform/src/main/java/com/bx/implatform/service/impl/SecurityUserDetailsServiceImpl.java
  64. 10
      im-platform/src/main/java/com/bx/implatform/service/impl/UserServiceImpl.java
  65. 20
      im-platform/src/main/java/com/bx/implatform/service/thirdparty/FileService.java
  66. 74
      im-platform/src/main/java/com/bx/implatform/task/PullAlreadyReadMessageTask.java
  67. 3
      im-platform/src/main/java/com/bx/implatform/util/BeanUtils.java
  68. 2
      im-platform/src/main/java/com/bx/implatform/util/DateTimeUtils.java
  69. 2
      im-platform/src/main/java/com/bx/implatform/util/FileUtil.java
  70. 1
      im-platform/src/main/java/com/bx/implatform/util/MinioUtil.java
  71. 2
      im-platform/src/main/resources/application.yml
  72. 6
      im-server/pom.xml
  73. 2
      im-server/src/main/java/com/bx/imserver/IMServerApp.java
  74. 77
      im-server/src/main/java/com/bx/imserver/processor/GroupMessageProcessor.java
  75. 18
      im-server/src/main/java/com/bx/imserver/processor/HeartbeatProcessor.java
  76. 28
      im-server/src/main/java/com/bx/imserver/processor/LoginProcessor.java
  77. 2
      im-server/src/main/java/com/bx/imserver/processor/MessageProcessor.java
  78. 69
      im-server/src/main/java/com/bx/imserver/processor/PrivateMessageProcessor.java
  79. 8
      im-server/src/main/java/com/bx/imserver/processor/ProcessorFactory.java
  80. 2
      im-server/src/main/java/com/bx/imserver/task/AbstractPullMessageTask.java
  81. 21
      im-server/src/main/java/com/bx/imserver/task/PullUnreadGroupMessageTask.java
  82. 21
      im-server/src/main/java/com/bx/imserver/task/PullUnreadPrivateMessageTask.java
  83. 2
      im-server/src/main/java/com/bx/imserver/util/SpringContextHolder.java
  84. 4
      im-server/src/main/java/com/bx/imserver/util/UserChannelCtxHolder.java
  85. 55
      im-server/src/main/java/com/bx/imserver/websocket/processor/GroupMessageProcessor.java
  86. 45
      im-server/src/main/java/com/bx/imserver/websocket/processor/PrivateMessageProcessor.java
  87. 25
      im-server/src/main/java/com/bx/imserver/ws/WebSocketHandler.java
  88. 8
      im-server/src/main/java/com/bx/imserver/ws/WebsocketServer.java
  89. 6
      im-server/src/main/java/com/bx/imserver/ws/endecode/MessageProtocolDecoder.java
  90. 9
      im-server/src/main/java/com/bx/imserver/ws/endecode/MessageProtocolEncoder.java
  91. 12
      im-ui/src/api/wssocket.js
  92. 21
      im-ui/src/components/chat/ChatBox.vue
  93. 170
      im-ui/src/components/chat/ChatHistory.vue
  94. 6
      im-ui/src/components/chat/MessageItem.vue
  95. 2
      im-ui/src/view/Home.vue
  96. 5
      pom.xml

2
.gitignore

@ -2,8 +2,8 @@
/box-im.iml
/im-server/im-server.iml
/im-platform/im-platform.iml
/commom/commom.iml
/im-platform/src/main/resources/application-prod.yml
/im-platform/src/main/resources/logback-prod.xml
/im-server/src/main/resources/application-prod.yml
/im-server/src/main/resources/logback-prod.xml
/im-commom/im-commom.iml

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

@ -1,26 +0,0 @@
package com.bx.common.contant;
public class RedisKey {
// 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_UNREAD_PRIVATE_MESSAGE = "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";
}

38
commom/src/main/java/com/bx/common/enums/MessageTypeEnum.java

@ -1,38 +0,0 @@
package com.bx.common.enums;
public enum MessageTypeEnum {
TEXT(0,"文字"),
FILE(1,"文件"),
IMAGE(2,"图片"),
VIDEO(3,"视频"),
TIP(10,"系统提示");
private Integer code;
private String desc;
MessageTypeEnum(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;
}
public Integer getCode(){
return this.code;
}
}

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

@ -1,24 +0,0 @@
package com.bx.common.model.im;
import lombok.Data;
import java.util.Date;
import java.util.List;
@Data
public class GroupMessageInfo {
private Long id;
private Long groupId;
private Long sendId;
private List<Long> recvIds;
private String content;
private Integer type;
private Date sendTime;
}

21
commom/src/main/java/com/bx/common/model/im/PrivateMessageInfo.java

@ -1,21 +0,0 @@
package com.bx.common.model.im;
import lombok.Data;
import java.util.Date;
@Data
public class PrivateMessageInfo {
private long id;
private Long sendId;
private Long recvId;
private String content;
private Integer type;
private Date sendTime;
}

11
commom/src/main/java/com/bx/common/model/im/SendInfo.java

@ -1,11 +0,0 @@
package com.bx.common.model.im;
import lombok.Data;
@Data
public class SendInfo<T> {
private Integer cmd;
private T data;
}

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.1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>im-client</artifactId>
<dependencies>
<dependency>
<groupId>com.bx</groupId>
<artifactId>im-commom</artifactId>
<version>1.1.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 {
}

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

@ -0,0 +1,41 @@
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 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;
/**
* 发送私聊消息
*
* @param recvId 接收用户id
* @param messageInfo 消息体将转成json发送到客户端
*/
public void sendPrivateMessage(Long recvId, PrivateMessageInfo... messageInfo){
imSender.sendPrivateMessage(recvId,messageInfo);
}
/**
* 发送群聊消息
*
* @param recvIds 群聊用户id列表
* @param messageInfo 消息体将转成json发送到客户端
*/
public void sendGroupMessage(List<Long> recvIds, GroupMessageInfo... messageInfo){
imSender.sendGroupMessage(recvIds,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.imcommon.enums.IMListenerType;
import org.springframework.stereotype.Component;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface IMListener {
IMListenerType type();
}

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.imcommon.model.SendResult;
public interface MessageListener {
void process(SendResult result);
}

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

@ -0,0 +1,27 @@
package com.bx.imclient.listener;
import com.bx.imclient.annotation.IMListener;
import com.bx.imcommon.enums.IMListenerType;
import com.bx.imcommon.model.SendResult;
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(IMListenerType type, SendResult result){
for(MessageListener listener:messageListeners){
IMListener annotation = listener.getClass().getAnnotation(IMListener.class);
if(annotation!=null && (annotation.type().equals(IMListenerType.ALL) || 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.imclient.listener.MessageListenerMulticaster;
import com.bx.imcommon.contant.RedisKey;
import com.bx.imcommon.enums.IMCmdType;
import com.bx.imcommon.enums.IMListenerType;
import com.bx.imcommon.enums.IMSendStatus;
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 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.code());
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.setStatus(IMSendStatus.FAIL);
result.setFailReason("用户不在线");
listenerMulticaster.multicast(IMListenerType.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.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);
}
// 不在线的用户,回复消息状态
for(GroupMessageInfo messageInfo:messageInfos ){
for(Long id : offLineIds){
// 回复消息状态
SendResult result = new SendResult();
result.setMessageInfo(messageInfo);
result.setRecvId(id);
result.setStatus(IMSendStatus.FAIL);
result.setFailReason("用户不在线");
listenerMulticaster.multicast(IMListenerType.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.imclient.listener.MessageListenerMulticaster;
import com.bx.imcommon.contant.RedisKey;
import com.bx.imcommon.enums.IMListenerType;
import com.bx.imcommon.model.SendResult;
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(IMListenerType.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.imclient.listener.MessageListenerMulticaster;
import com.bx.imcommon.contant.RedisKey;
import com.bx.imcommon.enums.IMListenerType;
import com.bx.imcommon.model.SendResult;
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(IMListenerType.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

29
commom/pom.xml → im-commom/pom.xml

@ -5,10 +5,10 @@
<parent>
<artifactId>box-im</artifactId>
<groupId>com.bx</groupId>
<version>1.0.0</version>
<version>1.1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>commom</artifactId>
<artifactId>im-commom</artifactId>
<properties>
<maven.compiler.source>8</maven.compiler.source>
@ -34,29 +34,24 @@
<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>
<version>${velocity.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-joda</artifactId>
<version>2.9.10</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
</dependencies>
</project>

10
im-commom/src/main/java/com/bx/imcommon/contant/Constant.java

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

19
im-commom/src/main/java/com/bx/imcommon/contant/RedisKey.java

@ -0,0 +1,19 @@
package com.bx.imcommon.contant;
public class RedisKey {
// 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_UNREAD_PRIVATE_QUEUE = "im:unread:private:";
// 未读群聊消息队列
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";
}

16
commom/src/main/java/com/bx/common/enums/WSCmdEnum.java → im-commom/src/main/java/com/bx/imcommon/enums/IMCmdType.java

@ -1,6 +1,8 @@
package com.bx.common.enums;
package com.bx.imcommon.enums;
public enum WSCmdEnum {
public enum IMCmdType {
LOGIN(0,"登陆"),
HEART_BEAT(1,"心跳"),
@ -13,13 +15,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;
}
@ -28,11 +30,11 @@ public enum WSCmdEnum {
}
public String getDesc() {
public String description() {
return desc;
}
public Integer getCode(){
public Integer code(){
return this.code;
}

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

@ -0,0 +1,27 @@
package com.bx.imcommon.enums;
public enum IMListenerType{
ALL(0,"全部消息"),
PRIVATE_MESSAGE(1,"私聊消息"),
GROUP_MESSAGE(2,"群聊消息");
private Integer code;
private String desc;
IMListenerType(Integer index, String desc) {
this.code =index;
this.desc=desc;
}
public String description() {
return desc;
}
public Integer code(){
return this.code;
}
}

27
im-commom/src/main/java/com/bx/imcommon/enums/IMSendStatus.java

@ -0,0 +1,27 @@
package com.bx.imcommon.enums;
public enum IMSendStatus {
SUCCESS(0,"发送成功"),
FAIL(1,"发送失败");
private int code;
private String desc;
// 构造方法
IMSendStatus(int code, String desc) {
this.code = code;
this.desc = desc;
}
public String description() {
return desc;
}
public Integer code(){
return this.code;
}
}

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

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

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

@ -1,4 +1,4 @@
package com.bx.common.model.im;
package com.bx.imcommon.model;
import lombok.Data;

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

@ -0,0 +1,26 @@
package com.bx.imcommon.model;
import lombok.Data;
import java.util.List;
@Data
public class IMRecvInfo<T> {
/*
* 命令类型
*/
private Integer cmd;
/*
* 接收者id列表
*/
private List<Long> recvIds;
/*
* 推送消息体
*/
private T data;
}

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

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

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

@ -1,4 +1,4 @@
package com.bx.common.model.im;
package com.bx.imcommon.model;
import lombok.Data;

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

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

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

@ -0,0 +1,29 @@
package com.bx.imcommon.model;
import com.bx.imcommon.enums.IMSendStatus;
import lombok.Data;
@Data
public class SendResult<T> {
/*
* 接收者id
*/
private Long recvId;
/*
* 发送状态
*/
private IMSendStatus status;
/*
* 失败原因
*/
private String failReason="";
/*
* 消息体(透传)
*/
private T messageInfo;
}

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

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

11
im-platform/pom.xml

@ -5,7 +5,7 @@
<parent>
<artifactId>box-im</artifactId>
<groupId>com.bx</groupId>
<version>1.0.0</version>
<version>1.1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@ -15,8 +15,8 @@
<dependencies>
<dependency>
<groupId>com.bx</groupId>
<artifactId>commom</artifactId>
<version>1.0.0</version>
<artifactId>im-client</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
@ -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;

9
commom/src/main/java/com/bx/common/contant/Constant.java → im-platform/src/main/java/com/bx/implatform/contant/Constant.java

@ -1,17 +1,12 @@
package com.bx.common.contant;
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;
// 在线状态过期时间 600s
public static final long ONLINE_TIMEOUT_SECOND = 600;
// 消息允许撤回时间 300s
public static final long ALLOW_RECALL_SECOND = 300;
}

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;

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

@ -1,8 +1,9 @@
package com.bx.implatform.controller;
import com.bx.common.result.Result;
import com.bx.common.result.ResultUtils;
import com.bx.imcommon.model.GroupMessageInfo;
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;
@ -12,6 +13,7 @@ import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.util.List;
@Api(tags = "群聊消息")
@ -43,5 +45,12 @@ public class GroupMessageController {
return ResultUtils.success();
}
@GetMapping("/history")
@ApiOperation(value = "查询聊天记录",notes="查询聊天记录")
public Result<List<GroupMessageInfo>> 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));
}
}

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

@ -1,8 +1,9 @@
package com.bx.implatform.controller;
import com.bx.common.result.Result;
import com.bx.common.result.ResultUtils;
import com.bx.imcommon.model.PrivateMessageInfo;
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;
@ -12,6 +13,7 @@ import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import java.util.List;
@Api(tags = "私聊消息")
@RestController
@ -42,5 +44,15 @@ public class PrivateMessageController {
privateMessageService.pullUnreadMessage();
return ResultUtils.success();
}
@GetMapping("/history")
@ApiOperation(value = "查询聊天记录",notes="查询聊天记录")
public Result<List<PrivateMessageInfo>> 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));
}
}

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;

14
commom/src/main/java/com/bx/common/enums/FileTypeEnum.java → im-platform/src/main/java/com/bx/implatform/enums/FileType.java

@ -1,6 +1,6 @@
package com.bx.common.enums;
package com.bx.implatform.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;
}
@ -28,11 +28,11 @@ public enum FileTypeEnum {
}
public String getDesc() {
public String description() {
return desc;
}
public Integer getCode(){
public Integer code(){
return this.code;
}

14
commom/src/main/java/com/bx/common/enums/MessageStatusEnum.java → im-platform/src/main/java/com/bx/implatform/enums/MessageStatus.java

@ -1,7 +1,7 @@
package com.bx.common.enums;
package com.bx.implatform.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;
}
@ -26,11 +26,11 @@ public enum MessageStatusEnum {
}
public String getDesc() {
public String description() {
return desc;
}
public Integer getCode(){
public Integer code(){
return this.code;
}
}

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

@ -0,0 +1,29 @@
package com.bx.implatform.enums;
public enum MessageType {
TEXT(0,"文字"),
FILE(1,"文件"),
IMAGE(2,"图片"),
VIDEO(3,"视频"),
TIP(10,"系统提示");
private Integer code;
private String desc;
MessageType(Integer index, String desc) {
this.code =index;
this.desc=desc;
}
public String description() {
return desc;
}
public Integer code(){
return this.code;
}
}

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;

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

@ -0,0 +1,38 @@
package com.bx.implatform.listener;
import com.bx.imclient.annotation.IMListener;
import com.bx.imclient.listener.MessageListener;
import com.bx.imcommon.enums.IMListenerType;
import com.bx.imcommon.enums.IMSendStatus;
import com.bx.imcommon.model.GroupMessageInfo;
import com.bx.imcommon.model.SendResult;
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;
@Slf4j
@IMListener(type = IMListenerType.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
if(result.getStatus().equals(IMSendStatus.SUCCESS)) {
String key = RedisKey.IM_GROUP_READED_POSITION + messageInfo.getGroupId() + ":" + result.getRecvId();
redisTemplate.opsForValue().set(key, messageInfo.getId());
}
}
}

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

@ -0,0 +1,43 @@
package com.bx.implatform.listener;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.bx.imclient.annotation.IMListener;
import com.bx.imclient.listener.MessageListener;
import com.bx.imcommon.enums.IMListenerType;
import com.bx.imcommon.enums.IMSendStatus;
import com.bx.imcommon.model.PrivateMessageInfo;
import com.bx.imcommon.model.SendResult;
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;
@Slf4j
@IMListener(type = IMListenerType.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;
}
// 更新消息状态
if(result.getStatus().equals(IMSendStatus.SUCCESS)){
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());
}
}
}

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;

6
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 {
@ -50,7 +50,7 @@ public class ResultUtils {
return result;
}
public static final <T> Result<T> error(ResultCode resultCode, String messsage,T data){
public static final <T> Result<T> error(ResultCode resultCode, String messsage, T data){
Result result=new Result();
result.setCode(resultCode.getCode());
result.setMessage(messsage);

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

@ -1,9 +1,12 @@
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 java.util.List;
public interface IGroupMessageService extends IService<GroupMessage> {
@ -13,4 +16,6 @@ public interface IGroupMessageService extends IService<GroupMessage> {
void recallMessage(Long id);
void pullUnreadMessage();
List<GroupMessageInfo> findHistoryMessage(Long groupId, Long page, Long size);
}

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

@ -1,9 +1,12 @@
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 java.util.List;
public interface IPrivateMessageService extends IService<PrivateMessage> {
@ -11,6 +14,8 @@ public interface IPrivateMessageService extends IService<PrivateMessage> {
void recallMessage(Long id);
List<PrivateMessageInfo> findHistoryMessage(Long friendId, Long page,Long size);
void pullUnreadMessage();
}

8
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;
@ -13,6 +13,7 @@ import com.bx.implatform.service.IUserService;
import com.bx.implatform.session.SessionContext;
import com.bx.implatform.session.UserSession;
import com.bx.implatform.vo.FriendVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
@ -24,6 +25,7 @@ import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Slf4j
@CacheConfig(cacheNames= RedisKey.IM_CACHE_FRIEND)
@Service
public class FriendServiceImpl extends ServiceImpl<FriendMapper, Friend> implements IFriendService {
@ -63,6 +65,7 @@ public class FriendServiceImpl extends ServiceImpl<FriendMapper, Friend> impleme
FriendServiceImpl proxy = (FriendServiceImpl)AopContext.currentProxy();
proxy.bindFriend(userId,friendId);
proxy.bindFriend(friendId,userId);
log.info("添加好友,用户id:{},好友id:{}",userId,friendId);
}
@ -80,6 +83,7 @@ public class FriendServiceImpl extends ServiceImpl<FriendMapper, Friend> impleme
FriendServiceImpl proxy = (FriendServiceImpl)AopContext.currentProxy();
proxy.unbindFriend(userId,friendId);
proxy.unbindFriend(friendId,userId);
log.info("删除好友,用户id:{},好友id:{}",userId,friendId);
}

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;

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

@ -2,30 +2,33 @@ 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.model.im.GroupMessageInfo;
import com.bx.common.util.BeanUtils;
import com.bx.imclient.IMClient;
import com.bx.imcommon.contant.Constant;
import com.bx.imcommon.model.GroupMessageInfo;
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.MessageStatus;
import com.bx.implatform.enums.MessageType;
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,11 +45,14 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
@Autowired
private RedisTemplate<String,Object> redisTemplate;
@Autowired
private IMClient imClient;
/**
* 发送群聊消息(与mysql所有交换都要进行缓存)
*
* @param vo
* @return
* @return 群聊id
*/
@Override
public Long sendMessage(GroupMessageVO vo) {
@ -70,7 +76,7 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
this.save(msg);
// 群发
GroupMessageInfo msgInfo = BeanUtils.copyProperties(msg, GroupMessageInfo.class);
this.sendMessage(userIds,msgInfo);
imClient.sendGroupMessage(userIds,msgInfo);
log.info("发送群聊消息,发送id:{},群聊id:{},内容:{}",userId,vo.getGroupId(),vo.getContent());
return msg.getId();
}
@ -102,16 +108,16 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
throw new GlobalException(ResultCode.PROGRAM_ERROR,"您已不在群聊里面,无法撤回消息");
}
// 修改数据库
msg.setStatus(MessageStatusEnum.RECALL.getCode());
msg.setStatus(MessageStatus.RECALL.code());
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.code());
String content = String.format("'%s'撤回了一条消息",member.getAliasName());
msgInfo.setContent(content);
msgInfo.setSendTime(new Date());
this.sendMessage(userIds,msgInfo);
imClient.sendGroupMessage(userIds,msgInfo);
log.info("撤回群聊消息,发送id:{},群聊id:{},内容:{}",userId,msg.getGroupId(),msg.getContent());
}
@ -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.code());
if(maxReadedId!=null){
wrapper.lambda().gt(GroupMessage::getId,maxReadedId);
}
@ -151,40 +152,50 @@ 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());
}
}
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);
/**
* 拉取历史聊天记录
*
* @param groupId 群聊id
* @param page 页码
* @param size 页码大小
* @return 聊天记录列表
*/
@Override
public List<GroupMessageInfo> 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;
// 群聊成员信息
GroupMember member = groupMemberService.findByGroupAndUserId(groupId,userId);
if(member == null || member.getQuit()){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"您已不在群聊中");
}
// 查询聊天记录,只查询加入群聊时间之后的消息
QueryWrapper<GroupMessage> wrapper = new QueryWrapper<>();
wrapper.lambda().eq(GroupMessage::getGroupId,groupId)
.gt(GroupMessage::getSendTime,member.getCreatedTime())
.ne(GroupMessage::getStatus, MessageStatus.RECALL.code())
.orderByDesc(GroupMessage::getId)
.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());
log.info("拉取群聊记录,用户id:{},群聊id:{},数量:{}",userId,groupId,messageInfos.size());
return messageInfos;
}
}

22
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,9 +17,11 @@ 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;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
@ -36,6 +37,7 @@ import java.util.Optional;
import java.util.stream.Collectors;
@Slf4j
@CacheConfig(cacheNames = RedisKey.IM_CACHE_GROUP)
@Service
public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements IGroupService {
@ -79,6 +81,7 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
GroupVO vo = BeanUtils.copyProperties(group, GroupVO.class);
vo.setAliasName(user.getNickName());
vo.setRemark(groupName);
log.info("创建群聊,群聊id:{},群聊名称:{}",group.getId(),group.getName());
return vo;
}
@ -109,6 +112,7 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
member.setAliasName(StringUtils.isEmpty(vo.getAliasName())?session.getNickName():vo.getAliasName());
member.setRemark(StringUtils.isEmpty(vo.getRemark())?group.getName():vo.getRemark());
groupMemberService.updateById(member);
log.info("修改群聊,群聊id:{},群聊名称:{}",group.getId(),group.getName());
return vo;
}
@ -131,6 +135,7 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
// 逻辑删除群数据
group.setDeleted(true);
this.updateById(group);
log.info("删除群聊,群聊id:{},群聊名称:{}",group.getId(),group.getName());
}
@ -142,13 +147,14 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
*/
@Override
public void quitGroup(Long groupId) {
UserSession session = SessionContext.getSession();
Long userId = SessionContext.getSession().getId();
Group group = this.getById(groupId);
if(group.getOwnerId() == session.getId()){
if(group.getOwnerId() == userId){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"您是群主,不可退出群聊");
}
// 删除群聊成员
groupMemberService.removeByGroupAndUserId(groupId,session.getId());
groupMemberService.removeByGroupAndUserId(groupId,userId);
log.info("退出群聊,群聊id:{},群聊名称:{},用户id:{}",group.getId(),group.getName(),userId);
}
@ -171,6 +177,7 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
}
// 删除群聊成员
groupMemberService.removeByGroupAndUserId(groupId,userId);
log.info("踢出群聊,群聊id:{},群聊名称:{},用户id:{}",group.getId(),group.getName(),userId);
}
@Override
@ -281,6 +288,7 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
if(!groupMembers.isEmpty()) {
groupMemberService.saveOrUpdateBatch(group.getId(),groupMembers);
}
log.info("邀请进入群聊,群聊id:{},群聊名称:{},被邀请用户id:{}",group.getId(),group.getName(),vo.getFriendIds());
}
/**

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

@ -2,19 +2,20 @@ 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.model.im.PrivateMessageInfo;
import com.bx.common.util.BeanUtils;
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.implatform.entity.PrivateMessage;
import com.bx.implatform.enums.MessageStatus;
import com.bx.implatform.enums.MessageType;
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;
@ -32,8 +33,9 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
@Autowired
private IFriendService friendService;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private IMClient imClient;
/**
* 发送私聊消息
*
@ -43,26 +45,20 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
@Override
public Long sendMessage(PrivateMessageVO vo) {
Long userId = SessionContext.getSession().getId();
Boolean isFriends = friendService.isFriend(userId,vo.getRecvId());
if(!isFriends){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"您已不是对方好友,无法发送消息");
Boolean isFriends = friendService.isFriend(userId, vo.getRecvId());
if (!isFriends) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "您已不是对方好友,无法发送消息");
}
// 保存消息
PrivateMessage msg = BeanUtils.copyProperties(vo, PrivateMessage.class);
msg.setSendId(userId);
msg.setStatus(MessageStatusEnum.UNREAD.getCode());
msg.setStatus(MessageStatus.UNREAD.code());
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);
}
log.info("发送私聊消息,发送id:{},接收id:{},内容:{}",userId,vo.getRecvId(),vo.getContent());
// 推送消息
PrivateMessageInfo msgInfo = BeanUtils.copyProperties(msg, PrivateMessageInfo.class);
imClient.sendPrivateMessage(vo.getRecvId(),msgInfo);
log.info("发送私聊消息,发送id:{},接收id:{},内容:{}", userId, vo.getRecvId(), vo.getContent());
return msg.getId();
}
@ -75,31 +71,60 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
public void recallMessage(Long id) {
Long userId = SessionContext.getSession().getId();
PrivateMessage msg = this.getById(id);
if(msg == null){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"消息不存在");
if (msg == null) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "消息不存在");
}
if(msg.getSendId() != userId){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"这条消息不是由您发送,无法撤回");
if (msg.getSendId() != userId) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "这条消息不是由您发送,无法撤回");
}
if(System.currentTimeMillis() - msg.getSendTime().getTime() > Constant.ALLOW_RECALL_SECOND * 1000){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"消息已发送超过5分钟,无法撤回");
if (System.currentTimeMillis() - msg.getSendTime().getTime() > Constant.ALLOW_RECALL_SECOND * 1000) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "消息已发送超过5分钟,无法撤回");
}
// 修改消息状态
msg.setStatus(MessageStatusEnum.RECALL.getCode());
msg.setStatus(MessageStatus.RECALL.code());
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.setSendTime(new Date());
msgInfo.setContent("对方撤回了一条消息");
redisTemplate.opsForList().rightPush(sendKey,msgInfo);
}
log.info("撤回私聊消息,发送id:{},接收id:{},内容:{}",msg.getSendId(),msg.getRecvId(),msg.getContent());
// 推送消息
PrivateMessageInfo msgInfo = BeanUtils.copyProperties(msg, PrivateMessageInfo.class);
msgInfo.setType(MessageType.TIP.code());
msgInfo.setSendTime(new Date());
msgInfo.setContent("对方撤回了一条消息");
imClient.sendPrivateMessage(msgInfo.getRecvId(),msgInfo);
log.info("撤回私聊消息,发送id:{},接收id:{},内容:{}", msg.getSendId(), msg.getRecvId(), msg.getContent());
}
/**
* 拉取历史聊天记录
*
* @param friendId 好友id
* @param page 页码
* @param size 页码大小
* @return 聊天记录列表
*/
@Override
public List<PrivateMessageInfo> 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;
QueryWrapper<PrivateMessage> wrapper = new QueryWrapper<>();
wrapper.lambda().and(wrap -> wrap.and(
wp -> wp.eq(PrivateMessage::getSendId, userId)
.eq(PrivateMessage::getRecvId, friendId))
.or(wp -> wp.eq(PrivateMessage::getRecvId, userId)
.eq(PrivateMessage::getSendId, friendId)))
.ne(PrivateMessage::getStatus, MessageStatus.RECALL.code())
.orderByDesc(PrivateMessage::getId)
.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());
log.info("拉取聊天记录,用户id:{},好友id:{},数量:{}", userId, friendId, messageInfos.size());
return messageInfos;
}
/**
@ -111,25 +136,26 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
public void pullUnreadMessage() {
// 获取当前连接的channelId
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,"用户未建立连接");
String key = RedisKey.IM_USER_SERVER_ID + userId;
Integer serverId = (Integer) redisTemplate.opsForValue().get(key);
if (serverId == null) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "用户未建立连接");
}
// 获取当前用户所有未读消息
QueryWrapper<PrivateMessage> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().eq(PrivateMessage::getRecvId,userId)
.eq(PrivateMessage::getStatus,MessageStatusEnum.UNREAD);
queryWrapper.lambda().eq(PrivateMessage::getRecvId, userId)
.eq(PrivateMessage::getStatus, MessageStatus.UNREAD);
List<PrivateMessage> messages = this.list(queryWrapper);
// 上传至redis,等待推送
if(!messages.isEmpty()){
List<PrivateMessageInfo> infos = messages.stream().map(m->{
if (!messages.isEmpty()) {
List<PrivateMessageInfo> messageInfos = messages.stream().map(m -> {
PrivateMessageInfo msgInfo = BeanUtils.copyProperties(m, PrivateMessageInfo.class);
return msgInfo;
return msgInfo;
}).collect(Collectors.toList());
String sendKey = RedisKey.IM_UNREAD_PRIVATE_MESSAGE + serverId;
redisTemplate.opsForList().rightPushAll(sendKey,infos.toArray());
log.info("拉取未读私聊消息,用户id:{},数量:{}",userId,infos.size());
// 推送消息
PrivateMessageInfo[] infoArr = messageInfos.toArray(new PrivateMessageInfo[messageInfos.size()]);
imClient.sendPrivateMessage(userId,infoArr);
log.info("拉取未读私聊消息,用户id:{},数量:{}", userId, infoArr.length);
}
}
}

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;

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

@ -2,12 +2,11 @@ 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.imcommon.contant.RedisKey;
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,8 +14,10 @@ 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;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.crypto.password.PasswordEncoder;
@ -28,6 +29,7 @@ import java.util.List;
import java.util.stream.Collectors;
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@ -58,6 +60,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IU
user = BeanUtils.copyProperties(vo,User.class);
user.setPassword(passwordEncoder.encode(user.getPassword()));
this.save(user);
log.info("注册用户,用户id:{},用户名:{},昵称:{}",user.getId(),vo.getUserName(),vo.getNickName());
}
/**
@ -116,6 +119,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());
}

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

@ -1,9 +1,10 @@
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.implatform.contant.Constant;
import com.bx.implatform.enums.FileType;
import com.bx.implatform.enums.ResultCode;
import com.bx.implatform.exception.GlobalException;
import com.bx.implatform.session.SessionContext;
import com.bx.implatform.util.FileUtil;
import com.bx.implatform.util.ImageUtil;
import com.bx.implatform.util.MinioUtil;
@ -50,6 +51,7 @@ public class FileService {
public String uploadFile(MultipartFile file){
Long userId = SessionContext.getSession().getId();
// 大小校验
if(file.getSize() > Constant.MAX_FILE_SIZE){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"文件大小不能超过10M");
@ -59,11 +61,14 @@ public class FileService {
if(StringUtils.isEmpty(fileName)){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"文件上传失败");
}
return generUrl(FileTypeEnum.FILE,fileName);
String url = generUrl(FileType.FILE,fileName);
log.info("文件文件成功,用户id:{},url:{}",userId,url);
return url;
}
public UploadImageVO uploadImage(MultipartFile file){
try {
Long userId = SessionContext.getSession().getId();
// 大小校验
if(file.getSize() > Constant.MAX_IMAGE_SIZE){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"图片大小不能超过5M");
@ -78,14 +83,15 @@ 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) {
log.error("上传图片失败,{}",e.getMessage(),e);
@ -94,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;

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

@ -21,7 +21,7 @@ public class FileUtil {
*/
public static boolean isImage(String fileName) {
String extension = getFileExtension(fileName);
String[] imageExtension = new String[]{"jpeg", "jpg", "bmp", "png","webp"};
String[] imageExtension = new String[]{"jpeg", "jpg", "bmp", "png","webp","gif"};
for (String e : imageExtension){
if (extension.toLowerCase().equals(e)) {
return true;

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的具体路径

6
im-server/pom.xml

@ -5,7 +5,7 @@
<parent>
<artifactId>box-im</artifactId>
<groupId>com.bx</groupId>
<version>1.0.0</version>
<version>1.1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
@ -15,8 +15,8 @@
<dependencies>
<dependency>
<groupId>com.bx</groupId>
<artifactId>commom</artifactId>
<version>1.0.0</version>
<artifactId>im-commom</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>

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

@ -1,7 +1,7 @@
package com.bx.imserver;
import com.bx.imserver.websocket.WebsocketServer;
import com.bx.imserver.ws.WebsocketServer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;

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

@ -0,0 +1,77 @@
package com.bx.imserver.processor;
import com.bx.imcommon.contant.RedisKey;
import com.bx.imcommon.enums.IMCmdType;
import com.bx.imcommon.enums.IMSendStatus;
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.imserver.util.UserChannelCtxHolder;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import java.util.List;
@Slf4j
@Component
public class GroupMessageProcessor extends MessageProcessor<IMRecvInfo<GroupMessageInfo>> {
@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){
try {
ChannelHandlerContext channelCtx = UserChannelCtxHolder.getChannelCtx(recvId);
if(channelCtx != null){
// 自己发的消息不用推送
if(recvId != messageInfo.getSendId()){
// 推送消息到用户
IMSendInfo sendInfo = new IMSendInfo();
sendInfo.setCmd(IMCmdType.GROUP_MESSAGE.code());
sendInfo.setData(messageInfo);
channelCtx.channel().writeAndFlush(sendInfo);
// 消息发送成功确认
String key = RedisKey.IM_RESULT_GROUP_QUEUE;
SendResult sendResult = new SendResult();
sendResult.setRecvId(recvId);
sendResult.setStatus(IMSendStatus.SUCCESS);
sendResult.setMessageInfo(messageInfo);
redisTemplate.opsForList().rightPush(key,sendResult);
}
}else {
// 消息发送失败确认
String key = RedisKey.IM_RESULT_GROUP_QUEUE;
SendResult sendResult = new SendResult();
sendResult.setRecvId(recvId);
sendResult.setStatus(IMSendStatus.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.setStatus(IMSendStatus.FAIL);
sendResult.setFailReason("未知异常");
sendResult.setMessageInfo(messageInfo);
redisTemplate.opsForList().rightPush(key,sendResult);
log.error("发送消息异常,发送者:{},群id:{},接收id:{},内容:{}",messageInfo.getSendId(),messageInfo.getGroupId(),recvIds,messageInfo.getContent());
}
}
}
}

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

@ -1,12 +1,12 @@
package com.bx.imserver.websocket.processor;
package com.bx.imserver.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.model.im.HeartbeatInfo;
import com.bx.common.model.im.SendInfo;
import com.bx.imserver.websocket.WebsocketServer;
import com.bx.imcommon.contant.Constant;
import com.bx.imcommon.contant.RedisKey;
import com.bx.imcommon.enums.IMCmdType;
import com.bx.imcommon.model.HeartbeatInfo;
import com.bx.imcommon.model.IMSendInfo;
import com.bx.imserver.ws.WebsocketServer;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.AttributeKey;
import lombok.extern.slf4j.Slf4j;
@ -31,8 +31,8 @@ public class HeartbeatProcessor extends MessageProcessor<HeartbeatInfo> {
@Override
public void process(ChannelHandlerContext ctx, HeartbeatInfo beatInfo) {
// 响应ws
SendInfo sendInfo = new SendInfo();
sendInfo.setCmd(WSCmdEnum.HEART_BEAT.getCode());
IMSendInfo sendInfo = new IMSendInfo();
sendInfo.setCmd(IMCmdType.HEART_BEAT.code());
ctx.channel().writeAndFlush(sendInfo);
// 设置属性

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

@ -1,13 +1,13 @@
package com.bx.imserver.websocket.processor;
package com.bx.imserver.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.model.im.LoginInfo;
import com.bx.common.model.im.SendInfo;
import com.bx.imserver.websocket.WebsocketChannelCtxHolder;
import com.bx.imserver.websocket.WebsocketServer;
import com.bx.imcommon.contant.Constant;
import com.bx.imcommon.contant.RedisKey;
import com.bx.imcommon.enums.IMCmdType;
import com.bx.imcommon.model.IMSendInfo;
import com.bx.imcommon.model.LoginInfo;
import com.bx.imserver.util.UserChannelCtxHolder;
import com.bx.imserver.ws.WebsocketServer;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.AttributeKey;
import lombok.extern.slf4j.Slf4j;
@ -32,15 +32,15 @@ public class LoginProcessor extends MessageProcessor<LoginInfo> {
@Override
synchronized public void process(ChannelHandlerContext ctx, LoginInfo loginInfo) {
log.info("用户登录,userId:{}",loginInfo.getUserId());
ChannelHandlerContext context = WebsocketChannelCtxHolder.getChannelCtx(loginInfo.getUserId());
ChannelHandlerContext context = UserChannelCtxHolder.getChannelCtx(loginInfo.getUserId());
if(context != null){
// 不允许多地登录,强制下线
SendInfo sendInfo = new SendInfo();
sendInfo.setCmd(WSCmdEnum.FORCE_LOGUT.getCode());
IMSendInfo sendInfo = new IMSendInfo();
sendInfo.setCmd(IMCmdType.FORCE_LOGUT.code());
context.channel().writeAndFlush(sendInfo);
}
// 绑定用户和channel
WebsocketChannelCtxHolder.addChannelCtx(loginInfo.getUserId(),ctx);
UserChannelCtxHolder.addChannelCtx(loginInfo.getUserId(),ctx);
// 设置用户id属性
AttributeKey<Long> attr = AttributeKey.valueOf("USER_ID");
ctx.channel().attr(attr).set(loginInfo.getUserId());
@ -51,8 +51,8 @@ public class LoginProcessor extends MessageProcessor<LoginInfo> {
String key = RedisKey.IM_USER_SERVER_ID+loginInfo.getUserId();
redisTemplate.opsForValue().set(key, WSServer.getServerId(), Constant.ONLINE_TIMEOUT_SECOND, TimeUnit.SECONDS);
// 响应ws
SendInfo sendInfo = new SendInfo();
sendInfo.setCmd(WSCmdEnum.LOGIN.getCode());
IMSendInfo sendInfo = new IMSendInfo();
sendInfo.setCmd(IMCmdType.LOGIN.code());
ctx.channel().writeAndFlush(sendInfo);
}

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

@ -1,4 +1,4 @@
package com.bx.imserver.websocket.processor;
package com.bx.imserver.processor;
import io.netty.channel.ChannelHandlerContext;

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

@ -0,0 +1,69 @@
package com.bx.imserver.processor;
import com.bx.imcommon.contant.RedisKey;
import com.bx.imcommon.enums.IMCmdType;
import com.bx.imcommon.enums.IMSendStatus;
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.imserver.util.UserChannelCtxHolder;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class PrivateMessageProcessor extends MessageProcessor<IMRecvInfo<PrivateMessageInfo>> {
@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());
try{
ChannelHandlerContext channelCtx = UserChannelCtxHolder.getChannelCtx(recvId);
if(channelCtx != null ){
// 推送消息到用户
IMSendInfo sendInfo = new IMSendInfo();
sendInfo.setCmd(IMCmdType.PRIVATE_MESSAGE.code());
sendInfo.setData(messageInfo);
channelCtx.channel().writeAndFlush(sendInfo);
// 消息发送成功确认
String key = RedisKey.IM_RESULT_PRIVATE_QUEUE;
SendResult sendResult = new SendResult();
sendResult.setRecvId(recvId);
sendResult.setStatus(IMSendStatus.SUCCESS);
sendResult.setMessageInfo(messageInfo);
redisTemplate.opsForList().rightPush(key,sendResult);
}else{
// 消息推送失败确认
String key = RedisKey.IM_RESULT_PRIVATE_QUEUE;
SendResult sendResult = new SendResult();
sendResult.setRecvId(recvId);
sendResult.setStatus(IMSendStatus.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.setStatus(IMSendStatus.FAIL);
sendResult.setFailReason("未知异常");
sendResult.setMessageInfo(messageInfo);
redisTemplate.opsForList().rightPush(key,sendResult);
log.error("发送异常,发送者:{},接收者:{},内容:{}",messageInfo.getSendId(),recvId,messageInfo.getContent(),e);
}
}
}

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

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

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

@ -1,6 +1,6 @@
package com.bx.imserver.task;
import com.bx.imserver.websocket.WebsocketServer;
import com.bx.imserver.ws.WebsocketServer;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;

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

@ -1,11 +1,12 @@
package com.bx.imserver.task;
import com.bx.common.contant.RedisKey;
import com.bx.common.enums.WSCmdEnum;
import com.bx.common.model.im.GroupMessageInfo;
import com.bx.imserver.websocket.WebsocketServer;
import com.bx.imserver.websocket.processor.MessageProcessor;
import com.bx.imserver.websocket.processor.ProcessorFactory;
import com.bx.imcommon.contant.RedisKey;
import com.bx.imcommon.enums.IMCmdType;
import com.bx.imcommon.model.GroupMessageInfo;
import com.bx.imcommon.model.IMRecvInfo;
import com.bx.imserver.processor.MessageProcessor;
import com.bx.imserver.processor.ProcessorFactory;
import com.bx.imserver.ws.WebsocketServer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
@ -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);
}
}

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

@ -1,12 +1,13 @@
package com.bx.imserver.task;
import com.bx.common.contant.RedisKey;
import com.bx.common.enums.WSCmdEnum;
import com.bx.common.model.im.PrivateMessageInfo;
import com.bx.imserver.websocket.WebsocketServer;
import com.bx.imserver.websocket.processor.MessageProcessor;
import com.bx.imserver.websocket.processor.ProcessorFactory;
import com.bx.imcommon.contant.RedisKey;
import com.bx.imcommon.enums.IMCmdType;
import com.bx.imcommon.model.IMRecvInfo;
import com.bx.imcommon.model.PrivateMessageInfo;
import com.bx.imserver.processor.MessageProcessor;
import com.bx.imserver.processor.ProcessorFactory;
import com.bx.imserver.ws.WebsocketServer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
@ -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);
}
}

2
commom/src/main/java/com/bx/common/util/SpringContextHolder.java → im-server/src/main/java/com/bx/imserver/util/SpringContextHolder.java

@ -1,4 +1,4 @@
package com.bx.common.util;
package com.bx.imserver.util;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;

4
im-server/src/main/java/com/bx/imserver/websocket/WebsocketChannelCtxHolder.java → im-server/src/main/java/com/bx/imserver/util/UserChannelCtxHolder.java

@ -1,4 +1,4 @@
package com.bx.imserver.websocket;
package com.bx.imserver.util;
import io.netty.channel.ChannelHandlerContext;
@ -6,7 +6,7 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class WebsocketChannelCtxHolder {
public class UserChannelCtxHolder {
/*
* 维护userId和ctx的关联关系格式:Map<userId,ctx>

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

@ -1,55 +0,0 @@
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.model.im.GroupMessageInfo;
import com.bx.common.model.im.SendInfo;
import com.bx.imserver.websocket.WebsocketChannelCtxHolder;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import java.util.List;
@Slf4j
@Component
public class GroupMessageProcessor extends MessageProcessor<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);
for(Long recvId:recvIds){
ChannelHandlerContext channelCtx = WebsocketChannelCtxHolder.getChannelCtx(recvId);
if(channelCtx != null){
// 自己发的消息不用推送
if(recvId != data.getSendId()){
// 推送消息到用户
SendInfo sendInfo = new SendInfo();
sendInfo.setCmd(WSCmdEnum.GROUP_MESSAGE.getCode());
sendInfo.setData(data);
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());
}
}else {
log.error("未找到WS连接,发送者:{},群id:{},接收id:{},内容:{}",data.getSendId(),data.getGroupId(),data.getRecvIds());
}
}
}
}

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

@ -1,45 +0,0 @@
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.model.im.PrivateMessageInfo;
import com.bx.common.model.im.SendInfo;
import com.bx.imserver.websocket.WebsocketChannelCtxHolder;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
@Slf4j
@Component
public class PrivateMessageProcessor extends MessageProcessor<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());
if(channelCtx != null ){
// 推送消息到用户
SendInfo sendInfo = new SendInfo();
sendInfo.setCmd(WSCmdEnum.PRIVATE_MESSAGE.getCode());
sendInfo.setData(data);
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());
}
}else{
log.error("未找到WS连接,发送者:{},接收者:{},内容:{}",data.getSendId(),data.getRecvId(),data.getContent());
}
}
}

25
im-server/src/main/java/com/bx/imserver/websocket/WebSocketHandler.java → im-server/src/main/java/com/bx/imserver/ws/WebSocketHandler.java

@ -1,11 +1,12 @@
package com.bx.imserver.websocket;
package com.bx.imserver.ws;
import com.bx.common.contant.RedisKey;
import com.bx.common.enums.WSCmdEnum;
import com.bx.common.model.im.SendInfo;
import com.bx.common.util.SpringContextHolder;
import com.bx.imserver.websocket.processor.MessageProcessor;
import com.bx.imserver.websocket.processor.ProcessorFactory;
import com.bx.imcommon.contant.RedisKey;
import com.bx.imcommon.enums.IMCmdType;
import com.bx.imcommon.model.IMSendInfo;
import com.bx.imserver.processor.MessageProcessor;
import com.bx.imserver.processor.ProcessorFactory;
import com.bx.imserver.util.SpringContextHolder;
import com.bx.imserver.util.UserChannelCtxHolder;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.timeout.IdleState;
@ -21,7 +22,7 @@ import org.springframework.data.redis.core.RedisTemplate;
* 浏览器连接状态监控
*/
@Slf4j
public class WebSocketHandler extends SimpleChannelInboundHandler<SendInfo> {
public class WebSocketHandler extends SimpleChannelInboundHandler<IMSendInfo> {
/**
* 读取到消息后进行处理
@ -31,9 +32,9 @@ public class WebSocketHandler extends SimpleChannelInboundHandler<SendInfo> {
* @throws Exception
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, SendInfo sendInfo) throws Exception {
protected void channelRead0(ChannelHandlerContext ctx, IMSendInfo 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()));
}
@ -66,11 +67,11 @@ public class WebSocketHandler extends SimpleChannelInboundHandler<SendInfo> {
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
AttributeKey<Long> attr = AttributeKey.valueOf("USER_ID");
Long userId = ctx.channel().attr(attr).get();
ChannelHandlerContext context = WebsocketChannelCtxHolder.getChannelCtx(userId);
ChannelHandlerContext context = UserChannelCtxHolder.getChannelCtx(userId);
// 判断一下,避免异地登录导致的误删
if(context != null && ctx.channel().id().equals(context.channel().id())){
// 移除channel
WebsocketChannelCtxHolder.removeChannelCtx(userId);
UserChannelCtxHolder.removeChannelCtx(userId);
// 用户下线
RedisTemplate redisTemplate = SpringContextHolder.getBean("redisTemplate");
String key = RedisKey.IM_USER_SERVER_ID + userId;

8
im-server/src/main/java/com/bx/imserver/websocket/WebsocketServer.java → im-server/src/main/java/com/bx/imserver/ws/WebsocketServer.java

@ -1,8 +1,8 @@
package com.bx.imserver.websocket;
package com.bx.imserver.ws;
import com.bx.common.contant.RedisKey;
import com.bx.imserver.websocket.endecode.MessageProtocolDecoder;
import com.bx.imserver.websocket.endecode.MessageProtocolEncoder;
import com.bx.imcommon.contant.RedisKey;
import com.bx.imserver.ws.endecode.MessageProtocolDecoder;
import com.bx.imserver.ws.endecode.MessageProtocolEncoder;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;

6
im-server/src/main/java/com/bx/imserver/websocket/endecode/MessageProtocolDecoder.java → im-server/src/main/java/com/bx/imserver/ws/endecode/MessageProtocolDecoder.java

@ -1,6 +1,6 @@
package com.bx.imserver.websocket.endecode;
package com.bx.imserver.ws.endecode;
import com.bx.common.model.im.SendInfo;
import com.bx.imcommon.model.IMSendInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageDecoder;
@ -13,7 +13,7 @@ public class MessageProtocolDecoder extends MessageToMessageDecoder<TextWebSocke
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame, List<Object> list) throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
SendInfo sendInfo = objectMapper.readValue(textWebSocketFrame.text(), SendInfo.class);
IMSendInfo sendInfo = objectMapper.readValue(textWebSocketFrame.text(), IMSendInfo.class);
list.add(sendInfo);
}
}

9
im-server/src/main/java/com/bx/imserver/websocket/endecode/MessageProtocolEncoder.java → im-server/src/main/java/com/bx/imserver/ws/endecode/MessageProtocolEncoder.java

@ -1,6 +1,6 @@
package com.bx.imserver.websocket.endecode;
package com.bx.imserver.ws.endecode;
import com.bx.common.model.im.SendInfo;
import com.bx.imcommon.model.IMSendInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageEncoder;
@ -8,12 +8,13 @@ import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import java.util.List;
public class MessageProtocolEncoder extends MessageToMessageEncoder<SendInfo> {
public class MessageProtocolEncoder extends MessageToMessageEncoder<IMSendInfo> {
@Override
protected void encode(ChannelHandlerContext channelHandlerContext, SendInfo sendInfo, List<Object> list) throws Exception {
protected void encode(ChannelHandlerContext channelHandlerContext, IMSendInfo sendInfo, List<Object> list) throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
String text = objectMapper.writeValueAsString(sendInfo);
TextWebSocketFrame frame = new TextWebSocketFrame(text);
list.add(frame);
}

12
im-ui/src/api/wssocket.js

@ -2,14 +2,14 @@ var websock = null;
let rec; //断线重连后,延迟5秒重新创建WebSocket连接 rec用来存储延迟请求的代码
let isConnect = false; //连接标识 避免重复连接
let wsurl = "";
let $store = null;
let userId = null;
let messageCallBack = null;
let openCallBack = null;
let hasLogin = false;
let createWebSocket = (url, store) => {
$store = store;
let createWebSocket = (url, id) => {
wsurl = url;
userId = id;
initWebSocket();
};
@ -45,7 +45,7 @@ let initWebSocket = () => {
// 发送登录命令
let loginInfo = {
cmd: 0,
data: {userId: $store.state.userStore.userInfo.id}
data: {userId: userId}
};
websock.send(JSON.stringify(loginInfo));
@ -88,7 +88,7 @@ var heartCheck = {
let heartBeat = {
cmd: 1,
data: {
userId: $store.state.userStore.userInfo.id
userId: userId
}
};
websock.send(JSON.stringify(heartBeat))
@ -139,8 +139,6 @@ function onopen(callback) {
}
// 将方法暴露出去
export {
createWebSocket,

21
im-ui/src/components/chat/ChatBox.vue

@ -40,7 +40,7 @@
</div>
<div title="发送语音" class="el-icon-microphone" @click="showVoiceBox()">
</div>
<div title="聊天记录" class="el-icon-chat-dot-round"></div>
<div title="聊天记录" class="el-icon-chat-dot-round" @click="showHistoryBox()"></div>
</div>
<textarea v-model="sendText" ref="sendBox" class="send-text-area"
@keydown.enter="sendTextMessage()"></textarea>
@ -57,6 +57,9 @@
</el-main>
<emotion v-show="showEmotion" :pos="emoBoxPos" @emotion="handleEmotion"></Emotion>
<chat-voice :visible="showVoice" @close="closeVoiceBox" @send="handleSendVoice"></chat-voice>
<chat-history :visible="showHistory"
:chat="chat" :friend="friend" :group="group" :groupMembers="groupMembers"
@close="closeHistoryBox"></chat-history>
</el-container>
</template>
@ -66,7 +69,9 @@
import FileUpload from "../common/FileUpload.vue";
import Emotion from "../common/Emotion.vue";
import ChatVoice from "./ChatVoice.vue";
import ChatHistory from "./ChatHistory.vue";
export default {
name: "chatPrivate",
components: {
@ -74,7 +79,8 @@
FileUpload,
ChatGroupSide,
Emotion,
ChatVoice
ChatVoice,
ChatHistory
},
props: {
chat: {
@ -93,7 +99,8 @@
emoBoxPos: { // emoji
x: 0,
y: 0
}
},
showHistory: false //
}
},
methods: {
@ -211,6 +218,12 @@
closeVoiceBox() {
this.showVoice = false;
},
showHistoryBox(){
this.showHistory = true;
},
closeHistoryBox(){
this.showHistory = false;
},
handleSendVoice(data) {
let msgInfo = {
content: JSON.stringify(data),

170
im-ui/src/components/chat/ChatHistory.vue

@ -0,0 +1,170 @@
<template>
<el-drawer title="聊天历史记录" size="700px" :visible.sync="visible" direction="rtl" :before-close="handleClose">
<div class="chat-history" v-loading="loading"
element-loading-text="拼命加载中">
<el-scrollbar class="chat-history-scrollbar" ref="scrollbar" id="historyScrollbar" >
<ul>
<li v-for="(msgInfo,idx) in messages" :key="idx">
<message-item :mine="msgInfo.sendId == mine.id" :headImage="headImage(msgInfo)" :showName="showName(msgInfo)"
:msgInfo="msgInfo" :menu="false">
</message-item>
</li>
</ul>
</el-scrollbar>
</div>
</el-drawer>
</template>
<script>
import MessageItem from './MessageItem.vue';
export default {
name: 'chatHistory',
components: {
MessageItem
},
props: {
visible: {
type: Boolean
},
chat: {
type: Object
},
friend: {
type: Object
},
group: {
type: Object
},
groupMembers: {
type: Array,
}
},
data() {
return {
page: 1,
size: 10,
messages: [],
loadAll: false,
loading: false,
lastScrollTime: new Date()
}
},
methods: {
handleClose() {
this.page = 1;
this.messages = [];
this.loadAll = false;
this.$emit('close');
},
handleScroll() {
let high = this.$refs.scrollbar.$refs.wrap.scrollTop; //
let timeDiff = new Date().getTime() - this.lastScrollTime.getTime();
if ( high < 30 && timeDiff>500) {
this.lastScrollTime = new Date();
this.loadMessages();
}
},
loadMessages() {
if(this.loadAll){
return this.$message.success("已到达顶部");
}
let param = {
page: this.page++,
size: this.size
}
if (this.chat.type == 'GROUP') {
param.groupId = this.group.id;
} else {
param.friendId = this.friend.id;
}
this.loading = true;
this.$http({
url: this.histroyAction,
method: 'get',
params: param
}).then(messages => {
messages.forEach(m => this.messages.unshift(m));
this.loading = false;
if(messages.length <this.size){
this.loadAll = true;
}
this.refreshScrollPos();
}).catch(()=>{
this.loading = false;
})
},
showName(msgInfo) {
if (this.chat.type == 'GROUP') {
let member = this.groupMembers.find((m) => m.userId == msgInfo.sendId);
return member ? member.aliasName : "";
} else {
return msgInfo.sendId == this.mine.id ? this.mine.nickName : this.chat.showName
}
},
headImage(msgInfo) {
if (this.chat.type == 'GROUP') {
let member = this.groupMembers.find((m) => m.userId == msgInfo.sendId);
return member ? member.headImage : "";
} else {
return msgInfo.sendId == this.mine.id ? this.mine.headImageThumb : this.chat.headImage
}
},
refreshScrollPos(){
let scrollWrap = this.$refs.scrollbar.$refs.wrap;
let scrollHeight = scrollWrap.scrollHeight;
let scrollTop = scrollWrap.scrollTop;
this.$nextTick(() => {
let offsetTop = scrollWrap.scrollHeight - scrollHeight;
scrollWrap.scrollTop = scrollTop + offsetTop;
//
if(scrollWrap.scrollHeight == scrollHeight){
this.loadMessages();
}
});
}
},
computed: {
mine() {
return this.$store.state.userStore.userInfo;
},
histroyAction() {
return `/message/${this.chat.type.toLowerCase()}/history`;
}
},
watch: {
visible: {
handler(newValue, oldValue) {
if (newValue) {
this.loadMessages();
this.$nextTick(() => {
document.getElementById('historyScrollbar').addEventListener("mousewheel", this.handleScroll,true);
});
}
}
}
}
}
</script>
<style lang="scss">
.chat-history {
display: flex;
height: 100%;
.chat-history-scrollbar {
flex: 1;
.el-scrollbar__thumb {
background-color: #555555;
}
ul {
padding: 20px;
li {
list-style-type: none;
}
}
}
}
</style>

6
im-ui/src/components/chat/MessageItem.vue

@ -37,7 +37,7 @@
</div>
</div>
<right-menu v-show="rightMenu.show" :pos="rightMenu.pos" :items="menuItems" @close="rightMenu.show=false"
<right-menu v-show="menu && rightMenu.show" :pos="rightMenu.pos" :items="menuItems" @close="rightMenu.show=false"
@select="handleSelectMenu"></right-menu>
</div>
</template>
@ -70,6 +70,10 @@
msgInfo: {
type: Object,
required: true
},
menu:{
type: Boolean,
default: true
}
},
data() {

2
im-ui/src/view/Home.vue

@ -63,7 +63,7 @@
init(userInfo) {
this.$store.commit("setUserInfo", userInfo);
this.$store.commit("initStore");
this.$wsApi.createWebSocket(process.env.VUE_APP_WS_URL, this.$store);
this.$wsApi.createWebSocket(process.env.VUE_APP_WS_URL, userInfo.id);
this.$wsApi.onopen(() => {
this.pullUnreadMessage();
});

5
pom.xml

@ -7,12 +7,13 @@
<artifactId>box-im</artifactId>
<groupId>com.bx</groupId>
<packaging>pom</packaging>
<version>1.0.0</version>
<version>1.1.0</version>
<modules>
<module>im-platform</module>
<module>im-server</module>
<module>commom</module>
<module>im-commom</module>
<module>im-client</module>
</modules>
<properties>

Loading…
Cancel
Save