Browse Source

!31 springboot升级

Merge pull request !31 from blue/v_2.0.0
master
blue 2 years ago
committed by Gitee
parent
commit
bc6c42e010
No known key found for this signature in database GPG Key ID: 173E9B9CA92EEF8F
  1. 4
      im-client/src/main/java/com/bx/imclient/IMClient.java
  2. 9
      im-client/src/main/java/com/bx/imclient/config/RedisConfig.java
  3. 11
      im-client/src/main/java/com/bx/imclient/sender/IMSender.java
  4. 13
      im-client/src/main/java/com/bx/imclient/task/AbstractMessageResultTask.java
  5. 11
      im-client/src/main/java/com/bx/imclient/task/GroupMessageResultResultTask.java
  6. 11
      im-client/src/main/java/com/bx/imclient/task/PrivateMessageResultResultTask.java
  7. 4
      im-commom/src/main/java/com/bx/imcommon/enums/IMCmdType.java
  8. 4
      im-commom/src/main/java/com/bx/imcommon/enums/IMListenerType.java
  9. 17
      im-commom/src/main/java/com/bx/imcommon/enums/IMSendCode.java
  10. 4
      im-commom/src/main/java/com/bx/imcommon/enums/IMTerminalType.java
  11. 3
      im-commom/src/main/java/com/bx/imcommon/model/IMGroupMessage.java
  12. 10
      im-commom/src/main/java/com/bx/imcommon/model/IMRecvInfo.java
  13. 4
      im-commom/src/main/java/com/bx/imcommon/model/IMSendInfo.java
  14. 4
      im-commom/src/main/java/com/bx/imcommon/model/IMSendResult.java
  15. 5
      im-commom/src/main/java/com/bx/imcommon/model/IMSessionInfo.java
  16. 2
      im-commom/src/main/java/com/bx/imcommon/model/IMUserInfo.java
  17. 28
      im-commom/src/main/java/com/bx/imcommon/util/JwtUtil.java
  18. 4
      im-platform/pom.xml
  19. 3
      im-platform/src/main/java/com/bx/implatform/config/GlobalCorsConfig.java
  20. 6
      im-platform/src/main/java/com/bx/implatform/config/ICEServer.java
  21. 19
      im-platform/src/main/java/com/bx/implatform/config/MvcConfig.java
  22. 5
      im-platform/src/main/java/com/bx/implatform/controller/FileController.java
  23. 4
      im-platform/src/main/java/com/bx/implatform/controller/FriendController.java
  24. 6
      im-platform/src/main/java/com/bx/implatform/controller/GroupController.java
  25. 10
      im-platform/src/main/java/com/bx/implatform/controller/GroupMessageController.java
  26. 9
      im-platform/src/main/java/com/bx/implatform/controller/LoginController.java
  27. 11
      im-platform/src/main/java/com/bx/implatform/controller/PrivateMessageController.java
  28. 4
      im-platform/src/main/java/com/bx/implatform/controller/UserController.java
  29. 4
      im-platform/src/main/java/com/bx/implatform/controller/WebrtcController.java
  30. 1
      im-platform/src/main/java/com/bx/implatform/dto/LoginDTO.java
  31. 12
      im-platform/src/main/java/com/bx/implatform/interceptor/AuthInterceptor.java
  32. 3
      im-platform/src/main/java/com/bx/implatform/interceptor/XssInterceptor.java
  33. 8
      im-platform/src/main/java/com/bx/implatform/listener/GroupMessageListener.java
  34. 5
      im-platform/src/main/java/com/bx/implatform/listener/PrivateMessageListener.java
  35. 36
      im-platform/src/main/java/com/bx/implatform/result/ResultUtils.java
  36. 35
      im-platform/src/main/java/com/bx/implatform/service/IFriendService.java
  37. 44
      im-platform/src/main/java/com/bx/implatform/service/IGroupMemberService.java
  38. 43
      im-platform/src/main/java/com/bx/implatform/service/IGroupMessageService.java
  39. 57
      im-platform/src/main/java/com/bx/implatform/service/IGroupService.java
  40. 40
      im-platform/src/main/java/com/bx/implatform/service/IPrivateMessageService.java
  41. 55
      im-platform/src/main/java/com/bx/implatform/service/IUserService.java
  42. 1
      im-platform/src/main/java/com/bx/implatform/service/IWebrtcService.java
  43. 73
      im-platform/src/main/java/com/bx/implatform/service/impl/FriendServiceImpl.java
  44. 61
      im-platform/src/main/java/com/bx/implatform/service/impl/GroupMemberServiceImpl.java
  45. 118
      im-platform/src/main/java/com/bx/implatform/service/impl/GroupMessageServiceImpl.java
  46. 205
      im-platform/src/main/java/com/bx/implatform/service/impl/GroupServiceImpl.java
  47. 58
      im-platform/src/main/java/com/bx/implatform/service/impl/PrivateMessageServiceImpl.java
  48. 165
      im-platform/src/main/java/com/bx/implatform/service/impl/UserServiceImpl.java
  49. 14
      im-platform/src/main/java/com/bx/implatform/service/impl/WebrtcServiceImpl.java
  50. 21
      im-platform/src/main/java/com/bx/implatform/service/thirdparty/FileService.java
  51. 2
      im-platform/src/main/java/com/bx/implatform/session/WebrtcSession.java
  52. 133
      im-platform/src/main/java/com/bx/implatform/util/BeanUtils.java
  53. 18
      im-platform/src/main/java/com/bx/implatform/util/DateTimeUtils.java
  54. 3
      im-platform/src/main/java/com/bx/implatform/util/FileUtil.java
  55. 58
      im-platform/src/main/java/com/bx/implatform/util/MinioUtil.java
  56. 4
      im-platform/src/main/java/com/bx/implatform/util/XssUtil.java
  57. 1
      im-platform/src/main/java/com/bx/implatform/vo/GroupVO.java
  58. 2
      im-platform/src/main/java/com/bx/implatform/vo/PrivateMessageVO.java
  59. 16
      im-platform/src/main/resources/application.yml
  60. 18
      im-server/src/main/java/com/bx/imserver/config/RedisConfig.java
  61. 23
      im-server/src/main/java/com/bx/imserver/netty/IMChannelHandler.java
  62. 5
      im-server/src/main/java/com/bx/imserver/netty/IMServerGroup.java
  63. 1
      im-server/src/main/java/com/bx/imserver/netty/UserChannelCtxMap.java
  64. 10
      im-server/src/main/java/com/bx/imserver/netty/processor/GroupMessageProcessor.java
  65. 18
      im-server/src/main/java/com/bx/imserver/netty/processor/HeartbeatProcessor.java
  66. 15
      im-server/src/main/java/com/bx/imserver/netty/processor/LoginProcessor.java
  67. 12
      im-server/src/main/java/com/bx/imserver/netty/processor/PrivateMessageProcessor.java
  68. 8
      im-server/src/main/java/com/bx/imserver/netty/processor/ProcessorFactory.java
  69. 7
      im-server/src/main/java/com/bx/imserver/netty/ws/WebSocketServer.java
  70. 1
      im-server/src/main/java/com/bx/imserver/netty/ws/endecode/MessageProtocolEncoder.java
  71. 12
      im-server/src/main/java/com/bx/imserver/task/AbstractPullMessageTask.java
  72. 9
      im-server/src/main/java/com/bx/imserver/task/PullGroupMessageTask.java
  73. 11
      im-server/src/main/java/com/bx/imserver/task/PullPrivateMessageTask.java
  74. 2
      im-ui/.env.development
  75. 4
      im-ui/src/App.vue
  76. 4
      im-ui/src/api/httpRequest.js
  77. BIN
      im-ui/src/assets/image/icp_logo.png
  78. 25
      im-ui/src/components/chat/ChatBox.vue
  79. 2
      im-ui/src/components/chat/ChatPrivateVideo.vue
  80. 60
      im-ui/src/components/common/FileUpload.vue
  81. 1
      im-ui/src/components/common/HeadImage.vue
  82. 26
      im-ui/src/components/common/Icp.vue
  83. 3
      im-ui/src/components/common/RightMenu.vue
  84. 43
      im-ui/src/components/group/AddGroupMember.vue
  85. 11
      im-ui/src/components/setting/Setting.vue
  86. 69
      im-ui/src/store/chatStore.js
  87. 27
      im-ui/src/store/friendStore.js
  88. 19
      im-ui/src/store/groupStore.js
  89. 19
      im-ui/src/view/Chat.vue
  90. 23
      im-ui/src/view/Friend.vue
  91. 19
      im-ui/src/view/Group.vue
  92. 226
      im-ui/src/view/Login.vue
  93. 7
      im-ui/src/view/Register.vue
  94. 2
      im-uniapp/App.vue
  95. 2
      im-uniapp/components/chat-message-item/chat-message-item.vue
  96. 50
      im-uniapp/pages/chat/chat-box.vue
  97. 45
      im-uniapp/store/chatStore.js
  98. 8
      pom.xml

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

@ -4,16 +4,16 @@ import com.bx.imclient.sender.IMSender;
import com.bx.imcommon.enums.IMTerminalType;
import com.bx.imcommon.model.IMGroupMessage;
import com.bx.imcommon.model.IMPrivateMessage;
import org.springframework.beans.factory.annotation.Autowired;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Configuration;
import java.util.List;
import java.util.Map;
@Configuration
@AllArgsConstructor
public class IMClient {
@Autowired
private IMSender imSender;
/**

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

@ -1,21 +1,12 @@
package com.bx.imclient.config;
import com.alibaba.fastjson.support.spring.FastJsonRedisSerializer;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
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.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import javax.annotation.Resource;
@Configuration("IMRedisConfig")
public class RedisConfig {

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

@ -8,22 +8,21 @@ import com.bx.imcommon.enums.IMListenerType;
import com.bx.imcommon.enums.IMSendCode;
import com.bx.imcommon.enums.IMTerminalType;
import com.bx.imcommon.model.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.*;
@Service
@RequiredArgsConstructor
public class IMSender {
@Autowired
@Qualifier("IMRedisTemplate")
@Resource(name="IMRedisTemplate")
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private MessageListenerMulticaster listenerMulticaster;
private final MessageListenerMulticaster listenerMulticaster;
public<T> void sendPrivateMessage(IMPrivateMessage<T> message) {
for (Integer terminal : message.getRecvTerminals()) {

13
im-client/src/main/java/com/bx/imclient/task/AbstractPullMessageTask.java → im-client/src/main/java/com/bx/imclient/task/AbstractMessageResultTask.java

@ -2,20 +2,22 @@ package com.bx.imclient.task;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.concurrent.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Slf4j
public abstract class AbstractPullMessageTask {
public abstract class AbstractMessageResultTask implements CommandLineRunner {
private int threadNum = 8;
private ExecutorService executorService = Executors.newFixedThreadPool(threadNum);
@PostConstruct
public void init(){
@Override
public void run(String... args) throws Exception {
// 初始化定时器
for(int i=0;i<threadNum;i++){
executorService.execute(new Runnable() {
@ -36,6 +38,7 @@ public abstract class AbstractPullMessageTask {
}
}
@PreDestroy
public void destroy(){
log.info("{}线程任务关闭",this.getClass().getSimpleName());

11
im-client/src/main/java/com/bx/imclient/task/PullSendResultGroupMessageTask.java → im-client/src/main/java/com/bx/imclient/task/GroupMessageResultResultTask.java

@ -5,21 +5,20 @@ import com.bx.imclient.listener.MessageListenerMulticaster;
import com.bx.imcommon.contant.IMRedisKey;
import com.bx.imcommon.enums.IMListenerType;
import com.bx.imcommon.model.IMSendResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import lombok.AllArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
@Component
public class PullSendResultGroupMessageTask extends AbstractPullMessageTask{
@AllArgsConstructor
public class GroupMessageResultResultTask extends AbstractMessageResultTask {
@Qualifier("IMRedisTemplate")
@Autowired
@Resource(name = "IMRedisTemplate")
private RedisTemplate<String,Object> redisTemplate;
@Autowired
private MessageListenerMulticaster listenerMulticaster;
@Override

11
im-client/src/main/java/com/bx/imclient/task/PullSendResultPrivateMessageTask.java → im-client/src/main/java/com/bx/imclient/task/PrivateMessageResultResultTask.java

@ -5,26 +5,25 @@ import com.bx.imclient.listener.MessageListenerMulticaster;
import com.bx.imcommon.contant.IMRedisKey;
import com.bx.imcommon.enums.IMListenerType;
import com.bx.imcommon.model.IMSendResult;
import lombok.AllArgsConstructor;
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 javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
;
@Slf4j
@Component
public class PullSendResultPrivateMessageTask extends AbstractPullMessageTask{
@AllArgsConstructor
public class PrivateMessageResultResultTask extends AbstractMessageResultTask {
@Qualifier("IMRedisTemplate")
@Autowired
@Resource(name = "IMRedisTemplate")
private RedisTemplate<String,Object> redisTemplate;
@Autowired
private MessageListenerMulticaster listenerMulticaster;
@Override

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

@ -13,9 +13,9 @@ public enum IMCmdType {
GROUP_MESSAGE(4,"群发消息");
private Integer code;
private final Integer code;
private String desc;
private final String desc;
public static IMCmdType fromCode(Integer code){

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

@ -8,9 +8,9 @@ public enum IMListenerType{
PRIVATE_MESSAGE(1,"私聊消息"),
GROUP_MESSAGE(2,"群聊消息");
private Integer code;
private final Integer code;
private String desc;
private final String desc;

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

@ -10,21 +10,8 @@ public enum IMSendCode {
NOT_FIND_CHANNEL(2,"未找到对方的channel"),
UNKONW_ERROR(9999,"未知异常");
private Integer code;
private String desc;
public static IMSendCode fromCode(Integer code){
for (IMSendCode typeEnum:values()) {
if (typeEnum.code.equals(code)) {
return typeEnum;
}
}
return null;
}
private final Integer code;
private final String desc;
public Integer code(){
return this.code;

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

@ -12,9 +12,9 @@ public enum IMTerminalType {
WEB(0,"web"),
APP(1,"app");
private Integer code;
private final Integer code;
private String desc;
private final String desc;
public static IMTerminalType fromCode(Integer code){

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

@ -3,7 +3,6 @@ package com.bx.imcommon.model;
import com.bx.imcommon.enums.IMTerminalType;
import lombok.Data;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
@ -18,7 +17,7 @@ public class IMGroupMessage<T> {
/**
* 接收者id列表(群成员列表,为空则不会推送)
*/
private List<Long> recvIds = Collections.EMPTY_LIST;
private List<Long> recvIds = new LinkedList<>();
/**

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

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

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

@ -5,12 +5,12 @@ import lombok.Data;
@Data
public class IMSendInfo<T> {
/*
/**
* 命令
*/
private Integer cmd;
/*
/**
* 推送消息体
*/
private T data;

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

@ -15,12 +15,12 @@ public class IMSendResult<T> {
*/
private IMUserInfo receiver;
/*
/**
* 发送状态 IMCmdType
*/
private Integer code;
/*
/**
* 消息内容
*/
private T data;

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

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

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

@ -4,8 +4,6 @@ import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* @author: 谢绍许
* @date: 2023-09-24 09:23:11

28
im-commom/src/main/java/com/bx/imcommon/util/JwtUtil.java

@ -12,11 +12,11 @@ public class JwtUtil {
/**
* 生成jwt字符串 JWT(json web token)
* @param userId
* @param info
* @param expireIn
* @param secret
* @return
* @param userId 用户id
* @param info 用户细腻系
* @param expireIn 过期时间
* @param secret 秘钥
* @return token
* */
public static String sign(Long userId, String info,long expireIn,String secret) {
try {
@ -38,8 +38,8 @@ public class JwtUtil {
/**
* 根据token获取userId
* @param token
* @return
* @param token 登录token
* @return 用户id
* */
public static Long getUserId(String token) {
try {
@ -51,9 +51,9 @@ public class JwtUtil {
}
/**
* 根据token获取自定义数据info
* @param token
* @return
* 根据token获取用户数据
* @param token 用户登录token
* @return 用户数据
* */
public static String getInfo(String token) {
try {
@ -65,11 +65,11 @@ public class JwtUtil {
/**
* 校验token
* @param token
* @param secret
* @return
* @param token 用户登录token
* @param secret 秘钥
* @return true/false
* */
public static boolean checkSign(String token,String secret) {
public static Boolean checkSign(String token,String secret) {
try{
Algorithm algorithm = Algorithm.HMAC256(secret);
JWTVerifier verifier = JWT.require(algorithm).build();

4
im-platform/pom.xml

@ -69,6 +69,10 @@
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!--minio-->
<dependency>
<groupId>io.minio</groupId>

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

@ -2,7 +2,6 @@ package com.bx.implatform.config;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
@ -10,7 +9,7 @@ import org.springframework.web.filter.CorsFilter;
import java.util.Arrays;
@Configuration
//@Configuration
public class GlobalCorsConfig {
@Bean

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

@ -6,14 +6,8 @@ import lombok.Data;
@Data
public class ICEServer {
private String urls;
private String username;
private String credential;
}

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

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

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

@ -6,8 +6,8 @@ import com.bx.implatform.service.thirdparty.FileService;
import com.bx.implatform.vo.UploadImageVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
@ -16,10 +16,9 @@ import org.springframework.web.multipart.MultipartFile;
@Slf4j
@RestController
@Api(tags = "文件上传")
@AllArgsConstructor
public class FileController {
@Autowired
private FileService fileService;
@ApiOperation(value = "上传图片",notes="上传图片,上传后返回原图和缩略图的url")

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

@ -8,7 +8,7 @@ import com.bx.implatform.session.SessionContext;
import com.bx.implatform.vo.FriendVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
@ -19,9 +19,9 @@ import java.util.stream.Collectors;
@Api(tags = "好友")
@RestController
@RequestMapping("/friend")
@AllArgsConstructor
public class FriendController {
@Autowired
private IFriendService friendService;
@GetMapping("/list")

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

@ -1,6 +1,5 @@
package com.bx.implatform.controller;
import com.bx.implatform.result.Result;
import com.bx.implatform.result.ResultUtils;
import com.bx.implatform.service.IGroupService;
@ -9,11 +8,10 @@ import com.bx.implatform.vo.GroupMemberVO;
import com.bx.implatform.vo.GroupVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.List;
@ -21,9 +19,9 @@ import java.util.List;
@Api(tags = "群聊")
@RestController
@RequestMapping("/group")
@AllArgsConstructor
public class GroupController {
@Autowired
private IGroupService groupService;
@ApiOperation(value = "创建群聊",notes="创建群聊")

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

@ -1,15 +1,13 @@
package com.bx.implatform.controller;
import com.bx.implatform.vo.GroupMessageVO;
import com.bx.implatform.dto.GroupMessageDTO;
import com.bx.implatform.result.Result;
import com.bx.implatform.result.ResultUtils;
import com.bx.implatform.service.IGroupMessageService;
import com.bx.implatform.dto.GroupMessageDTO;
import com.bx.implatform.vo.PrivateMessageVO;
import com.bx.implatform.vo.GroupMessageVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
@ -20,9 +18,9 @@ import java.util.List;
@Api(tags = "群聊消息")
@RestController
@RequestMapping("/message/group")
@AllArgsConstructor
public class GroupMessageController {
@Autowired
private IGroupMessageService groupMessageService;

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

@ -1,16 +1,15 @@
package com.bx.implatform.controller;
import com.bx.implatform.dto.LoginDTO;
import com.bx.implatform.dto.ModifyPwdDTO;
import com.bx.implatform.dto.RegisterDTO;
import com.bx.implatform.result.Result;
import com.bx.implatform.result.ResultUtils;
import com.bx.implatform.service.IUserService;
import com.bx.implatform.dto.LoginDTO;
import com.bx.implatform.dto.RegisterDTO;
import com.bx.implatform.vo.LoginVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
@ -19,9 +18,9 @@ import javax.validation.Valid;
@Api(tags = "用户登录和注册")
@RestController
@AllArgsConstructor
public class LoginController {
@Autowired
private IUserService userService;
@PostMapping("/login")

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

@ -1,14 +1,13 @@
package com.bx.implatform.controller;
import com.bx.implatform.vo.PrivateMessageVO;
import com.bx.implatform.dto.PrivateMessageDTO;
import com.bx.implatform.result.Result;
import com.bx.implatform.result.ResultUtils;
import com.bx.implatform.service.IPrivateMessageService;
import com.bx.implatform.dto.PrivateMessageDTO;
import com.bx.implatform.vo.PrivateMessageVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
@ -18,9 +17,9 @@ import java.util.List;
@Api(tags = "私聊消息")
@RestController
@RequestMapping("/message/private")
@AllArgsConstructor
public class PrivateMessageController {
@Autowired
private IPrivateMessageService privateMessageService;
@PostMapping("/send")

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

@ -11,7 +11,7 @@ import com.bx.implatform.vo.OnlineTerminalVO;
import com.bx.implatform.vo.UserVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
@ -22,9 +22,9 @@ import java.util.List;
@Api(tags = "用户")
@RestController
@RequestMapping("/user")
@AllArgsConstructor
public class UserController {
@Autowired
private IUserService userService;

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

@ -6,7 +6,7 @@ import com.bx.implatform.result.ResultUtils;
import com.bx.implatform.service.IWebrtcService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@ -14,9 +14,9 @@ import java.util.List;
@Api(tags = "webrtc视频单人通话")
@RestController
@RequestMapping("/webrtc/private")
@AllArgsConstructor
public class WebrtcController {
@Autowired
private IWebrtcService webrtcService;
@ApiOperation(httpMethod = "POST", value = "呼叫视频通话")

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

@ -3,7 +3,6 @@ package com.bx.implatform.dto;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;

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

@ -1,25 +1,27 @@
package com.bx.implatform.interceptor;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.bx.imcommon.util.JwtUtil;
import com.bx.implatform.config.JwtProperties;
import com.bx.implatform.enums.ResultCode;
import com.bx.implatform.exception.GlobalException;
import com.bx.implatform.session.UserSession;
import com.bx.imcommon.util.JwtUtil;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Slf4j
@Component
@AllArgsConstructor
public class AuthInterceptor implements HandlerInterceptor {
@Autowired
private JwtProperties jwtProperties;
private final JwtProperties jwtProperties;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

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

@ -5,6 +5,7 @@ import com.bx.implatform.exception.GlobalException;
import com.bx.implatform.util.XssUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
@ -13,9 +14,9 @@ import java.io.BufferedReader;
import java.util.Map;
@Slf4j
@Component
public class XssInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
// 检查参数

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

@ -4,19 +4,19 @@ import com.bx.imclient.annotation.IMListener;
import com.bx.imclient.listener.MessageListener;
import com.bx.imcommon.enums.IMListenerType;
import com.bx.imcommon.enums.IMSendCode;
import com.bx.implatform.vo.GroupMessageVO;
import com.bx.imcommon.model.IMSendResult;
import com.bx.implatform.contant.RedisKey;
import com.bx.implatform.vo.GroupMessageVO;
import lombok.AllArgsConstructor;
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)
@AllArgsConstructor
public class GroupMessageListener implements MessageListener<GroupMessageVO> {
@Autowired
private RedisTemplate<String,Object> redisTemplate;
@Override

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

@ -5,23 +5,24 @@ import com.bx.imclient.annotation.IMListener;
import com.bx.imclient.listener.MessageListener;
import com.bx.imcommon.enums.IMListenerType;
import com.bx.imcommon.enums.IMSendCode;
import com.bx.implatform.vo.PrivateMessageVO;
import com.bx.imcommon.model.IMSendResult;
import com.bx.implatform.entity.PrivateMessage;
import com.bx.implatform.enums.MessageStatus;
import com.bx.implatform.service.IPrivateMessageService;
import com.bx.implatform.vo.PrivateMessageVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
@Slf4j
@IMListener(type = IMListenerType.PRIVATE_MESSAGE)
public class PrivateMessageListener implements MessageListener<PrivateMessageVO> {
@Lazy
@Autowired
private IPrivateMessageService privateMessageService;
@Override
public void process(IMSendResult<PrivateMessageVO> result){
PrivateMessageVO messageInfo = result.getData();

36
im-platform/src/main/java/com/bx/implatform/result/ResultUtils.java

@ -5,61 +5,53 @@ import com.bx.implatform.enums.ResultCode;
public class ResultUtils {
public static final <T> Result<T> success(){
Result result=new Result();
public static <T> Result<T> success(){
Result<T> result=new Result<>();
result.setCode(ResultCode.SUCCESS.getCode());
result.setMessage(ResultCode.SUCCESS.getMsg());
return result;
}
public static final <T> Result<T> success(T data){
Result result=new Result();
public static <T> Result<T> success(T data){
Result<T> result=new Result<>();
result.setCode(ResultCode.SUCCESS.getCode());
result.setMessage(ResultCode.SUCCESS.getMsg());
result.setData(data);
return result;
}
public static final <T> Result<T> success(T data, String messsage){
Result result=new Result();
public static <T> Result<T> success(T data, String messsage){
Result<T> result=new Result<>();
result.setCode(ResultCode.SUCCESS.getCode());
result.setMessage(messsage);
result.setData(data);
return result;
}
public static final <T> Result<T> success(String messsage){
Result result=new Result();
public static <T> Result<T> success(String messsage){
Result<T> result=new Result<>();
result.setCode(ResultCode.SUCCESS.getCode());
result.setMessage(messsage);
return result;
}
public static final <T> Result<T> error(Integer code, String messsage){
Result result=new Result();
public static <T> Result<T> error(Integer code, String messsage){
Result<T> result=new Result<>();
result.setCode(code);
result.setMessage(messsage);
return result;
}
public static final <T> Result<T> error(ResultCode resultCode, String messsage){
Result result=new Result();
public static <T> Result<T> error(ResultCode resultCode, String messsage){
Result<T> result=new Result<>();
result.setCode(resultCode.getCode());
result.setMessage(messsage);
return result;
}
public static final <T> Result<T> error(ResultCode resultCode, String messsage, T data){
Result result=new Result();
result.setCode(resultCode.getCode());
result.setMessage(messsage);
result.setData(data);
return result;
}
public static final <T> Result<T> error(ResultCode resultCode){
Result result=new Result();
public static <T> Result<T> error(ResultCode resultCode){
Result<T> result=new Result<>();
result.setCode(resultCode.getCode());
result.setMessage(resultCode.getMsg());
return result;

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

@ -9,15 +9,50 @@ import java.util.List;
public interface IFriendService extends IService<Friend> {
/**
* 判断用户2是否用户1的好友
*
* @param userId1 用户1的id
* @param userId2 用户2的id
* @return true/false
*/
Boolean isFriend(Long userId1, Long userId2);
/**
* 查询用户的所有好友
*
* @param userId 用户id
* @return 好友列表
*/
List<Friend> findFriendByUserId(Long userId);
/**
* 添加好友互相建立好友关系
*
* @param friendId 好友的用户id
*/
void addFriend(Long friendId);
/**
* 删除好友双方都会解除好友关系
*
* @param friendId 好友的用户id
*/
void delFriend(Long friendId);
/**
* 更新好友信息主要是头像和昵称
*
* @param vo 好友vo
*/
void update(FriendVO vo);
/**
* 查询指定的某个好友信息
*
* @param friendId 好友的用户id
* @return 好友信息
*/
FriendVO findFriend(Long friendId);
}

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

@ -9,18 +9,60 @@ import java.util.List;
public interface IGroupMemberService extends IService<GroupMember> {
/**
* 根据群聊id和用户id查询群聊成员
*
* @param groupId 群聊id
* @param userId 用户id
* @return 群聊成员信息
*/
GroupMember findByGroupAndUserId(Long groupId,Long userId);
/**
* 根据用户id查询群聊成员
*
* @param userId 用户id
* @return 成员列表
*/
List<GroupMember> findByUserId(Long userId);
/**
* 根据群聊id查询群聊成员包括已退出
*
* @param groupId 群聊id
* @return 群聊成员列表
*/
List<GroupMember> findByGroupId(Long groupId);
/**
* 根据群聊id查询没有退出的群聊成员id
*
* @param groupId 群聊id
* @return 群聊成员id列表
*/
List<Long> findUserIdsByGroupId(Long groupId);
/**
* 批量添加成员
*
* @param groupId 群聊id
* @param members 成员列表
* @return 成功或失败
*/
boolean saveOrUpdateBatch(Long groupId,List<GroupMember> members);
/**
* 根据群聊id删除移除成员
*
* @param groupId 群聊id
*/
void removeByGroupId(Long groupId);
/**
*根据群聊id和用户id移除成员
*
* @param groupId 群聊id
* @param userId 用户id
*/
void removeByGroupAndUserId(Long groupId,Long userId);
}

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

@ -1,25 +1,58 @@
package com.bx.implatform.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.bx.implatform.vo.GroupMessageVO;
import com.bx.implatform.entity.GroupMessage;
import com.bx.implatform.dto.GroupMessageDTO;
import com.bx.implatform.entity.GroupMessage;
import com.bx.implatform.vo.GroupMessageVO;
import java.util.List;
public interface IGroupMessageService extends IService<GroupMessage> {
Long sendMessage(GroupMessageDTO vo);
/**
* 发送群聊消息(高并发接口查询mysql接口都要进行缓存)
*
* @param dto 群聊消息
* @return 群聊id
*/
Long sendMessage(GroupMessageDTO dto);
/**
* 撤回消息
*
* @param id 消息id
*/
void recallMessage(Long id);
/**
*
* 异步拉取群聊消息通过websocket异步推送
*/
void pullUnreadMessage();
/**
* 拉取消息只能拉取最近1个月的消息一次拉取100条
*
* @param minId 消息起始id
* @return 聊天消息列表
*/
List<GroupMessageVO> loadMessage(Long minId);
/**
* 消息已读,同步其他终端清空未读数量
*
* @param groupId 群聊
*/
void readedMessage(Long groupId);
/**
* 拉取历史聊天记录
*
* @param groupId 群聊id
* @param page 页码
* @param size 页码大小
* @return 聊天记录列表
*/
List<GroupMessageVO> findHistoryMessage(Long groupId, Long page, Long size);
}

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

@ -11,24 +11,79 @@ import java.util.List;
public interface IGroupService extends IService<Group> {
/**
* 创建新群聊
*
* @param vo 群聊信息
* @return 群聊信息
**/
GroupVO createGroup(GroupVO vo);
/**
* 修改群聊信息
*
* @param vo 群聊信息
* @return 群聊信息
**/
GroupVO modifyGroup(GroupVO vo);
/**
* 删除群聊
*
* @param groupId 群聊id
**/
void deleteGroup(Long groupId);
/**
* 退出群聊
*
* @param groupId 群聊id
*/
void quitGroup(Long groupId);
/**
* 将用户踢出群聊
*
* @param groupId 群聊id
* @param userId 用户id
*/
void kickGroup(Long groupId,Long userId);
/**
* 查询当前用户的所有群聊
*
* @return 群聊信息列表
**/
List<GroupVO> findGroups();
/**
* 邀请好友进群
*
* @param vo 群id好友id列表
**/
void invite(GroupInviteVO vo);
/**
* 根据id查找群聊并进行缓存
*
* @param groupId 群聊id
* @return 群聊实体
*/
Group getById(Long groupId);
/**
* 根据id查找群聊
*
* @param groupId 群聊id
* @return 群聊vo
*/
GroupVO findById(Long groupId);
/**
* 查询群成员
*
* @param groupId 群聊id
* @return List<GroupMemberVO>
**/
List<GroupMemberVO> findGroupMembers(Long groupId);
}

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

@ -1,24 +1,58 @@
package com.bx.implatform.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.bx.implatform.vo.PrivateMessageVO;
import com.bx.implatform.entity.PrivateMessage;
import com.bx.implatform.dto.PrivateMessageDTO;
import com.bx.implatform.entity.PrivateMessage;
import com.bx.implatform.vo.PrivateMessageVO;
import java.util.List;
public interface IPrivateMessageService extends IService<PrivateMessage> {
Long sendMessage(PrivateMessageDTO vo);
/**
* 发送私聊消息(高并发接口查询mysql接口都要进行缓存)
*
* @param dto 私聊消息
* @return 消息id
*/
Long sendMessage(PrivateMessageDTO dto);
/**
* 撤回消息
*
* @param id 消息id
*/
void recallMessage(Long id);
/**
* 拉取历史聊天记录
*
* @param friendId 好友id
* @param page 页码
* @param size 页码大小
* @return 聊天记录列表
*/
List<PrivateMessageVO> findHistoryMessage(Long friendId, Long page,Long size);
/**
* 异步拉取私聊消息通过websocket异步推送
*/
void pullUnreadMessage();
/**
* 拉取消息只能拉取最近1个月的消息一次拉取100条
*
* @param minId 消息起始id
* @return 聊天消息列表
*/
List<PrivateMessageVO> loadMessage(Long minId);
/**
* 消息已读,将整个会话的消息都置为已读状态
*
* @param friendId 好友id
*/
void readedMessage(Long friendId);
}

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

@ -1,10 +1,10 @@
package com.bx.implatform.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.bx.implatform.dto.ModifyPwdDTO;
import com.bx.implatform.entity.User;
import com.bx.implatform.dto.LoginDTO;
import com.bx.implatform.dto.ModifyPwdDTO;
import com.bx.implatform.dto.RegisterDTO;
import com.bx.implatform.entity.User;
import com.bx.implatform.vo.LoginVO;
import com.bx.implatform.vo.OnlineTerminalVO;
import com.bx.implatform.vo.UserVO;
@ -14,22 +14,73 @@ import java.util.List;
public interface IUserService extends IService<User> {
/**
* 用户登录
*
* @param dto 登录dto
* @return 登录token
*/
LoginVO login(LoginDTO dto);
/**
* 修改用户密码
*
* @param dto 修改密码dto
*/
void modifyPassword(ModifyPwdDTO dto);
/**
* 用refreshToken换取新 token
*
* @param refreshToken 刷新token
* @return 登录token
*/
LoginVO refreshToken(String refreshToken);
/**
* 用户注册
*
* @param dto 注册dto
*/
void register(RegisterDTO dto);
/**
* 根据用户名查询用户
*
* @param username 用户名
* @return 用户信息
*/
User findUserByUserName(String username);
/**
* 更新用户信息好友昵称和群聊昵称等冗余信息也会更新
*
* @param vo 用户信息vo
*/
void update(UserVO vo);
/**
* 根据用户昵id查询用户以及在线状态
*
* @param id 用户id
* @return 用户信息
*/
UserVO findUserById(Long id);
/**
* 根据用户昵称查询用户最多返回20条数据
*
* @param name 用户名或昵称
* @return 用户列表
*/
List<UserVO> findUserByName(String name);
/**
* 获取用户在线的终端类型
*
* @param userIds 用户id多个用,分割
* @return 在线用户终端
*/
List<OnlineTerminalVO> getOnlineTerminals(String userIds);

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

@ -2,6 +2,7 @@ package com.bx.implatform.service;
import com.bx.implatform.config.ICEServer;
import org.springframework.web.bind.annotation.RequestBody;
import java.util.List;

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

@ -1,6 +1,8 @@
package com.bx.implatform.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bx.implatform.contant.RedisKey;
import com.bx.implatform.entity.Friend;
@ -8,14 +10,14 @@ 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.mapper.UserMapper;
import com.bx.implatform.service.IFriendService;
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.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
@ -26,35 +28,23 @@ import java.util.List;
@Slf4j
@CacheConfig(cacheNames= RedisKey.IM_CACHE_FRIEND)
@Service
@AllArgsConstructor
@CacheConfig(cacheNames= RedisKey.IM_CACHE_FRIEND)
public class FriendServiceImpl extends ServiceImpl<FriendMapper, Friend> implements IFriendService {
@Autowired
private IUserService userService;
private final UserMapper userMapper;
/**
* 查询用户的所有好友
*
* @param userId 用户id
* @return
*/
@Override
public List<Friend> findFriendByUserId(Long userId) {
QueryWrapper<Friend> queryWrapper = new QueryWrapper();
queryWrapper.lambda().eq(Friend::getUserId,userId);
List<Friend> friends = this.list(queryWrapper);
return friends;
LambdaQueryWrapper<Friend> queryWrapper = Wrappers.lambdaQuery();
queryWrapper.eq(Friend::getUserId,userId);
return this.list(queryWrapper);
}
/**
* 添加好友互相建立好友关系
*
* @param friendId 好友的用户id
* @return
*/
@Transactional
@Transactional(rollbackFor = Exception.class)
@Override
public void addFriend(Long friendId) {
long userId = SessionContext.getSession().getUserId();
@ -69,13 +59,8 @@ public class FriendServiceImpl extends ServiceImpl<FriendMapper, Friend> impleme
}
/**
* 删除好友双方都会解除好友关系
*
* @param friendId 好友的用户id
* @return
*/
@Transactional
@Transactional(rollbackFor = Exception.class)
@Override
public void delFriend(Long friendId) {
long userId = SessionContext.getSession().getUserId();
@ -87,13 +72,7 @@ public class FriendServiceImpl extends ServiceImpl<FriendMapper, Friend> impleme
}
/**
* 判断用户2是否用户1的好友
*
* @param userId1 用户1的id
* @param userId2 用户2的id
* @return
*/
@Cacheable(key="#userId1+':'+#userId2")
@Override
public Boolean isFriend(Long userId1, Long userId2) {
@ -105,12 +84,7 @@ public class FriendServiceImpl extends ServiceImpl<FriendMapper, Friend> impleme
}
/**
* 更新好友信息主要是头像和昵称
*
* @param vo 好友vo
* @return
*/
@Override
public void update(FriendVO vo) {
long userId = SessionContext.getSession().getUserId();
@ -135,7 +109,6 @@ public class FriendServiceImpl extends ServiceImpl<FriendMapper, Friend> impleme
*
* @param userId 用户id
* @param friendId 好友的用户id
* @return
*/
@CacheEvict(key="#userId+':'+#friendId")
public void bindFriend(Long userId, Long friendId) {
@ -147,7 +120,7 @@ public class FriendServiceImpl extends ServiceImpl<FriendMapper, Friend> impleme
Friend friend = new Friend();
friend.setUserId(userId);
friend.setFriendId(friendId);
User friendInfo = userService.getById(friendId);
User friendInfo = userMapper.selectById(friendId);
friend.setFriendHeadImage(friendInfo.getHeadImage());
friend.setFriendNickName(friendInfo.getNickName());
this.save(friend);
@ -160,7 +133,6 @@ public class FriendServiceImpl extends ServiceImpl<FriendMapper, Friend> impleme
*
* @param userId 用户id
* @param friendId 好友的用户id
* @return
*/
@CacheEvict(key="#userId+':'+#friendId")
public void unbindFriend(Long userId, Long friendId) {
@ -169,18 +141,11 @@ public class FriendServiceImpl extends ServiceImpl<FriendMapper, Friend> impleme
.eq(Friend::getUserId,userId)
.eq(Friend::getFriendId,friendId);
List<Friend> friends = this.list(queryWrapper);
friends.stream().forEach(friend -> {
this.removeById(friend.getId());
});
friends.forEach(friend -> this.removeById(friend.getId()));
}
/**
* 查询指定的某个好友信息
*
* @param friendId 好友的用户id
* @return
*/
@Override
public FriendVO findFriend(Long friendId) {
UserSession session = SessionContext.getSession();

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

@ -18,44 +18,22 @@ import java.util.List;
import java.util.stream.Collectors;
@CacheConfig(cacheNames = RedisKey.IM_CACHE_GROUP_MEMBER_ID)
@Service
@CacheConfig(cacheNames = RedisKey.IM_CACHE_GROUP_MEMBER_ID)
public class GroupMemberServiceImpl extends ServiceImpl<GroupMemberMapper, GroupMember> implements IGroupMemberService {
/**
* 添加群聊成员
*
* @param member 成员
* @return
*/
@CacheEvict(key="#member.getGroupId()")
@Override
public boolean save(GroupMember member) {
return super.save(member);
}
/**
* 批量添加成员
*
* @param groupId 群聊id
* @param members 成员列表
* @return
*/
@CacheEvict(key="#groupId")
@Override
public boolean saveOrUpdateBatch(Long groupId,List<GroupMember> members) {
return super.saveOrUpdateBatch(members);
}
/**
* 根据群聊id和用户id查询群聊成员
*
* @param groupId 群聊id
* @param userId 用户id
* @return
*/
@Override
public GroupMember findByGroupAndUserId(Long groupId, Long userId) {
QueryWrapper<GroupMember> wrapper = new QueryWrapper<>();
@ -64,13 +42,6 @@ public class GroupMemberServiceImpl extends ServiceImpl<GroupMemberMapper, Group
return this.getOne(wrapper);
}
/**
* 根据用户id查询群聊成员
*
* @param userId 用户id
* @return
*/
@Override
public List<GroupMember> findByUserId(Long userId) {
LambdaQueryWrapper<GroupMember> memberWrapper = Wrappers.lambdaQuery();
@ -79,13 +50,6 @@ public class GroupMemberServiceImpl extends ServiceImpl<GroupMemberMapper, Group
return this.list(memberWrapper);
}
/**
* 根据群聊id查询群聊成员包括已退出
*
* @param groupId 群聊id
* @return
*/
@Override
public List<GroupMember> findByGroupId(Long groupId) {
LambdaQueryWrapper<GroupMember> memberWrapper = Wrappers.lambdaQuery();
@ -93,13 +57,6 @@ public class GroupMemberServiceImpl extends ServiceImpl<GroupMemberMapper, Group
return this.list(memberWrapper);
}
/**
* 根据群聊id查询没有退出的群聊成员id
*
* @param groupId 群聊id
* @return
*/
@Cacheable(key="#groupId")
@Override
public List<Long> findUserIdsByGroupId(Long groupId) {
@ -110,13 +67,6 @@ public class GroupMemberServiceImpl extends ServiceImpl<GroupMemberMapper, Group
return members.stream().map(GroupMember::getUserId).collect(Collectors.toList());
}
/**
* 根据群聊id删除移除成员
*
* @param groupId 群聊id
* @return
*/
@CacheEvict(key = "#groupId")
@Override
public void removeByGroupId(Long groupId) {
@ -126,13 +76,6 @@ public class GroupMemberServiceImpl extends ServiceImpl<GroupMemberMapper, Group
this.update(wrapper);
}
/**
*根据群聊id和用户id移除成员
*
* @param groupId 群聊id
* @param userId 用户id
* @return
*/
@CacheEvict(key = "#groupId")
@Override
public void removeByGroupAndUserId(Long groupId, Long userId) {

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

@ -8,11 +8,10 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bx.imclient.IMClient;
import com.bx.imcommon.contant.IMConstant;
import com.bx.implatform.util.DateTimeUtils;
import com.bx.implatform.vo.GroupMessageVO;
import com.bx.imcommon.model.IMGroupMessage;
import com.bx.imcommon.model.IMUserInfo;
import com.bx.implatform.contant.RedisKey;
import com.bx.implatform.dto.GroupMessageDTO;
import com.bx.implatform.entity.Group;
import com.bx.implatform.entity.GroupMember;
import com.bx.implatform.entity.GroupMessage;
@ -27,10 +26,10 @@ import com.bx.implatform.service.IGroupService;
import com.bx.implatform.session.SessionContext;
import com.bx.implatform.session.UserSession;
import com.bx.implatform.util.BeanUtils;
import com.bx.implatform.dto.GroupMessageDTO;
import com.google.common.collect.Lists;
import com.bx.implatform.util.DateTimeUtils;
import com.bx.implatform.vo.GroupMessageVO;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
@ -39,22 +38,13 @@ import java.util.stream.Collectors;
@Slf4j
@Service
@AllArgsConstructor
public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, GroupMessage> implements IGroupMessageService {
@Autowired
private IGroupService groupService;
@Autowired
private IGroupMemberService groupMemberService;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private IMClient imClient;
private final IGroupService groupService;
private final IGroupMemberService groupMemberService;
private final RedisTemplate<String, Object> redisTemplate;
private final IMClient imClient;
/**
* 发送群聊消息(高并发接口查询mysql接口都要进行缓存)
*
* @param dto 群聊消息
* @return 群聊id
*/
@Override
public Long sendMessage(GroupMessageDTO dto) {
UserSession session = SessionContext.getSession();
@ -67,7 +57,7 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
}
// 是否在群聊里面
GroupMember member = groupMemberService.findByGroupAndUserId(dto.getGroupId(), session.getUserId());
if (Objects.isNull(member)||member.getQuit()) {
if (Objects.isNull(member) || member.getQuit()) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "您已不在群聊里面,无法发送消息");
}
// 群聊成员列表
@ -79,8 +69,8 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
msg.setSendId(session.getUserId());
msg.setSendTime(new Date());
msg.setSendNickName(member.getAliasName());
if(CollectionUtil.isNotEmpty(dto.getAtUserIds())){
msg.setAtUserIds(StrUtil.join(",",dto.getAtUserIds()));
if (CollectionUtil.isNotEmpty(dto.getAtUserIds())) {
msg.setAtUserIds(StrUtil.join(",", dto.getAtUserIds()));
}
this.save(msg);
// 群发
@ -95,17 +85,11 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
return msg.getId();
}
/**
* 撤回消息
*
* @param id 消息id
*/
@Override
public void recallMessage(Long id) {
UserSession session = SessionContext.getSession();
GroupMessage msg = this.getById(id);
if (msg == null) {
if (Objects.isNull(msg)) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "消息不存在");
}
if (!msg.getSendId().equals(session.getUserId())) {
@ -116,7 +100,7 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
}
// 判断是否在群里
GroupMember member = groupMemberService.findByGroupAndUserId(msg.getGroupId(), session.getUserId());
if (Objects.isNull(member)||member.getQuit()) {
if (Objects.isNull(member) || member.getQuit()) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "您已不在群聊里面,无法撤回消息");
}
// 修改数据库
@ -149,10 +133,6 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
log.info("撤回群聊消息,发送id:{},群聊id:{},内容:{}", session.getUserId(), msg.getGroupId(), msg.getContent());
}
/**
* 异步拉取群聊消息通过websocket异步推送
*/
@Override
public void pullUnreadMessage() {
UserSession session = SessionContext.getSession();
@ -160,12 +140,10 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
for (GroupMember member : members) {
// 获取群聊已读的最大消息id,只推送未读消息
String key = String.join(":", RedisKey.IM_GROUP_READED_POSITION, member.getGroupId().toString(), session.getUserId().toString());
Integer maxReadedId = (Integer) redisTemplate.opsForValue().get(key);
Integer maxReadedId = (Integer)redisTemplate.opsForValue().get(key);
LambdaQueryWrapper<GroupMessage> wrapper = Wrappers.lambdaQuery();
wrapper.eq(GroupMessage::getGroupId, member.getGroupId())
.gt(GroupMessage::getSendTime, member.getCreatedTime())
.ne(GroupMessage::getSendId, session.getUserId())
.ne(GroupMessage::getStatus, MessageStatus.RECALL.code());
wrapper.eq(GroupMessage::getGroupId, member.getGroupId()).gt(GroupMessage::getSendTime, member.getCreatedTime())
.ne(GroupMessage::getSendId, session.getUserId()).ne(GroupMessage::getStatus, MessageStatus.RECALL.code());
if (maxReadedId != null) {
wrapper.gt(GroupMessage::getId, maxReadedId);
}
@ -191,48 +169,37 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
}
/**
* 拉取消息只能拉取最近1个月的消息一次拉取100条
*
* @param minId 消息起始id
* @return 聊天消息列表
*/
@Override
public List<GroupMessageVO> loadMessage(Long minId) {
UserSession session = SessionContext.getSession();
List<GroupMember> members = groupMemberService.findByUserId(session.getUserId());
List<Long> ids = members.stream().map(GroupMember::getGroupId).collect(Collectors.toList());
if(CollectionUtil.isEmpty(ids)){
return Collections.EMPTY_LIST;
if (CollectionUtil.isEmpty(ids)) {
return new ArrayList<>();
}
// 只能拉取最近1个月的
Date minDate = DateTimeUtils.addMonths(new Date(), -1);
LambdaQueryWrapper<GroupMessage> wrapper = Wrappers.lambdaQuery();
wrapper.gt(GroupMessage::getId, minId)
.gt(GroupMessage::getSendTime, minDate)
.in(GroupMessage::getGroupId, ids)
.ne(GroupMessage::getStatus, MessageStatus.RECALL.code())
.orderByAsc(GroupMessage::getId)
.last("limit 100");
wrapper.gt(GroupMessage::getId, minId).gt(GroupMessage::getSendTime, minDate).in(GroupMessage::getGroupId, ids)
.ne(GroupMessage::getStatus, MessageStatus.RECALL.code()).orderByAsc(GroupMessage::getId).last("limit 100");
List<GroupMessage> messages = this.list(wrapper);
// 转成vo
List<GroupMessageVO> vos = messages.stream().map(m -> {
GroupMessageVO vo = BeanUtils.copyProperties(m, GroupMessageVO.class);
// 被@用户列表
List<String> atIds = Arrays.asList(StrUtil.split(m.getAtUserIds(),","));
vo.setAtUserIds(atIds.stream().map(id->Long.parseLong(id)).collect(Collectors.toList()));
List<String> atIds = Arrays.asList(StrUtil.split(m.getAtUserIds(), ","));
vo.setAtUserIds(atIds.stream().map(Long::parseLong).collect(Collectors.toList()));
return vo;
}).collect(Collectors.toList());
// 消息状态,数据库没有存群聊的消息状态,需要从redis取
List<String> keys = ids.stream()
.map(id -> String.join(":", RedisKey.IM_GROUP_READED_POSITION, id.toString(), session.getUserId().toString()))
.collect(Collectors.toList());
List<String> keys = ids.stream().map(id -> String.join(":", RedisKey.IM_GROUP_READED_POSITION, id.toString(), session.getUserId().toString()))
.collect(Collectors.toList());
List<Object> sendPos = redisTemplate.opsForValue().multiGet(keys);
int idx = 0;
for (Long id : ids) {
Object o = sendPos.get(idx);
Integer sendMaxId = Objects.isNull(o) ? -1 : (Integer) o;
Integer sendMaxId = Objects.isNull(o) ? -1 : (Integer)o;
vos.stream().filter(vo -> vo.getGroupId().equals(id)).forEach(vo -> {
if (vo.getId() <= sendMaxId) {
// 已读
@ -247,22 +214,14 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
return vos;
}
/**
* 消息已读,同步其他终端清空未读数量
*
* @param groupId 群聊
*/
@Override
public void readedMessage(Long groupId) {
UserSession session = SessionContext.getSession();
// 取出最后的消息id
LambdaQueryWrapper<GroupMessage> wrapper = Wrappers.lambdaQuery();
wrapper.eq(GroupMessage::getGroupId, groupId)
.orderByDesc(GroupMessage::getId)
.last("limit 1")
.select(GroupMessage::getId);
wrapper.eq(GroupMessage::getGroupId, groupId).orderByDesc(GroupMessage::getId).last("limit 1").select(GroupMessage::getId);
GroupMessage message = this.getOne(wrapper);
if(Objects.isNull(message)){
if (Objects.isNull(message)) {
return;
}
// 推送消息给自己的其他终端
@ -278,19 +237,11 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
sendMessage.setSendResult(false);
imClient.sendGroupMessage(sendMessage);
// 记录已读消息位置
String key = StrUtil.join(":",RedisKey.IM_GROUP_READED_POSITION,groupId,session.getUserId());
String key = StrUtil.join(":", RedisKey.IM_GROUP_READED_POSITION, groupId, session.getUserId());
redisTemplate.opsForValue().set(key, message.getId());
}
/**
* 拉取历史聊天记录
*
* @param groupId 群聊id
* @param page 页码
* @param size 页码大小
* @return 聊天记录列表
*/
@Override
public List<GroupMessageVO> findHistoryMessage(Long groupId, Long page, Long size) {
page = page > 0 ? page : 1;
@ -304,17 +255,14 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
}
// 查询聊天记录,只查询加入群聊时间之后的消息
QueryWrapper<GroupMessage> wrapper = new QueryWrapper<>();
wrapper.lambda().eq(GroupMessage::getGroupId, groupId)
.gt(GroupMessage::getSendTime, member.getCreatedTime())
.ne(GroupMessage::getStatus, MessageStatus.RECALL.code())
.orderByDesc(GroupMessage::getId)
.last("limit " + stIdx + "," + size);
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<GroupMessageVO> messageInfos = messages.stream().map(m -> BeanUtils.copyProperties(m, GroupMessageVO.class)).collect(Collectors.toList());
List<GroupMessageVO> messageInfos =
messages.stream().map(m -> BeanUtils.copyProperties(m, GroupMessageVO.class)).collect(Collectors.toList());
log.info("拉取群聊记录,用户id:{},群聊id:{},数量:{}", userId, groupId, messageInfos.size());
return messageInfos;
}
}

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

@ -23,9 +23,9 @@ 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.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
@ -38,192 +38,141 @@ import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@Slf4j
@CacheConfig(cacheNames = RedisKey.IM_CACHE_GROUP)
@Service
@AllArgsConstructor
public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements IGroupService {
private final IUserService userService;
private final IGroupMemberService groupMemberService;
private final IFriendService friendsService;
private final IMClient imClient;
@Autowired
private IUserService userService;
@Autowired
private IGroupMemberService groupMemberService;
@Autowired
private IFriendService friendsService;
@Autowired
private IMClient imClient;
/**
* 创建新群聊
*
* @param vo 群聊信息
* @return 群聊信息
**/
@Override
public GroupVO createGroup(GroupVO vo) {
UserSession session = SessionContext.getSession();
User user = userService.getById(session.getUserId());
// 保存群组数据
Group group = BeanUtils.copyProperties(vo,Group.class);
Group group = BeanUtils.copyProperties(vo, Group.class);
group.setOwnerId(user.getId());
this.save(group);
// 把群主加入群
GroupMember groupMember = new GroupMember();
groupMember.setGroupId(group.getId());
groupMember.setUserId(user.getId());
groupMember.setAliasName(StringUtils.isEmpty(vo.getAliasName())?session.getNickName():vo.getAliasName());
groupMember.setRemark(StringUtils.isEmpty(vo.getRemark())?group.getName():vo.getRemark());
groupMember.setHeadImage(user.getHeadImageThumb());
groupMember.setAliasName(StringUtils.isEmpty(vo.getAliasName()) ? session.getNickName() : vo.getAliasName());
groupMember.setRemark(StringUtils.isEmpty(vo.getRemark()) ? group.getName() : vo.getRemark());
groupMemberService.save(groupMember);
vo.setId(group.getId());
vo.setAliasName(groupMember.getAliasName());
vo.setRemark(groupMember.getRemark());
log.info("创建群聊,群聊id:{},群聊名称:{}",group.getId(),group.getName());
log.info("创建群聊,群聊id:{},群聊名称:{}", group.getId(), group.getName());
return vo;
}
/**
* 修改群聊信息
*
* @param vo 群聊信息
* @return 群聊信息
**/
@CacheEvict(value = "#vo.getId()")
@Transactional
@Transactional(rollbackFor = Exception.class)
@Override
public GroupVO modifyGroup(GroupVO vo) {
UserSession session = SessionContext.getSession();
// 校验是不是群主,只有群主能改信息
Group group = this.getById(vo.getId());
// 群主有权修改群基本信息
if(group.getOwnerId().equals(session.getUserId()) ){
group = BeanUtils.copyProperties(vo,Group.class);
if (group.getOwnerId().equals(session.getUserId())) {
group = BeanUtils.copyProperties(vo, Group.class);
this.updateById(group);
}
// 更新成员信息
GroupMember member = groupMemberService.findByGroupAndUserId(vo.getId(),session.getUserId());
if(member == null){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"您不是群聊的成员");
GroupMember member = groupMemberService.findByGroupAndUserId(vo.getId(), session.getUserId());
if (member == null) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "您不是群聊的成员");
}
member.setAliasName(StringUtils.isEmpty(vo.getAliasName())?session.getNickName():vo.getAliasName());
member.setRemark(StringUtils.isEmpty(vo.getRemark())?group.getName():vo.getRemark());
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());
log.info("修改群聊,群聊id:{},群聊名称:{}", group.getId(), group.getName());
return vo;
}
/**
* 删除群聊
*
* @param groupId 群聊id
**/
@Transactional
@Transactional(rollbackFor = Exception.class)
@CacheEvict(value = "#groupId")
@Override
public void deleteGroup(Long groupId) {
UserSession session = SessionContext.getSession();
Group group = this.getById(groupId);
if(!group.getOwnerId().equals(session.getUserId())){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"只有群主才有权限解除群聊");
if (!group.getOwnerId().equals(session.getUserId())) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "只有群主才有权限解除群聊");
}
// 逻辑删除群数据
group.setDeleted(true);
this.updateById(group);
// 删除成员数据
groupMemberService.removeByGroupId(groupId);
log.info("删除群聊,群聊id:{},群聊名称:{}",group.getId(),group.getName());
log.info("删除群聊,群聊id:{},群聊名称:{}", group.getId(), group.getName());
}
/**
* 退出群聊
*
* @param groupId 群聊id
*/
@Override
public void quitGroup(Long groupId) {
Long userId = SessionContext.getSession().getUserId();
Group group = this.getById(groupId);
if(group.getOwnerId().equals(userId)){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"您是群主,不可退出群聊");
if (group.getOwnerId().equals(userId)) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "您是群主,不可退出群聊");
}
// 删除群聊成员
groupMemberService.removeByGroupAndUserId(groupId,userId);
log.info("退出群聊,群聊id:{},群聊名称:{},用户id:{}",group.getId(),group.getName(),userId);
groupMemberService.removeByGroupAndUserId(groupId, userId);
log.info("退出群聊,群聊id:{},群聊名称:{},用户id:{}", group.getId(), group.getName(), userId);
}
/**
* 将用户踢出群聊
*
* @param groupId 群聊id
* @param userId 用户id
*/
@Override
public void kickGroup(Long groupId, Long userId) {
UserSession session = SessionContext.getSession();
Group group = this.getById(groupId);
if(!group.getOwnerId().equals(session.getUserId()) ){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"您不是群主,没有权限踢人");
if (!group.getOwnerId().equals(session.getUserId())) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "您不是群主,没有权限踢人");
}
if(userId.equals(session.getUserId())){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"亲,不能自己踢自己哟");
if (userId.equals(session.getUserId())) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "亲,不能自己踢自己哟");
}
// 删除群聊成员
groupMemberService.removeByGroupAndUserId(groupId,userId);
log.info("踢出群聊,群聊id:{},群聊名称:{},用户id:{}",group.getId(),group.getName(),userId);
groupMemberService.removeByGroupAndUserId(groupId, userId);
log.info("踢出群聊,群聊id:{},群聊名称:{},用户id:{}", group.getId(), group.getName(), userId);
}
@Override
public GroupVO findById(Long groupId) {
UserSession session = SessionContext.getSession();
Group group = this.getById(groupId);
GroupMember member = groupMemberService.findByGroupAndUserId(groupId,session.getUserId());
if(member == null){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"您未加入群聊");
GroupMember member = groupMemberService.findByGroupAndUserId(groupId, session.getUserId());
if (member == null) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "您未加入群聊");
}
GroupVO vo = BeanUtils.copyProperties(group,GroupVO.class);
GroupVO vo = BeanUtils.copyProperties(group, GroupVO.class);
vo.setAliasName(member.getAliasName());
vo.setRemark(member.getRemark());
return vo;
return vo;
}
/**
* 根据id查找群聊并进行缓存
*
* @param groupId 群聊id
* @return 群聊实体
*/
@Cacheable(value = "#groupId")
@Override
public Group getById(Long groupId){
public Group getById(Long groupId) {
Group group = super.getById(groupId);
if(group == null){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"群组不存在");
if (group == null) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "群组不存在");
}
if(group.getDeleted()){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"群组'"+group.getName()+"'已解散");
if (group.getDeleted()) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "群组'" + group.getName() + "'已解散");
}
return group;
}
/**
* 查询当前用户的所有群聊
*
* @return 群聊信息列表
**/
@Override
public List<GroupVO> findGroups() {
UserSession session = SessionContext.getSession();
// 查询当前用户的群id列表
List<GroupMember> groupMembers = groupMemberService.findByUserId(session.getUserId());
if(groupMembers.isEmpty()){
if (groupMembers.isEmpty()) {
return new LinkedList<>();
}
// 拉取群列表
@ -241,69 +190,55 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
}).collect(Collectors.toList());
}
/**
* 邀请好友进群
*
* @Param GroupInviteVO 群id好友id列表
**/
@Override
public void invite(GroupInviteVO vo) {
UserSession session = SessionContext.getSession();
Group group = this.getById(vo.getGroupId());
if(group == null){
if (group == null) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "群聊不存在");
}
// 群聊人数校验
List<GroupMember> members = groupMemberService.findByGroupId(vo.getGroupId());
long size = members.stream().filter(m->!m.getQuit()).count();
if(vo.getFriendIds().size() + size > Constant.MAX_GROUP_MEMBER){
throw new GlobalException(ResultCode.PROGRAM_ERROR, "群聊人数不能大于"+Constant.MAX_GROUP_MEMBER+"人");
long size = members.stream().filter(m -> !m.getQuit()).count();
if (vo.getFriendIds().size() + size > Constant.MAX_GROUP_MEMBER) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "群聊人数不能大于" + Constant.MAX_GROUP_MEMBER + "人");
}
// 找出好友信息
List<Friend> friends = friendsService.findFriendByUserId(session.getUserId());
List<Friend> friendsList = vo.getFriendIds().stream().map(id ->
friends.stream().filter(f -> f.getFriendId().equals(id)).findFirst().get()).collect(Collectors.toList());
List<Friend> friendsList = vo.getFriendIds().stream().map(id -> friends.stream().filter(f -> f.getFriendId().equals(id)).findFirst().get())
.collect(Collectors.toList());
if (friendsList.size() != vo.getFriendIds().size()) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "部分用户不是您的好友,邀请失败");
}
// 批量保存成员数据
List<GroupMember> groupMembers = friendsList.stream()
.map(f -> {
Optional<GroupMember> optional = members.stream().filter(m->m.getUserId().equals(f.getFriendId())).findFirst();
GroupMember groupMember = optional.orElseGet(GroupMember::new);
groupMember.setGroupId(vo.getGroupId());
groupMember.setUserId(f.getFriendId());
groupMember.setAliasName(f.getFriendNickName());
groupMember.setRemark(group.getName());
groupMember.setHeadImage(f.getFriendHeadImage());
groupMember.setCreatedTime(new Date());
groupMember.setQuit(false);
return groupMember;
}).collect(Collectors.toList());
if(!groupMembers.isEmpty()) {
groupMemberService.saveOrUpdateBatch(group.getId(),groupMembers);
List<GroupMember> groupMembers = friendsList.stream().map(f -> {
Optional<GroupMember> optional = members.stream().filter(m -> m.getUserId().equals(f.getFriendId())).findFirst();
GroupMember groupMember = optional.orElseGet(GroupMember::new);
groupMember.setGroupId(vo.getGroupId());
groupMember.setUserId(f.getFriendId());
groupMember.setAliasName(f.getFriendNickName());
groupMember.setRemark(group.getName());
groupMember.setHeadImage(f.getFriendHeadImage());
groupMember.setCreatedTime(new Date());
groupMember.setQuit(false);
return groupMember;
}).collect(Collectors.toList());
if (!groupMembers.isEmpty()) {
groupMemberService.saveOrUpdateBatch(group.getId(), groupMembers);
}
log.info("邀请进入群聊,群聊id:{},群聊名称:{},被邀请用户id:{}",group.getId(),group.getName(),vo.getFriendIds());
log.info("邀请进入群聊,群聊id:{},群聊名称:{},被邀请用户id:{}", group.getId(), group.getName(), vo.getFriendIds());
}
/**
* 查询群成员
*
* @Param groupId 群聊id
* @return List<GroupMemberVO>
**/
@Override
public List<GroupMemberVO> findGroupMembers(Long groupId) {
List<GroupMember> members = groupMemberService.findByGroupId(groupId);
List<Long> userIds = members.stream().map(GroupMember::getUserId).collect(Collectors.toList());
List<Long> userIds = members.stream().map(GroupMember::getUserId).collect(Collectors.toList());
List<Long> onlineUserIds = imClient.getOnlineUser(userIds);
return members.stream().map(m->{
GroupMemberVO vo = BeanUtils.copyProperties(m,GroupMemberVO.class);
return members.stream().map(m -> {
GroupMemberVO vo = BeanUtils.copyProperties(m, GroupMemberVO.class);
vo.setOnline(onlineUserIds.contains(m.getUserId()));
return vo;
}).sorted((m1,m2)->{
return m2.getOnline().compareTo(m1.getOnline());
}).collect(Collectors.toList());
}).sorted((m1, m2) -> m2.getOnline().compareTo(m1.getOnline())).collect(Collectors.toList());
}
}

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

@ -9,9 +9,8 @@ import com.bx.imclient.IMClient;
import com.bx.imcommon.contant.IMConstant;
import com.bx.imcommon.model.IMPrivateMessage;
import com.bx.imcommon.model.IMUserInfo;
import com.bx.implatform.dto.PrivateMessageDTO;
import com.bx.implatform.entity.Friend;
import com.bx.implatform.util.DateTimeUtils;
import com.bx.implatform.vo.PrivateMessageVO;
import com.bx.implatform.entity.PrivateMessage;
import com.bx.implatform.enums.MessageStatus;
import com.bx.implatform.enums.MessageType;
@ -23,12 +22,14 @@ import com.bx.implatform.service.IPrivateMessageService;
import com.bx.implatform.session.SessionContext;
import com.bx.implatform.session.UserSession;
import com.bx.implatform.util.BeanUtils;
import com.bx.implatform.dto.PrivateMessageDTO;
import com.bx.implatform.util.DateTimeUtils;
import com.bx.implatform.vo.PrivateMessageVO;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
@ -36,20 +37,12 @@ import java.util.stream.Collectors;
@Slf4j
@Service
@AllArgsConstructor
public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper, PrivateMessage> implements IPrivateMessageService {
@Autowired
private IFriendService friendService;
private final IFriendService friendService;
private final IMClient imClient;
@Autowired
private IMClient imClient;
/**
* 发送私聊消息(高并发接口查询mysql接口都要进行缓存)
*
* @param dto 私聊消息
* @return 消息id
*/
@Override
public Long sendMessage(PrivateMessageDTO dto) {
UserSession session = SessionContext.getSession();
@ -75,11 +68,6 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
return msg.getId();
}
/**
* 撤回消息
*
* @param id 消息id
*/
@Override
public void recallMessage(Long id) {
UserSession session = SessionContext.getSession();
@ -119,14 +107,7 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
}
/**
* 拉取历史聊天记录
*
* @param friendId 好友id
* @param page 页码
* @param size 页码大小
* @return 聊天记录列表
*/
@Override
public List<PrivateMessageVO> findHistoryMessage(Long friendId, Long page, Long size) {
page = page > 0 ? page : 1;
@ -149,9 +130,7 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
return messageInfos;
}
/**
* 异步拉取私聊消息通过websocket异步推送
*/
@Override
public void pullUnreadMessage() {
UserSession session = SessionContext.getSession();
@ -188,18 +167,13 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
}
/**
* 拉取消息只能拉取最近1个月的消息一次拉取100条
*
* @param minId 消息起始id
* @return 聊天消息列表
*/
@Override
public List<PrivateMessageVO> loadMessage(Long minId) {
UserSession session = SessionContext.getSession();
List<Friend> friends = friendService.findFriendByUserId(session.getUserId());
if (friends.isEmpty()) {
return Collections.EMPTY_LIST;
return new ArrayList<>();
}
List<Long> friendIds = friends.stream().map(Friend::getFriendId).collect(Collectors.toList());
// 获取当前用户的消息
@ -234,12 +208,8 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
}
/**
* 消息已读,将整个会话的消息都置为已读状态
*
* @param friendId 好友id
*/
@Transactional
@Transactional(rollbackFor = Exception.class)
@Override
public void readedMessage(Long friendId) {
UserSession session = SessionContext.getSession();

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

@ -27,64 +27,44 @@ import com.bx.implatform.util.BeanUtils;
import com.bx.implatform.vo.LoginVO;
import com.bx.implatform.vo.OnlineTerminalVO;
import com.bx.implatform.vo.UserVO;
import lombok.AllArgsConstructor;
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;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.stream.Collectors;
@Slf4j
@Service
@AllArgsConstructor
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Autowired
RedisTemplate<String,Object> redisTemplate;
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private IGroupMemberService groupMemberService;
@Autowired
private IFriendService friendService;
@Autowired
private JwtProperties jwtProperties;
@Autowired
private IMClient imClient;
/**
* 用户登录
*
* @param dto 登录dto
* @return 登录token
*/
private final RedisTemplate<String, Object> redisTemplate;
private final PasswordEncoder passwordEncoder;
private final IGroupMemberService groupMemberService;
private final IFriendService friendService;
private final JwtProperties jwtProperties;
private final IMClient imClient;
@Override
public LoginVO login(LoginDTO dto) {
User user = this.findUserByUserName(dto.getUserName());
if(null == user){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"用户不存在");
if (null == user) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "用户不存在");
}
if(!passwordEncoder.matches(dto.getPassword(),user.getPassword())){
throw new GlobalException(ResultCode.PASSWOR_ERROR);
if (!passwordEncoder.matches(dto.getPassword(), user.getPassword())) {
throw new GlobalException(ResultCode.PASSWOR_ERROR);
}
// 生成token
UserSession session = BeanUtils.copyProperties(user,UserSession.class);
UserSession session = BeanUtils.copyProperties(user, UserSession.class);
session.setUserId(user.getId());
session.setTerminal(dto.getTerminal());
String strJson = JSON.toJSONString(session);
String accessToken = JwtUtil.sign(user.getId(),strJson,jwtProperties.getAccessTokenExpireIn(),jwtProperties.getAccessTokenSecret());
String refreshToken = JwtUtil.sign(user.getId(),strJson,jwtProperties.getRefreshTokenExpireIn(),jwtProperties.getRefreshTokenSecret());
String accessToken = JwtUtil.sign(user.getId(), strJson, jwtProperties.getAccessTokenExpireIn(), jwtProperties.getAccessTokenSecret());
String refreshToken = JwtUtil.sign(user.getId(), strJson, jwtProperties.getRefreshTokenExpireIn(), jwtProperties.getRefreshTokenSecret());
LoginVO vo = new LoginVO();
vo.setAccessToken(accessToken);
vo.setAccessTokenExpiresIn(jwtProperties.getAccessTokenExpireIn());
@ -93,26 +73,17 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IU
return vo;
}
/**
* 用refreshToken换取新 token
*
* @param refreshToken 刷新token
* @return 登录token
*/
@Override
public LoginVO refreshToken(String refreshToken) {
//验证 token
if(!JwtUtil.checkSign(refreshToken, jwtProperties.getRefreshTokenSecret())){
if (!JwtUtil.checkSign(refreshToken, jwtProperties.getRefreshTokenSecret())) {
throw new GlobalException("refreshToken无效或已过期");
}
String strJson = JwtUtil.getInfo(refreshToken);
Long userId = JwtUtil.getUserId(refreshToken);
String accessToken = JwtUtil.sign(userId,strJson,jwtProperties.getAccessTokenExpireIn(),jwtProperties.getAccessTokenSecret());
String newRefreshToken = JwtUtil.sign(userId,strJson,jwtProperties.getRefreshTokenExpireIn(),jwtProperties.getRefreshTokenSecret());
LoginVO vo =new LoginVO();
String accessToken = JwtUtil.sign(userId, strJson, jwtProperties.getAccessTokenExpireIn(), jwtProperties.getAccessTokenSecret());
String newRefreshToken = JwtUtil.sign(userId, strJson, jwtProperties.getRefreshTokenExpireIn(), jwtProperties.getRefreshTokenSecret());
LoginVO vo = new LoginVO();
vo.setAccessToken(accessToken);
vo.setAccessTokenExpiresIn(jwtProperties.getAccessTokenExpireIn());
vo.setRefreshToken(newRefreshToken);
@ -120,81 +91,63 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IU
return vo;
}
/**
* 用户注册
*
* @param dto 注册dto
*/
@Override
public void register(RegisterDTO dto) {
User user = this.findUserByUserName(dto.getUserName());
if(null != user){
throw new GlobalException(ResultCode.USERNAME_ALREADY_REGISTER);
if (null != user) {
throw new GlobalException(ResultCode.USERNAME_ALREADY_REGISTER);
}
user = BeanUtils.copyProperties(dto,User.class);
user = BeanUtils.copyProperties(dto, User.class);
user.setPassword(passwordEncoder.encode(user.getPassword()));
this.save(user);
log.info("注册用户,用户id:{},用户名:{},昵称:{}",user.getId(),dto.getUserName(),dto.getNickName());
log.info("注册用户,用户id:{},用户名:{},昵称:{}", user.getId(), dto.getUserName(), dto.getNickName());
}
@Override
public void modifyPassword(ModifyPwdDTO dto) {
UserSession session = SessionContext.getSession();
User user = this.getById(session.getUserId());
if(!passwordEncoder.matches(dto.getOldPassword(),user.getPassword())){
throw new GlobalException("旧密码不正确");
if (!passwordEncoder.matches(dto.getOldPassword(), user.getPassword())) {
throw new GlobalException("旧密码不正确");
}
user.setPassword(passwordEncoder.encode(dto.getNewPassword()));
this.updateById(user);
log.info("用户修改密码,用户id:{},用户名:{},昵称:{}",user.getId(),user.getUserName(),user.getNickName());
log.info("用户修改密码,用户id:{},用户名:{},昵称:{}", user.getId(), user.getUserName(), user.getNickName());
}
/**
* 根据用户名查询用户
*
* @param username 用户名
* @return 用户信息
*/
@Override
public User findUserByUserName(String username) {
LambdaQueryWrapper<User> queryWrapper = Wrappers.lambdaQuery();
queryWrapper.eq(User::getUserName,username);
LambdaQueryWrapper<User> queryWrapper = Wrappers.lambdaQuery();
queryWrapper.eq(User::getUserName, username);
return this.getOne(queryWrapper);
}
/**
* 更新用户信息好友昵称和群聊昵称等冗余信息也会更新
*
* @param vo 用户信息vo
*/
@Transactional
@Transactional(rollbackFor = Exception.class)
@Override
public void update(UserVO vo) {
UserSession session = SessionContext.getSession();
if(!session.getUserId().equals(vo.getId()) ){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"不允许修改其他用户的信息!");
if (!session.getUserId().equals(vo.getId())) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "不允许修改其他用户的信息!");
}
User user = this.getById(vo.getId());
if(null == user){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"用户不存在");
if (Objects.isNull(user)) {
throw new GlobalException(ResultCode.PROGRAM_ERROR, "用户不存在");
}
// 更新好友昵称和头像
if(!user.getNickName().equals(vo.getNickName()) || !user.getHeadImageThumb().equals(vo.getHeadImageThumb())){
if (!user.getNickName().equals(vo.getNickName()) || !user.getHeadImageThumb().equals(vo.getHeadImageThumb())) {
QueryWrapper<Friend> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().eq(Friend::getFriendId,session.getUserId());
queryWrapper.lambda().eq(Friend::getFriendId, session.getUserId());
List<Friend> friends = friendService.list(queryWrapper);
for(Friend friend: friends){
for (Friend friend : friends) {
friend.setFriendNickName(vo.getNickName());
friend.setFriendHeadImage(vo.getHeadImageThumb());
}
friendService.updateBatchById(friends);
}
// 更新群聊中的头像
if(!user.getHeadImageThumb().equals(vo.getHeadImageThumb())){
if (!user.getHeadImageThumb().equals(vo.getHeadImageThumb())) {
List<GroupMember> members = groupMemberService.findByUserId(session.getUserId());
for(GroupMember member:members){
for (GroupMember member : members) {
member.setHeadImage(vo.getHeadImageThumb());
}
groupMemberService.updateBatchById(members);
@ -209,62 +162,38 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IU
log.info("用户信息更新,用户:{}}", user);
}
/**
* 根据用户昵id查询用户以及在线状态
*
* @param id 用户id
* @return 用户信息
*/
@Override
public UserVO findUserById(Long id) {
User user = this.getById(id);
UserVO vo = BeanUtils.copyProperties(user,UserVO.class);
UserVO vo = BeanUtils.copyProperties(user, UserVO.class);
vo.setOnline(imClient.isOnline(id));
return vo;
}
/**
* 根据用户昵称查询用户最多返回20条数据
*
* @param name 用户名或昵称
* @return 用户列表
*/
@Override
public List<UserVO> findUserByName(String name) {
LambdaQueryWrapper<User> queryWrapper = Wrappers.lambdaQuery();
queryWrapper.like(User::getUserName,name)
.or()
.like(User::getNickName,name)
.last("limit 20");
queryWrapper.like(User::getUserName, name).or().like(User::getNickName, name).last("limit 20");
List<User> users = this.list(queryWrapper);
List<Long> userIds = users.stream().map(User::getId).collect(Collectors.toList());
List<Long> onlineUserIds = imClient.getOnlineUser(userIds);
return users.stream().map(u-> {
UserVO vo = BeanUtils.copyProperties(u,UserVO.class);
return users.stream().map(u -> {
UserVO vo = BeanUtils.copyProperties(u, UserVO.class);
vo.setOnline(onlineUserIds.contains(u.getId()));
return vo;
}).collect(Collectors.toList());
}
/**
* 获取用户在线的终端类型
*
* @param userIds 用户id多个用,分割
* @return 在线用户终端
*/
@Override
public List<OnlineTerminalVO> getOnlineTerminals(String userIds) {
List<Long> userIdList = Arrays.stream(userIds.split(","))
.map(Long::parseLong).collect(Collectors.toList());
List<Long> userIdList = Arrays.stream(userIds.split(",")).map(Long::parseLong).collect(Collectors.toList());
// 查询在线的终端
Map<Long,List<IMTerminalType>> terminalMap = imClient.getOnlineTerminal(userIdList);
Map<Long, List<IMTerminalType>> terminalMap = imClient.getOnlineTerminal(userIdList);
// 组装vo
List<OnlineTerminalVO> vos = new LinkedList<>();
terminalMap.forEach((userId,types)->{
terminalMap.forEach((userId, types) -> {
List<Integer> terminals = types.stream().map(IMTerminalType::code).collect(Collectors.toList());
vos.add(new OnlineTerminalVO(userId,terminals));
vos.add(new OnlineTerminalVO(userId, terminals));
});
return vos;
}

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

@ -3,7 +3,6 @@ package com.bx.implatform.service.impl;
import com.bx.imclient.IMClient;
import com.bx.imcommon.model.IMPrivateMessage;
import com.bx.imcommon.model.IMUserInfo;
import com.bx.implatform.vo.PrivateMessageVO;
import com.bx.implatform.config.ICEServer;
import com.bx.implatform.config.ICEServerConfig;
import com.bx.implatform.contant.RedisKey;
@ -13,8 +12,9 @@ import com.bx.implatform.service.IWebrtcService;
import com.bx.implatform.session.SessionContext;
import com.bx.implatform.session.UserSession;
import com.bx.implatform.session.WebrtcSession;
import com.bx.implatform.vo.PrivateMessageVO;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestBody;
@ -25,14 +25,12 @@ import java.util.concurrent.TimeUnit;
@Slf4j
@Service
@AllArgsConstructor
public class WebrtcServiceImpl implements IWebrtcService {
@Autowired
private IMClient imClient;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private ICEServerConfig iceServerConfig;
private final IMClient imClient;
private final RedisTemplate<String, Object> redisTemplate;
private final ICEServerConfig iceServerConfig;
@Override
public void call(Long uid, String offer) {

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

@ -9,9 +9,9 @@ import com.bx.implatform.util.FileUtil;
import com.bx.implatform.util.ImageUtil;
import com.bx.implatform.util.MinioUtil;
import com.bx.implatform.vo.UploadImageVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
@ -22,28 +22,25 @@ import java.io.IOException;
/**
* todo 通过校验文件MD5实现重复文件秒传
* 文件上传服务
* @Author Blue
* @Date 2022/10/28
* @author Blue
* @date 2022/10/28
*
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class FileService {
@Autowired
private MinioUtil minioUtil;
private final MinioUtil minioUtil;
@Value("${minio.public}")
private String minIOServer;
private String minIoServer;
@Value("${minio.bucketName}")
private String bucketName;
@Value("${minio.imagePath}")
private String imagePath;
@Value("${minio.filePath}")
private String filePath;
@PostConstruct
public void init(){
if(!minioUtil.bucketExists(bucketName)){
@ -108,7 +105,7 @@ public class FileService {
public String generUrl(FileType fileTypeEnum, String fileName){
String url = minIOServer+"/"+bucketName;
String url = minIoServer +"/"+bucketName;
switch (fileTypeEnum){
case FILE:
url += "/file/";
@ -119,6 +116,8 @@ public class FileService {
case VIDEO:
url += "/video/";
break;
default:
break;
}
url += fileName;
return url;

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

@ -1,7 +1,5 @@
package com.bx.implatform.session;
import com.bx.imcommon.enums.IMTerminalType;
import io.swagger.models.auth.In;
import lombok.Data;
/*

133
im-platform/src/main/java/com/bx/implatform/util/BeanUtils.java

@ -1,13 +1,6 @@
package com.bx.implatform.util;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.util.ReflectionUtils;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class BeanUtils {
@ -17,63 +10,19 @@ public class BeanUtils {
}
public static <T, U> List<U> copyProperties(List<T> sourceList, Class<U> clazz) {
if(sourceList == null || sourceList.size() <= 0) {
return new ArrayList<>();
}
List<U> result = new ArrayList<>();
for (T source : sourceList) {
result.add(copyProperties(source, clazz));
}
return result;
}
/**
* 如果source , 为空返回空对象
* @param sourceList
* @param clazz
* @return
*/
public static <T, U> List<U> copyPropertiesList(List<T> sourceList, Class<U> clazz) {
if(sourceList == null || sourceList.size() <= 0) {
return new ArrayList<U>();
}
List<U> result = new ArrayList<>();
U target = null;
for (T source : sourceList) {
try {
target = clazz.newInstance();
copyProperties(source, target);
}catch(Exception e) {
handleReflectionException(e);
return new ArrayList<>();
}
result.add(target);
}
return result;
}
/**
* source空为null
* @param orig
* @param destClass
* @param <T>
* @return
* 属性拷贝
* @param orig 源对象
* @param destClass 目标
* @return T
*/
public static <T> T copyProperties(Object orig, Class<T> destClass) {
try {
Object target = destClass.newInstance();
if(orig == null) {
return null;
}
copyProperties(orig, (Object)target);
copyProperties(orig, target);
return (T) target;
}catch(Exception e) {
handleReflectionException(e);
@ -81,78 +30,6 @@ public class BeanUtils {
}
}
/**
* source 为null 返回 空对象
* @param orig
* @param destClass
* @param <T>
* @return
*/
public static <T> T copyProperty(Object orig, Class<T> destClass) {
try {
Object target = destClass.newInstance();
if(orig == null) {
return (T)target;
}
copyProperties(orig, (Object)target);
return (T) target;
}catch(Exception e) {
handleReflectionException(e);
Object o1 = new Object();
try {
o1 = destClass.newInstance();
}catch(Exception ex) {
handleReflectionException(e);
}
return (T)o1;
}
}
public static <T, U> List<U> copyProperties(List<T> sourceList, Class<U> clazz, String... ignoreProperties) {
if(sourceList == null || sourceList.size() <= 0) {
return new ArrayList<U>();
}
List<U> result = new ArrayList<>();
for (T source : sourceList) {
result.add(copyProperties(source, clazz, ignoreProperties));
}
return result;
}
public static <T> T copyProperties(Object orig, Class<T> destClass, String... ignoreProperties) {
if(orig == null) {
return null;
}
try {
Object target = destClass.newInstance();
org.springframework.beans.BeanUtils.copyProperties(orig, (Object)target, ignoreProperties);
return (T)target;
}catch(Exception e) {
return null;
}
}
public static String[] getNullPropertyNames(Object source) {
final BeanWrapper beanWrapper = new BeanWrapperImpl(source);
java.beans.PropertyDescriptor[] propDesc = beanWrapper.getPropertyDescriptors();
Set<String> emptynames = new HashSet<String>();
for(java.beans.PropertyDescriptor pd : propDesc) {
Object srcValue = beanWrapper.getPropertyValue(pd.getName());
if (srcValue == null) {emptynames.add(pd.getName());}
}
String[] result = new String[emptynames.size()];
return emptynames.toArray(result);
}
public static void copyProperties(Object orig, Object dest) {
try {

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

@ -1,21 +1,10 @@
package com.bx.implatform.util;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import java.sql.Timestamp;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.Date;
/**
* 日期处理工具类
@ -25,7 +14,6 @@ import java.util.*;
public class DateTimeUtils extends DateUtils {
public static final String FULL_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
public static final String PART_DATE_FORMAT = "yyyy-MM-dd";
public static final String PARTDATEFORMAT = "yyyyMMdd";
@ -34,11 +22,11 @@ public class DateTimeUtils extends DateUtils {
*
* @param date 日期
* @param xFormat 格式
* @return
* @return 日期
*/
public static String getFormatDate(Date date, String xFormat) {
date = date == null ? new Date() : date;
xFormat = StringUtils.isNotEmpty(xFormat) == true ? xFormat : FULL_DATE_FORMAT;
xFormat = StringUtils.isNotEmpty(xFormat) ? xFormat : FULL_DATE_FORMAT;
SimpleDateFormat sdf = new SimpleDateFormat(xFormat);
return sdf.format(date);
}

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

@ -9,8 +9,7 @@ public class FileUtil {
* @return boolean
*/
public static String getFileExtension(String fileName) {
String extension = fileName.substring(fileName.lastIndexOf(".") + 1);
return extension;
return fileName.substring(fileName.lastIndexOf(".") + 1);
}
/**

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

@ -2,9 +2,9 @@ package com.bx.implatform.util;
import io.minio.*;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
@ -14,11 +14,10 @@ import java.util.Date;
@Slf4j
@Component
@AllArgsConstructor
public class MinioUtil {
@Autowired
private MinioClient minioClient;
private final MinioClient minioClient;
/**
* 查看存储bucket是否存在
@ -35,25 +34,21 @@ public class MinioUtil {
/**
* 创建存储bucket
* @return Boolean
*/
public Boolean makeBucket(String bucketName) {
public void makeBucket(String bucketName) {
try {
minioClient.makeBucket(MakeBucketArgs.builder()
.bucket(bucketName)
.build());
} catch (Exception e) {
log.error("创建bucket失败,",e);
return false;
}
return true;
}
/**
* 设置bucket权限为public
* @return Boolean
* 设置bucket权限为public
*/
public Boolean setBucketPublic(String bucketName) {
public void setBucketPublic(String bucketName) {
try {
// 设置公开
String sb = "{\"Version\":\"2012-10-17\"," +
@ -70,34 +65,13 @@ public class MinioUtil {
.build());
} catch (Exception e) {
log.error("创建bucket失败,",e);
return false;
}
return true;
}
/**
* 删除存储bucket
* @return Boolean
*/
public Boolean removeBucket(String bucketName) {
try {
minioClient.removeBucket(RemoveBucketArgs.builder()
.bucket(bucketName)
.build());
} catch (Exception e) {
log.error("删除bucket失败,",e);
return false;
}
return true;
}
/**
* 文件上传
* @bucketName bucket名称
* @path 路径
* @param bucketName bucket名称
* @param path 路径
* @param file 文件
* @return Boolean
*/
@ -129,8 +103,8 @@ public class MinioUtil {
* @param path 路径
* @param name 文件名
* @param fileByte 文件内容
* @param contentType
* @return Boolean
* @param contentType contentType
* @return objectName
*/
public String upload(String bucketName,String path,String name,byte[] fileByte,String contentType) {
@ -142,9 +116,8 @@ public class MinioUtil {
.stream(stream, fileByte.length, -1).contentType(contentType).build();
//文件名称相同会覆盖
minioClient.putObject(objectArgs);
} catch (Exception e) {
log.error("上传图片失败,",e);
log.error("上传文件失败,",e);
return null;
}
return objectName;
@ -154,16 +127,15 @@ public class MinioUtil {
/**
* 删除
* @param bucketName bucket名称
* @path path
* @param fileName
* @return
* @throws Exception
* @param path 路径
* @param fileName 文件名
* @return true/false
*/
public boolean remove(String bucketName,String path,String fileName){
try {
minioClient.removeObject( RemoveObjectArgs.builder().bucket(bucketName).object(path+fileName).build());
}catch (Exception e){
log.error("删除图片失败,",e);
log.error("删除文件失败,",e);
return false;
}
return true;

4
im-platform/src/main/java/com/bx/implatform/util/XssUtil.java

@ -11,9 +11,7 @@ public class XssUtil {
public static boolean checkXss(String inputString) {
if (inputString!=null) {
Matcher matcher = PATTERN.matcher(inputString);
if (matcher.find()) {
return true;
}
return matcher.find();
}
return false;
}

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

@ -6,7 +6,6 @@ import lombok.Data;
import org.hibernate.validator.constraints.Length;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
@Data
@ApiModel("群信息VO")

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

@ -13,7 +13,7 @@ import java.util.Date;
public class PrivateMessageVO {
@ApiModelProperty(value = " 消息id")
private long id;
private Long id;
@ApiModelProperty(value = " 发送者id")
private Long sendId;

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

@ -3,6 +3,9 @@ server:
port: 8888
#配置项目的数据源
spring:
mvc:
pathmatch:
matching-strategy: ant_path_matcher
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/box-im?useSSL=false&useUnicode=true&characterEncoding=utf-8&allowPublicKeyRetrieval=true
@ -29,11 +32,11 @@ mybatis-plus:
# *.xml的具体路径
- classpath*:mapper/*.xml
minio:
endpoint: http://42.194.187.243:9001 #内网地址
public: http://42.194.187.243:9001 #外网访问地址
endpoint: http://127.0.0.1:9001 #内网地址
public: http://127.0.0.1:9001 #外网访问地址
accessKey: admin
secretKey: admin123456
bucketName: box-im2
secretKey: 12345678
bucketName: box-im
imagePath: image
filePath: file
@ -47,4 +50,7 @@ jwt:
secret: MIIBIjANBgkq
refreshToken:
expireIn: 604800 #7天
secret: IKDiqVmn0VFU
secret: IKDiqVmn0VFU
logging:
config: classpath:logback-prod.xml

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

@ -1,31 +1,18 @@
package com.bx.imserver.config;
import com.alibaba.fastjson.support.spring.FastJsonRedisSerializer;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
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
public class RedisConfig {
@Resource
private RedisConnectionFactory factory;
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate();
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
// 设置值(value)的序列化采用FastJsonRedisSerializer
redisTemplate.setValueSerializer(fastJsonRedisSerializer());
@ -39,8 +26,7 @@ public class RedisConfig {
public FastJsonRedisSerializer fastJsonRedisSerializer(){
FastJsonRedisSerializer <Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
return fastJsonRedisSerializer;
return new FastJsonRedisSerializer<>(Object.class);
}

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

@ -27,12 +27,11 @@ public class IMChannelHandler extends SimpleChannelInboundHandler<IMSendInfo> {
/**
* 读取到消息后进行处理
*
* @param ctx
* @param sendInfo
* @throws Exception
* @param ctx channel上下文
* @param sendInfo 发送消息
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, IMSendInfo sendInfo) throws Exception {
protected void channelRead0(ChannelHandlerContext ctx, IMSendInfo sendInfo) {
// 创建处理器进行处理
AbstractMessageProcessor processor = ProcessorFactory.createProcessor(IMCmdType.fromCode(sendInfo.getCmd()));
processor.process(ctx,processor.transForm(sendInfo.getData()));
@ -41,12 +40,11 @@ public class IMChannelHandler extends SimpleChannelInboundHandler<IMSendInfo> {
/**
* 出现异常的处理 打印报错日志
*
* @param ctx
* @param cause
* @throws Exception
* @param ctx channel上下文
* @param cause 异常信息
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
log.error(cause.getMessage());
//关闭上下文
//ctx.close();
@ -55,16 +53,15 @@ public class IMChannelHandler extends SimpleChannelInboundHandler<IMSendInfo> {
/**
* 监控浏览器上线
*
* @param ctx
* @throws Exception
* @param ctx channel上下文
*/
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
public void handlerAdded(ChannelHandlerContext ctx) {
log.info(ctx.channel().id().asLongText() + "连接");
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
public void handlerRemoved(ChannelHandlerContext ctx) {
AttributeKey<Long> userIdAttr = AttributeKey.valueOf(ChannelAttrKey.USER_ID);
Long userId = ctx.channel().attr(userIdAttr).get();
AttributeKey<Integer> terminalAttr = AttributeKey.valueOf(ChannelAttrKey.TERMINAL_TYPE);
@ -75,7 +72,7 @@ public class IMChannelHandler extends SimpleChannelInboundHandler<IMSendInfo> {
// 移除channel
UserChannelCtxMap.removeChannelCtx(userId,terminal);
// 用户下线
RedisTemplate redisTemplate = SpringContextHolder.getBean("redisTemplate");
RedisTemplate<String,Object> redisTemplate = SpringContextHolder.getBean("redisTemplate");
String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID,userId.toString(), terminal.toString());
redisTemplate.delete(key);
log.info("断开连接,userId:{},终端类型:{}",userId,terminal);

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

@ -1,8 +1,8 @@
package com.bx.imserver.netty;
import com.bx.imcommon.contant.IMRedisKey;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
@ -12,14 +12,13 @@ import java.util.List;
@Slf4j
@Component
@AllArgsConstructor
public class IMServerGroup implements CommandLineRunner {
public static volatile long serverId = 0;
@Autowired
RedisTemplate<String,Object> redisTemplate;
@Autowired
private List<IMServer> imServers;
/***

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

@ -2,7 +2,6 @@ package com.bx.imserver.netty;
import io.netty.channel.ChannelHandlerContext;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

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

@ -5,12 +5,12 @@ import com.bx.imcommon.enums.IMCmdType;
import com.bx.imcommon.enums.IMSendCode;
import com.bx.imcommon.model.IMRecvInfo;
import com.bx.imcommon.model.IMSendInfo;
import com.bx.imcommon.model.IMUserInfo;
import com.bx.imcommon.model.IMSendResult;
import com.bx.imcommon.model.IMUserInfo;
import com.bx.imserver.netty.UserChannelCtxMap;
import io.netty.channel.ChannelHandlerContext;
import lombok.AllArgsConstructor;
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;
@ -19,10 +19,10 @@ import java.util.List;
@Slf4j
@Component
@AllArgsConstructor
public class GroupMessageProcessor extends AbstractMessageProcessor<IMRecvInfo> {
@Autowired
private RedisTemplate<String,Object> redisTemplate;
private final RedisTemplate<String,Object> redisTemplate;
@Async
@Override

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

@ -7,11 +7,10 @@ import com.bx.imcommon.enums.IMCmdType;
import com.bx.imcommon.model.IMHeartbeatInfo;
import com.bx.imcommon.model.IMSendInfo;
import com.bx.imserver.constant.ChannelAttrKey;
import com.bx.imserver.netty.ws.WebSocketServer;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.AttributeKey;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
@ -20,14 +19,10 @@ import java.util.concurrent.TimeUnit;
@Slf4j
@Component
@AllArgsConstructor
public class HeartbeatProcessor extends AbstractMessageProcessor<IMHeartbeatInfo> {
@Autowired
private WebSocketServer wsServer;
@Autowired
RedisTemplate<String,Object> redisTemplate;
private final RedisTemplate<String, Object> redisTemplate;
@Override
public void process(ChannelHandlerContext ctx, IMHeartbeatInfo beatInfo) {
@ -40,22 +35,21 @@ public class HeartbeatProcessor extends AbstractMessageProcessor<IMHeartbeatInfo
AttributeKey<Long> heartBeatAttr = AttributeKey.valueOf(ChannelAttrKey.HEARTBEAT_TIMES);
Long heartbeatTimes = ctx.channel().attr(heartBeatAttr).get();
ctx.channel().attr(heartBeatAttr).set(++heartbeatTimes);
if(heartbeatTimes%10 == 0){
if (heartbeatTimes % 10 == 0) {
// 每心跳10次,用户在线状态续一次命
AttributeKey<Long> userIdAttr = AttributeKey.valueOf(ChannelAttrKey.USER_ID);
Long userId = ctx.channel().attr(userIdAttr).get();
AttributeKey<Integer> terminalAttr = AttributeKey.valueOf(ChannelAttrKey.TERMINAL_TYPE);
Integer terminal = ctx.channel().attr(terminalAttr).get();
String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID,userId.toString(),terminal.toString());
String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID, userId.toString(), terminal.toString());
redisTemplate.expire(key, IMConstant.ONLINE_TIMEOUT_SECOND, TimeUnit.SECONDS);
}
}
@Override
public IMHeartbeatInfo transForm(Object o) {
HashMap map = (HashMap)o;
IMHeartbeatInfo heartbeatInfo = BeanUtil.fillBeanWithMap(map, new IMHeartbeatInfo(), false);
return heartbeatInfo;
return heartbeatInfo;
}
}

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

@ -5,17 +5,17 @@ import com.alibaba.fastjson.JSON;
import com.bx.imcommon.contant.IMConstant;
import com.bx.imcommon.contant.IMRedisKey;
import com.bx.imcommon.enums.IMCmdType;
import com.bx.imcommon.model.IMLoginInfo;
import com.bx.imcommon.model.IMSendInfo;
import com.bx.imcommon.model.IMSessionInfo;
import com.bx.imcommon.model.IMLoginInfo;
import com.bx.imcommon.util.JwtUtil;
import com.bx.imserver.constant.ChannelAttrKey;
import com.bx.imserver.netty.IMServerGroup;
import com.bx.imserver.netty.UserChannelCtxMap;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.AttributeKey;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
@ -25,10 +25,10 @@ import java.util.concurrent.TimeUnit;
@Slf4j
@Component
@RequiredArgsConstructor
public class LoginProcessor extends AbstractMessageProcessor<IMLoginInfo> {
@Autowired
RedisTemplate<String,Object> redisTemplate;
private final RedisTemplate<String,Object> redisTemplate;
@Value("${jwt.accessToken.secret}")
private String accessTokenSecret;
@ -47,7 +47,7 @@ public class LoginProcessor extends AbstractMessageProcessor<IMLoginInfo> {
ChannelHandlerContext context = UserChannelCtxMap.getChannelCtx(userId,terminal);
if(context != null && !ctx.channel().id().equals(context.channel().id())){
// 不允许多地登录,强制下线
IMSendInfo sendInfo = new IMSendInfo();
IMSendInfo<Object> sendInfo = new IMSendInfo<>();
sendInfo.setCmd(IMCmdType.FORCE_LOGUT.code());
sendInfo.setData("您已在其他地方登陆,将被强制下线");
context.channel().writeAndFlush(sendInfo);
@ -68,7 +68,7 @@ public class LoginProcessor extends AbstractMessageProcessor<IMLoginInfo> {
String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID,userId.toString(), terminal.toString());
redisTemplate.opsForValue().set(key, IMServerGroup.serverId, IMConstant.ONLINE_TIMEOUT_SECOND, TimeUnit.SECONDS);
// 响应ws
IMSendInfo sendInfo = new IMSendInfo();
IMSendInfo<Object> sendInfo = new IMSendInfo<>();
sendInfo.setCmd(IMCmdType.LOGIN.code());
ctx.channel().writeAndFlush(sendInfo);
}
@ -77,7 +77,6 @@ public class LoginProcessor extends AbstractMessageProcessor<IMLoginInfo> {
@Override
public IMLoginInfo transForm(Object o) {
HashMap map = (HashMap)o;
IMLoginInfo loginInfo = BeanUtil.fillBeanWithMap(map, new IMLoginInfo(), false);
return loginInfo;
return BeanUtil.fillBeanWithMap(map, new IMLoginInfo(), false);
}
}

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

@ -5,21 +5,21 @@ import com.bx.imcommon.enums.IMCmdType;
import com.bx.imcommon.enums.IMSendCode;
import com.bx.imcommon.model.IMRecvInfo;
import com.bx.imcommon.model.IMSendInfo;
import com.bx.imcommon.model.IMUserInfo;
import com.bx.imcommon.model.IMSendResult;
import com.bx.imcommon.model.IMUserInfo;
import com.bx.imserver.netty.UserChannelCtxMap;
import io.netty.channel.ChannelHandlerContext;
import lombok.AllArgsConstructor;
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
@AllArgsConstructor
public class PrivateMessageProcessor extends AbstractMessageProcessor<IMRecvInfo> {
@Autowired
private RedisTemplate<String,Object> redisTemplate;
private final RedisTemplate<String,Object> redisTemplate;
@Override
public void process(IMRecvInfo recvInfo) {
@ -30,7 +30,7 @@ public class PrivateMessageProcessor extends AbstractMessageProcessor<IMRecvInfo
ChannelHandlerContext channelCtx = UserChannelCtxMap.getChannelCtx(receiver.getId(),receiver.getTerminal());
if(channelCtx != null ){
// 推送消息到用户
IMSendInfo sendInfo = new IMSendInfo();
IMSendInfo<Object> sendInfo = new IMSendInfo<>();
sendInfo.setCmd(IMCmdType.PRIVATE_MESSAGE.code());
sendInfo.setData(recvInfo.getData());
channelCtx.channel().writeAndFlush(sendInfo);
@ -51,7 +51,7 @@ public class PrivateMessageProcessor extends AbstractMessageProcessor<IMRecvInfo
private void sendResult(IMRecvInfo recvInfo,IMSendCode sendCode){
if(recvInfo.getSendResult()) {
IMSendResult result = new IMSendResult();
IMSendResult<Object> result = new IMSendResult<>();
result.setSender(recvInfo.getSender());
result.setReceiver(recvInfo.getReceivers().get(0));
result.setCode(sendCode.code());

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

@ -9,16 +9,16 @@ public class ProcessorFactory {
AbstractMessageProcessor processor = null;
switch (cmd){
case LOGIN:
processor = (AbstractMessageProcessor) SpringContextHolder.getApplicationContext().getBean(LoginProcessor.class);
processor = SpringContextHolder.getApplicationContext().getBean(LoginProcessor.class);
break;
case HEART_BEAT:
processor = (AbstractMessageProcessor) SpringContextHolder.getApplicationContext().getBean(HeartbeatProcessor.class);
processor = SpringContextHolder.getApplicationContext().getBean(HeartbeatProcessor.class);
break;
case PRIVATE_MESSAGE:
processor = (AbstractMessageProcessor)SpringContextHolder.getApplicationContext().getBean(PrivateMessageProcessor.class);
processor = SpringContextHolder.getApplicationContext().getBean(PrivateMessageProcessor.class);
break;
case GROUP_MESSAGE:
processor = (AbstractMessageProcessor)SpringContextHolder.getApplicationContext().getBean(GroupMessageProcessor.class);
processor = SpringContextHolder.getApplicationContext().getBean(GroupMessageProcessor.class);
break;
default:
break;

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

@ -37,7 +37,6 @@ public class WebSocketServer implements IMServer {
private volatile boolean ready = false;
private ServerBootstrap bootstrap;
private EventLoopGroup bossGroup;
private EventLoopGroup workGroup;
@ -49,7 +48,7 @@ public class WebSocketServer implements IMServer {
@Override
public void start() {
bootstrap = new ServerBootstrap();
ServerBootstrap bootstrap = new ServerBootstrap();
bossGroup = new NioEventLoopGroup();
workGroup = new NioEventLoopGroup();
// 设置为主从线程模型
@ -60,7 +59,7 @@ public class WebSocketServer implements IMServer {
.childHandler(new ChannelInitializer<Channel>() {
// 添加处理的Handler,通常包括消息编解码、业务处理,也可以是日志、权限、过滤等
@Override
protected void initChannel(Channel ch) throws Exception {
protected void initChannel(Channel ch) {
// 获取职责链
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new IdleStateHandler(120, 0, 0, TimeUnit.SECONDS));
@ -82,7 +81,7 @@ public class WebSocketServer implements IMServer {
try {
// 绑定端口,启动select线程,轮询监听channel事件,监听到事件之后就会交给从线程池处理
Channel channel = bootstrap.bind(port).sync().channel();
bootstrap.bind(port).sync().channel();
// 就绪标志
this.ready = true;
log.info("websocket server 初始化完成,端口:{}",port);

1
im-server/src/main/java/com/bx/imserver/netty/ws/endecode/MessageProtocolEncoder.java

@ -14,7 +14,6 @@ public class MessageProtocolEncoder extends MessageToMessageEncoder<IMSendInfo>
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-server/src/main/java/com/bx/imserver/task/AbstractPullMessageTask.java

@ -4,13 +4,14 @@ import com.bx.imserver.netty.IMServerGroup;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.concurrent.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Slf4j
public abstract class AbstractPullMessageTask {
public abstract class AbstractPullMessageTask implements CommandLineRunner {
private int threadNum = 1;
private ExecutorService executorService;
@ -26,8 +27,9 @@ public abstract class AbstractPullMessageTask {
this.threadNum = threadNum;
}
@PostConstruct
public void init() {
@Override
public void run(String... args) {
// 初始化定时器
executorService = Executors.newFixedThreadPool(threadNum);

9
im-server/src/main/java/com/bx/imserver/task/PullUnreadGroupMessageTask.java → im-server/src/main/java/com/bx/imserver/task/PullGroupMessageTask.java

@ -7,18 +7,19 @@ import com.bx.imcommon.model.IMRecvInfo;
import com.bx.imserver.netty.IMServerGroup;
import com.bx.imserver.netty.processor.AbstractMessageProcessor;
import com.bx.imserver.netty.processor.ProcessorFactory;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Slf4j
@Component
public class PullUnreadGroupMessageTask extends AbstractPullMessageTask {
@AllArgsConstructor
public class PullGroupMessageTask extends AbstractPullMessageTask {
@Autowired
private RedisTemplate<String,Object> redisTemplate;
private final RedisTemplate<String,Object> redisTemplate;
@Override
public void pullMessage() {

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

@ -1,6 +1,5 @@
package com.bx.imserver.task;
import com.alibaba.fastjson.JSONObject;
import com.bx.imcommon.contant.IMRedisKey;
import com.bx.imcommon.enums.IMCmdType;
@ -8,19 +7,19 @@ import com.bx.imcommon.model.IMRecvInfo;
import com.bx.imserver.netty.IMServerGroup;
import com.bx.imserver.netty.processor.AbstractMessageProcessor;
import com.bx.imserver.netty.processor.ProcessorFactory;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Slf4j
@Component
public class PullUnreadPrivateMessageTask extends AbstractPullMessageTask {
@AllArgsConstructor
public class PullPrivateMessageTask extends AbstractPullMessageTask {
@Autowired
private RedisTemplate<String,Object> redisTemplate;
private final RedisTemplate<String,Object> redisTemplate;
@Override
public void pullMessage() {

2
im-ui/.env.development

@ -2,7 +2,7 @@
ENV = 'development'
# app名称
VUE_APP_NAME = "盒子IM"
// 接口请求地址
# 接口请求地址
VUE_APP_BASE_API = '/api'
# ws地址
VUE_APP_WS_URL = 'ws://localhost:8878/im'

4
im-ui/src/App.vue

@ -26,4 +26,8 @@ export default {
height: 100%;
width: 100%;
}
.el-message {
z-index: 99999999 !important;
}
</style>

4
im-ui/src/api/httpRequest.js

@ -54,7 +54,9 @@ http.interceptors.response.use(async response => {
sessionStorage.setItem("accessToken", data.accessToken);
sessionStorage.setItem("refreshToken", data.refreshToken);
// 这里需要把headers清掉,否则请求时会报错,原因暂不详...
response.config.headers=undefined;
if(typeof response.config.data != 'object'){
response.config.headers=undefined;
}
// 重新发送刚才的请求
return http(response.config)
} else {

BIN
im-ui/src/assets/image/icp_logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

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

@ -27,14 +27,14 @@
@click.stop="showEmotionBox()">
</div>
<div title="发送图片">
<file-upload :action="imageAction" :maxSize="5*1024*1024"
<file-upload :action="'/image/upload'" :maxSize="5*1024*1024"
:fileTypes="['image/jpeg', 'image/png', 'image/jpg', 'image/webp','image/gif']"
@before="onImageBefore" @success="onImageSuccess" @fail="onImageFail">
<i class="el-icon-picture-outline"></i>
</file-upload>
</div>
<div title="发送文件">
<file-upload :action="fileAction" :maxSize="10*1024*1024" @before="onFileBefore"
<file-upload :action="'/file/upload'" :maxSize="10*1024*1024" @before="onFileBefore"
@success="onFileSuccess" @fail="onFileFail">
<i class="el-icon-wallet"></i>
</file-upload>
@ -264,7 +264,7 @@
this.sendImageFile = null;
},
onImageSuccess(data, file) {
let msgInfo = JSON.parse(JSON.stringify(file.msgInfo || file.raw.msgInfo));
let msgInfo = JSON.parse(JSON.stringify(file.msgInfo));
msgInfo.content = JSON.stringify(data);
this.$http({
url: this.messageAction,
@ -277,7 +277,7 @@
})
},
onImageFail(e, file) {
let msgInfo = JSON.parse(JSON.stringify(file.msgInfo || file.raw.msgInfo));
let msgInfo = JSON.parse(JSON.stringify(file.msgInfo));
msgInfo.loadStatus = 'fail';
this.$store.commit("insertMessage", msgInfo);
},
@ -313,7 +313,7 @@
size: file.size,
url: url
}
let msgInfo = JSON.parse(JSON.stringify(file.raw.msgInfo));
let msgInfo = JSON.parse(JSON.stringify(file.msgInfo));
msgInfo.content = JSON.stringify(data);
this.$http({
url: this.messageAction,
@ -326,7 +326,7 @@
})
},
onFileFail(e, file) {
let msgInfo = JSON.parse(JSON.stringify(file.raw.msgInfo));
let msgInfo = JSON.parse(JSON.stringify(file.msgInfo));
msgInfo.loadStatus = 'fail';
this.$store.commit("insertMessage", msgInfo);
},
@ -462,7 +462,7 @@
let file = this.sendImageFile;
this.onImageBefore(this.sendImageFile);
let formData = new FormData()
formData.append('file', file.raw || file)
formData.append('file', file)
this.$http.post("/image/upload", formData, {
headers: {
'Content-Type': 'multipart/form-data'
@ -546,6 +546,7 @@
if(this.chat.unreadCount==0){
return;
}
this.$store.commit("resetUnreadCount", this.chat)
if (this.chat.type == "GROUP") {
var url = `/message/group/readed?groupId=${this.chat.targetId}`
} else {
@ -554,9 +555,7 @@
this.$http({
url: url,
method: 'put'
}).then(() => {
this.$store.commit("resetUnreadCount", this.chat)
})
}).then(() => {})
},
loadGroup(groupId) {
this.$http({
@ -631,12 +630,6 @@
}
return title;
},
imageAction() {
return `${process.env.VUE_APP_BASE_API}/image/upload`;
},
fileAction() {
return `${process.env.VUE_APP_BASE_API}/file/upload`;
},
messageAction() {
return `/message/${this.chat.type.toLowerCase()}/send`;
},

2
im-ui/src/components/chat/ChatPrivateVideo.vue

@ -67,7 +67,7 @@
methods: {
init() {
if (!this.hasUserMedia() || !this.hasRTCPeerConnection()) {
this.$message.error("您的浏览器不支持WebRTC");
this.$message.error("初始化失败,原因可能是: 1.未部署ssl证书 2.您的浏览器不支持WebRTC");
if (!this.master) {
this.sendFailed("对方浏览器不支持WebRTC")
}

60
im-ui/src/components/common/FileUpload.vue

@ -1,6 +1,6 @@
<template>
<el-upload :action="action" :headers="uploadHeaders" :accept="fileTypes==null?'':fileTypes.join(',')"
:show-file-list="false" :on-success="onSuccess" :on-error="onError" :disabled="disabled" :before-upload="beforeUpload">
<el-upload :action="'#'" :http-request="onFileUpload" :accept="fileTypes==null?'':fileTypes.join(',')" :show-file-list="false"
:disabled="disabled" :before-upload="beforeUpload">
<slot></slot>
</el-upload>
</template>
@ -11,13 +11,15 @@
data() {
return {
loading: null,
uploadHeaders: {"accessToken":sessionStorage.getItem('accessToken')}
uploadHeaders: {
"accessToken": sessionStorage.getItem('accessToken')
}
}
},
props: {
action: {
type: String,
required: true
required: false
},
fileTypes: {
type: Array,
@ -37,17 +39,33 @@
}
},
methods: {
onSuccess(res, file) {
this.loading && this.loading.close();
if (res.code == 200) {
this.$emit("success", res.data, file);
} else {
this.$message.error(res.message);
this.$emit("fail", res, file);
onFileUpload(file) {
//
if (this.showLoading) {
this.loading = this.$loading({
lock: true,
text: '正在上传...',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
}
},
onError(err, file) {
this.$emit("fail", err, file);
let formData = new FormData()
formData.append('file', file.file)
this.$http({
url: this.action,
data: formData,
method: 'post',
headers: {
'Content-Type': 'multipart/form-data'
}
}).then((data) => {
this.$emit("success", data, file.file);
}).catch((e) => {
this.$emit("fail", e, file.file);
}).finally(() => {
this.loading && this.loading.close();
})
},
beforeUpload(file) {
//
@ -64,20 +82,10 @@
this.$message.error(`文件大小不能超过 ${this.fileSizeStr}!`);
return false;
}
//
if (this.showLoading) {
this.loading = this.$loading({
lock: true,
text: '正在上传...',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
}
this.$emit("before", file);
return true;
}
},
computed: {
fileSizeStr() {
@ -94,4 +102,4 @@
</script>
<style>
</style>
</style>

1
im-ui/src/components/common/HeadImage.vue

@ -41,7 +41,6 @@
},
methods:{
showUserInfo(e){
console.log(this.id)
if(this.id && this.id>0){
this.$http({
url: `/user/find/${this.id}`,

26
im-ui/src/components/common/Icp.vue

@ -0,0 +1,26 @@
<template>
<div class="icp">
<img class="icp-icon" src="../../assets/image/icp_logo.png">
<a target="_blank" href="https://beian.miit.gov.cn/">粤ICP备2022140558号-1</a>
</div>
</template>
<script>
</script>
<style lang="scss">
.icp {
position: fixed;
text-align: center;
bottom: 20px;
margin: 0 auto;
width: 100%;
color: #5c6b77;
.icp-icon {
width: 20px;
height: 20px;
vertical-align: bottom;
}
}
</style>

3
im-ui/src/components/common/RightMenu.vue

@ -3,7 +3,7 @@
<div class="right-menu" :style="{'left':pos.x+'px','top':pos.y+'px'}">
<el-menu text-color="#333333">
<el-menu-item v-for="(item) in items" :key="item.key" :title="item.name"
@click="onSelectMenu(item)">
@click.native.stop="onSelectMenu(item)">
<span :class="item.icon"></span>
<span>{{item.name}}</span>
@ -33,6 +33,7 @@
},
onSelectMenu(item) {
this.$emit("select", item);
this.close();
}
}
}

43
im-ui/src/components/group/AddGroupMember.vue

@ -1,27 +1,29 @@
<template>
<el-dialog title="邀请好友" :visible.sync="visible" width="50%" :before-close="onClose">
<el-dialog title="邀请好友" :visible.sync="visible" width="50%" :before-close="onClose">
<div class="agm-container">
<div class="agm-l-box">
<el-input width="200px" placeholder="搜索好友" class="input-with-select" v-model="searchText" @keyup.enter.native="onSearch()">
<el-input width="200px" placeholder="搜索好友" class="input-with-select" v-model="searchText"
@keyup.enter.native="onSearch()">
<el-button slot="append" icon="el-icon-search" @click="onSearch()"></el-button>
</el-input>
<el-scrollbar style="height:400px;">
<div v-for="(friend,index) in friends" :key="friend.id">
<friend-item v-show="friend.nickName.startsWith(searchText)" :showDelete="false" @click.native="onSwitchCheck(friend)"
:menu="false" :friend="friend" :index="index" :active="index === activeIndex">
<el-checkbox :disabled="friend.disabled" @click.native.stop="" class="agm-friend-checkbox" v-model="friend.isCheck"
size="medium"></el-checkbox>
<friend-item v-show="friend.nickName.startsWith(searchText)" :showDelete="false"
@click.native="onSwitchCheck(friend)" :menu="false" :friend="friend" :index="index"
:active="false">
<el-checkbox :disabled="friend.disabled" @click.native.stop="" class="agm-friend-checkbox"
v-model="friend.isCheck" size="medium"></el-checkbox>
</friend-item>
</div>
</el-scrollbar>
</div>
<div class="agm-arrow el-icon-d-arrow-right"></div>
<div class="agm-r-box">
<div class="agm-select-tip"> 已勾选{{checkCount}}位好友</div>
<el-scrollbar style="height:400px;">
<div v-for="(friend,index) in friends" :key="friend.id">
<friend-item v-if="friend.isCheck && !friend.disabled" :friend="friend"
:index="index" :active="false" @del="onRemoveFriend(friend,index)"
:menu="false">
<friend-item v-if="friend.isCheck && !friend.disabled" :friend="friend" :index="index"
:active="false" @del="onRemoveFriend(friend,index)" :menu="false">
</friend-item>
</div>
</el-scrollbar>
@ -45,7 +47,6 @@
data() {
return {
searchText: "",
activeIndex: -1,
friends: []
}
},
@ -130,11 +131,11 @@
<style lang="scss">
.agm-container {
display: flex;
.agm-l-box {
flex: 1;
border: #dddddd solid 1px;
border: #53a0e79c solid 1px;
border-radius: 5px;
overflow: hidden;
.el-checkbox {
display: flex;
align-items: center;
@ -164,14 +165,22 @@
.agm-friend-checkbox {
margin-right: 20px;
}
}
.agm-arrow {
display: flex;
align-items: center;
font-size: 20px;
padding: 5px;
font-weight: 600;
color: #53a0e7cc;
}
.agm-r-box {
flex: 1;
border: #dddddd solid 1px;
border: #53a0e79c solid 1px;
border-radius: 5px;
.agm-select-tip {
text-align: left;
@ -181,4 +190,4 @@
}
}
}
</style>
</style>

11
im-ui/src/components/setting/Setting.vue

@ -7,7 +7,6 @@
:showLoading="true"
:maxSize="maxSize"
@success="onUploadSuccess"
:fileTypes="['image/jpeg', 'image/png', 'image/jpg','image/webp']">
<img v-if="userInfo.headImage" :src="userInfo.headImage" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
@ -38,13 +37,11 @@
</template>
<script>
import HeadImage from "../common/HeadImage.vue";
import FileUpload from "../common/FileUpload.vue";
export default {
name: "setting",
components: {
HeadImage,
FileUpload
},
data() {
@ -84,9 +81,9 @@
})
});
},
onUploadSuccess(res, file) {
this.userInfo.headImage = res.data.originUrl;
this.userInfo.headImageThumb = res.data.thumbUrl;
onUploadSuccess(data, file) {
this.userInfo.headImage = data.originUrl;
this.userInfo.headImageThumb = data.thumbUrl;
}
},
props: {
@ -96,7 +93,7 @@
},
computed:{
imageAction(){
return `${process.env.VUE_APP_BASE_API}/image/upload`;
return `/image/upload`;
}
},
watch: {

69
im-ui/src/store/chatStore.js

@ -6,7 +6,7 @@ import userStore from './userStore';
export default {
state: {
activeIndex: -1,
activeChat: null,
privateMsgMaxId: 0,
groupMsgMaxId: 0,
loadingPrivateMsg: false,
@ -30,14 +30,12 @@ export default {
},
openChat(state, chatInfo) {
let chat = null;
let activeChat = state.activeIndex >= 0 ? state.chats[state.activeIndex] : null;
for (let i in state.chats) {
if (state.chats[i].type == chatInfo.type &&
state.chats[i].targetId === chatInfo.targetId) {
chat = state.chats[i];
for (let idx in state.chats) {
if (state.chats[idx].type == chatInfo.type &&
state.chats[idx].targetId === chatInfo.targetId) {
chat = state.chats[idx];
// 放置头部
state.chats.splice(i, 1);
state.chats.unshift(chat);
this.commit("moveTop", idx)
break;
}
}
@ -52,21 +50,14 @@ export default {
lastSendTime: new Date().getTime(),
unreadCount: 0,
messages: [],
atMe: false,
atAll: false
};
state.chats.unshift(chat);
}
// 选中会话保持不变
if (activeChat) {
state.chats.forEach((chat, idx) => {
if (activeChat.type == chat.type &&
activeChat.targetId == chat.targetId) {
state.activeIndex = idx;
}
})
}
},
activeChat(state, idx) {
state.activeIndex = idx;
state.activeChat = state.chats[idx];
},
resetUnreadCount(state, chatInfo) {
for (let idx in state.chats) {
@ -74,7 +65,7 @@ export default {
state.chats[idx].targetId == chatInfo.targetId) {
state.chats[idx].unreadCount = 0;
state.chats[idx].atMe = false;
state.chats[idx].atAll = false;
state.chats[idx].atAll = false;
}
}
this.commit("saveToStorage");
@ -93,18 +84,23 @@ export default {
this.commit("saveToStorage");
},
removeChat(state, idx) {
state.chats.splice(idx, 1);
if (state.activeIndex >= state.chats.length) {
state.activeIndex = state.chats.length - 1;
if (state.chats[idx] == state.activeChat) {
state.activeChat = null;
}
state.chats.splice(idx, 1);
this.commit("saveToStorage");
},
moveTop(state, idx) {
let chat = state.chats[idx];
// 放置头部
state.chats.splice(idx, 1);
state.chats.unshift(chat);
this.commit("saveToStorage");
// 加载中不移动,很耗性能
if(state.loadingPrivateMsg || state.loadingGroupMsg){
return ;
}
if (idx > 0) {
let chat = state.chats[idx];
state.chats.splice(idx, 1);
state.chats.unshift(chat);
this.commit("saveToStorage");
}
},
removeGroupChat(state, groupId) {
for (let idx in state.chats) {
@ -131,6 +127,7 @@ export default {
if (state.chats[idx].type == type &&
state.chats[idx].targetId === targetId) {
chat = state.chats[idx];
this.commit("moveTop", idx)
break;
}
}
@ -151,12 +148,13 @@ export default {
chat.unreadCount++;
}
// 是否有人@我
if(!msgInfo.selfSend && chat.type=="GROUP" && msgInfo.atUserIds){
if (!msgInfo.selfSend && chat.type == "GROUP" && msgInfo.atUserIds &&
msgInfo.status != MESSAGE_STATUS.READED) {
let userId = userStore.state.userInfo.id;
if(msgInfo.atUserIds.indexOf(userId)>=0){
if (msgInfo.atUserIds.indexOf(userId) >= 0) {
chat.atMe = true;
}
if(msgInfo.atUserIds.indexOf(-1)>=0){
if (msgInfo.atUserIds.indexOf(-1) >= 0) {
chat.atAll = true;
}
}
@ -247,9 +245,18 @@ export default {
loadingPrivateMsg(state, loadding) {
state.loadingPrivateMsg = loadding;
if(!state.loadingPrivateMsg && !state.loadingGroupMsg){
this.commit("sort")
}
},
loadingGroupMsg(state, loadding) {
state.loadingGroupMsg = loadding;
if(!state.loadingPrivateMsg && !state.loadingGroupMsg){
this.commit("sort")
}
},
sort(state){
state.chats.sort((c1,c2)=>c2.lastSendTime-c1.lastSendTime);
},
saveToStorage(state) {
let userId = userStore.state.userInfo.id;
@ -262,7 +269,7 @@ export default {
localStorage.setItem(key, JSON.stringify(chatsData));
},
clear(state) {
state.activeIndex = -1;
state.activeChat = null;
state.chats = [];
}
},

27
im-ui/src/store/friendStore.js

@ -5,7 +5,7 @@ export default {
state: {
friends: [],
activeIndex: -1,
activeFriend: null,
timer: null
},
mutations: {
@ -22,12 +22,14 @@ export default {
}
})
},
activeFriend(state, index) {
state.activeIndex = index;
activeFriend(state, idx) {
state.activeFriend = state.friends[idx];
},
removeFriend(state, index) {
state.friends.splice(index, 1);
state.activeIndex = -1;
removeFriend(state, idx) {
if (state.friends[idx] == state.activeFriend) {
state.activeFriend = null;
}
state.friends.splice(idx, 1);
},
addFriend(state, friend) {
state.friends.push(friend);
@ -67,7 +69,7 @@ export default {
f.onlineApp = false;
}
});
let activeFriend = state.friends[state.activeIndex];
// 在线的在前面
state.friends.sort((f1,f2)=>{
if(f1.online&&!f2.online){
return -1;
@ -77,21 +79,12 @@ export default {
}
return 0;
});
// 重新排序后,activeIndex指向的好友可能会变化,需要重新指定
if(state.activeIndex >=0){
state.friends.forEach((f,i)=>{
if(f.id == activeFriend.id){
state.activeIndex = i;
}
})
}
},
clear(state) {
clearTimeout(state.timer);
state.friends = [];
state.timer = null;
state.activeIndex = -1;
state.activeFriend = [];
}
},
actions: {

19
im-ui/src/store/groupStore.js

@ -4,26 +4,27 @@ export default {
state: {
groups: [],
activeIndex: -1,
activeGroup: null,
},
mutations: {
setGroups(state, groups) {
state.groups = groups;
},
activeGroup(state, index) {
state.activeIndex = index;
activeGroup(state, idx) {
state.activeGroup = state.groups[idx];
},
addGroup(state, group) {
state.groups.unshift(group);
},
removeGroup(state, groupId) {
state.groups.forEach((g, index) => {
state.groups.forEach((g, idx) => {
if (g.id == groupId) {
state.groups.splice(index, 1);
state.activeIndex = -1;
state.groups.splice(idx, 1);
}
})
if (state.activeGroup.id == groupId) {
state.activeGroup = null;
}
},
updateGroup(state, group) {
state.groups.forEach((g, idx) => {
@ -33,9 +34,9 @@ export default {
}
})
},
clear(state){
clear(state) {
state.groups = [];
state.activeGroup = -1;
state.activeGroup = null;
}
},
actions: {

19
im-ui/src/view/Chat.vue

@ -14,12 +14,12 @@
<div v-for="(chat,index) in chatStore.chats" :key="index">
<chat-item v-show="chat.showName.startsWith(searchText)" :chat="chat" :index="index"
@click.native="onActiveItem(index)" @delete="onDelItem(index)" @top="onTop(index)"
:active="index === chatStore.activeIndex"></chat-item>
:active="chat === chatStore.activeChat"></chat-item>
</div>
</el-scrollbar>
</el-aside>
<el-container class="chat-box">
<chat-box v-show="activeChat.targetId>0" :chat="activeChat"></chat-box>
<chat-box v-if="chatStore.activeChat" :chat="chatStore.activeChat"></chat-box>
</el-container>
</el-container>
</template>
@ -57,21 +57,6 @@
chatStore() {
return this.$store.state.chatStore;
},
activeChat() {
let index = this.chatStore.activeIndex;
let chats = this.chatStore.chats
if (index >= 0 && chats.length > 0) {
return chats[index];
}
//
let emptyChat = {
targetId: -1,
showName: "",
headImage: "",
messages: []
}
return emptyChat;
},
loading(){
return this.chatStore.loadingGroupMsg || this.chatStore.loadingPrivateMsg
}

23
im-ui/src/view/Friend.vue

@ -15,7 +15,7 @@
<el-scrollbar class="friend-list-items">
<div v-for="(friend,index) in $store.state.friendStore.friends" :key="index">
<friend-item v-show="friend.nickName.startsWith(searchText)" :index="index"
:active="index === $store.state.friendStore.activeIndex" @chat="onSendMessage(friend)"
:active="friend === $store.state.friendStore.activeFriend" @chat="onSendMessage(friend)"
@delete="onDelItem(friend,index)" @click.native="onActiveItem(friend,index)">
</friend-item>
</div>
@ -46,7 +46,7 @@
<div class="frient-btn-group">
<el-button v-show="isFriend" icon="el-icon-chat-dot-round" type="primary" @click="onSendMessage(userInfo)">发送消息</el-button>
<el-button v-show="!isFriend" icon="el-icon-plus" type="primary" @click="onAddFriend(userInfo)">加为好友</el-button>
<el-button v-show="isFriend" icon="el-icon-delete" type="danger" @click="onDelItem(userInfo,friendStore.activeIndex)">删除好友</el-button>
<el-button v-show="isFriend" icon="el-icon-delete" type="danger" @click="onDelItem(userInfo,activeIdx)">删除好友</el-button>
</div>
</div>
</div>
@ -74,6 +74,7 @@
return {
searchText: "",
showAddFriend: false,
activeIdx: -1,
userInfo: {}
}
},
@ -84,11 +85,12 @@
onCloseAddFriend() {
this.showAddFriend = false;
},
onActiveItem(friend, index) {
this.$store.commit("activeFriend", index);
this.loadUserInfo(friend, index);
onActiveItem(friend, idx) {
this.$store.commit("activeFriend", idx);
this.activeIdx = idx
this.loadUserInfo(friend, idx);
},
onDelItem(friend, index) {
onDelItem(friend, idx) {
this.$confirm(`确认要解除与 '${friend.nickName}'的好友关系吗?`, '确认解除?', {
confirmButtonText: '确定',
cancelButtonText: '取消',
@ -99,7 +101,7 @@
method: 'delete'
}).then((data) => {
this.$message.success("删除好友成功");
this.$store.commit("removeFriend", index);
this.$store.commit("removeFriend", idx);
this.$store.commit("removePrivateChat", friend.id);
})
})
@ -173,14 +175,7 @@
isFriend(){
return this.friendStore.friends.find((f)=>f.id==this.userInfo.id);
}
},
mounted() {
if (this.friendStore.activeIndex >= 0) {
let friend = this.friendStore.friends[this.friendStore.activeIndex];
this.loadUserInfo(friend, this.friendStore.activeIndex);
}
}
}
</script>

19
im-ui/src/view/Group.vue

@ -13,7 +13,7 @@
<el-scrollbar class="group-list-items">
<div v-for="(group,index) in groupStore.groups" :key="index">
<group-item v-show="group.remark.startsWith(searchText)" :group="group"
:active="index === groupStore.activeIndex" @click.native="onActiveItem(group,index)">
:active="group === groupStore.activeGroup" @click.native="onActiveItem(group,index)">
</group-item>
</div>
</el-scrollbar>
@ -158,9 +158,9 @@
onCloseAddGroupMember() {
this.showAddGroupMember = false;
},
onUploadSuccess(res) {
this.activeGroup.headImage = res.data.originUrl;
this.activeGroup.headImageThumb = res.data.thumbUrl;
onUploadSuccess(data) {
this.activeGroup.headImage =data.originUrl;
this.activeGroup.headImageThumb = data.thumbUrl;
},
onSaveGroup() {
this.$refs['groupForm'].validate((valid) => {
@ -267,16 +267,7 @@
return this.activeGroup.ownerId == this.$store.state.userStore.userInfo.id;
},
imageAction() {
return `${process.env.VUE_APP_BASE_API}/image/upload`;
}
},
mounted() {
if (this.groupStore.activeIndex >= 0) {
let activeGroup = this.groupStore.groups[this.groupStore.activeIndex];
// store
this.activeGroup = JSON.parse(JSON.stringify(activeGroup));
//
this.loadGroupMembers();
return `/image/upload`;
}
}
}

226
im-ui/src/view/Login.vue

@ -1,73 +1,82 @@
<template>
<div class="login-view">
<div class="login-intro">
<div>
<h3>盒子IM 2.0版本正式发布</h3>
<ul>
<li>发布uniapp移动版本支持移动端和web端同时在线多端消息同步</li>
<li>目前移动端仅兼容h5和微信小程序后续会继续兼容更多终端类型</li>
<li>页面风格升级表情包更新自动生成文字头像等</li>
</ul>
</div>
<div>
<h3>最近更新(2023-11-05)</h3>
<ul>
<li>聊天输入框支持粘贴截图</li>
<li>聊天消息支持显示已读未读状态</li>
<li>修改拉取离线消息机制:用户登录后,自动从服务器同步最近1个月的消息</li>
</ul>
</div>
<div>
<h3>最近更新(2023-11-12)</h3>
<ul>
<li>群聊加入@功能</li>
<li>聊天输入框支持显示emoji表情</li>
</ul>
</div>
<div>
<h3>项目依旧完全开源可内网部署如果项目对您有帮助,请帮忙点个star:</h3>
</div>
<div class="login-icons">
<a class="login-icon">
<img src="https://img.shields.io/badge/license-MIT-red" />
</a>
<a class="login-icon" href="https://gitee.com/bluexsx/box-im" target="_blank">
<img src="https://gitee.com/bluexsx/box-im/badge/star.svg" />
</a>
<a class="login-icon" href="https://gitee.com/bluexsx/box-im" target="_blank">
<img src="https://img.shields.io/github/stars/bluexsx/box-im.svg?style=flat&logo=GitHub" />
</a>
<div class="login-content">
<div class="login-intro">
<div>
<h3>盒子IM 2.0版本正式发布</h3>
<ul>
<li>发布uniapp移动版本支持移动端和web端同时在线多端消息同步</li>
<li>目前移动端仅兼容h5和微信小程序后续会继续兼容更多终端类型</li>
<li>页面风格升级表情包更新自动生成文字头像等</li>
</ul>
</div>
<div>
<h3>最近更新(2023-11-05)</h3>
<ul>
<li>聊天输入框支持粘贴截图</li>
<li>聊天消息支持显示已读未读状态</li>
<li>修改拉取离线消息机制:用户登录后,自动从服务器同步最近1个月的消息</li>
</ul>
</div>
<div>
<h3>最近更新(2023-11-12)</h3>
<ul>
<li>群聊加入@功能</li>
<li>聊天输入框支持显示emoji表情</li>
</ul>
</div>
<div>
<h3>项目依旧完全开源可内网部署如果项目对您有帮助,请帮忙点个star:</h3>
</div>
<div class="login-icons">
<a class="login-icon">
<img src="https://img.shields.io/badge/license-MIT-red" />
</a>
<a class="login-icon" href="https://gitee.com/bluexsx/box-im" target="_blank">
<img src="https://gitee.com/bluexsx/box-im/badge/star.svg" />
</a>
<a class="login-icon" href="https://gitee.com/bluexsx/box-im" target="_blank">
<img src="https://img.shields.io/github/stars/bluexsx/box-im.svg?style=flat&logo=GitHub" />
</a>
</div>
</div>
</div>
<el-form class="login-form" :model="loginForm" status-icon :rules="rules" ref="loginForm" label-width="60px"
@keyup.enter.native="submitForm('loginForm')">
<div class="login-brand">登陆盒子IM</div>
<el-form-item label="终端" prop="userName" v-show="false">
<el-input type="terminal" v-model="loginForm.terminal" autocomplete="off" ></el-input>
</el-form-item>
<el-form-item label="用户名" prop="userName">
<el-input type="userName" v-model="loginForm.userName" autocomplete="off" placeholder="用户名"></el-input>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input type="password" v-model="loginForm.password" autocomplete="off" placeholder="密码"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm('loginForm')">登陆</el-button>
<el-button @click="resetForm('loginForm')">清空</el-button>
</el-form-item>
<div class="register">
<router-link to="/register">没有账号,前往注册</router-link>
</div>
</el-form>
<el-form class="login-form" :model="loginForm" status-icon :rules="rules" ref="loginForm" label-width="60px"
@keyup.enter.native="submitForm('loginForm')">
<div class="login-brand">登陆盒子IM</div>
<el-form-item label="终端" prop="userName" v-show="false">
<el-input type="terminal" v-model="loginForm.terminal" autocomplete="off"></el-input>
</el-form-item>
<el-form-item label="用户名" prop="userName">
<el-input type="userName" v-model="loginForm.userName" autocomplete="off"
placeholder="用户名"></el-input>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input type="password" v-model="loginForm.password" autocomplete="off"
placeholder="密码"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="submitForm('loginForm')">登陆</el-button>
<el-button @click="resetForm('loginForm')">清空</el-button>
</el-form-item>
<div class="register">
<router-link to="/register">没有账号,前往注册</router-link>
</div>
</el-form>
</div>
<icp></icp>
</div>
</template>
<script>
import Icp from '../components/common/Icp.vue'
export default {
name: "login",
components: {
Icp
},
data() {
var checkUsername = (rule, value, callback) => {
if (!value) {
@ -162,64 +171,69 @@
<style scoped lang="scss">
.login-view {
position: relative;
display: flex;
justify-content: space-around;
align-items: center;
width: 100%;
height: 100%;
background: rgb(232, 242, 255);
background-size: cover;
box-sizing: border-box;
padding: 10%;
.login-intro {
flex: 1;
padding: 40px;
max-width: 600px;
.login-title {
text-align: center;
font-weight: 600;
font-size: 30px;
}
.login-icons {
display: flex;
align-items: center;
.login-icon {
padding-left: 5px;
.login-content {
position: relative;
display: flex;
justify-content: space-around;
align-items: center;
padding: 10%;
.login-intro {
flex: 1;
padding: 40px;
max-width: 600px;
.login-title {
text-align: center;
font-weight: 600;
font-size: 30px;
}
}
}
.login-form {
height: 340px;
width: 400px;
padding: 30px;
background: white;
opacity: 0.9;
box-shadow: 0px 0px 1px #ccc;
border-radius: 3%;
overflow: hidden;
border: 1px solid #ccc;
.login-brand {
line-height: 50px;
margin: 30px 0 40px 0;
font-size: 22px;
font-weight: 600;
letter-spacing: 2px;
text-transform: uppercase;
text-align: center;
.login-icons {
display: flex;
align-items: center;
.login-icon {
padding-left: 5px;
}
}
}
.register {
display: flex;
flex-direction: row-reverse;
line-height: 40px;
text-align: left;
padding-left: 20px;
.login-form {
height: 340px;
width: 400px;
padding: 30px;
background: white;
opacity: 0.9;
box-shadow: 0px 0px 1px #ccc;
border-radius: 3%;
overflow: hidden;
border: 1px solid #ccc;
.login-brand {
line-height: 50px;
margin: 30px 0 40px 0;
font-size: 22px;
font-weight: 600;
letter-spacing: 2px;
text-transform: uppercase;
text-align: center;
}
.register {
display: flex;
flex-direction: row-reverse;
line-height: 40px;
text-align: left;
padding-left: 20px;
}
}
}
}

7
im-ui/src/view/Register.vue

@ -1,7 +1,6 @@
<template>
<el-container class="register-view">
<div>
<el-form :model="registerForm" status-icon :rules="rules" ref="registerForm" label-width="80px" class="web-ruleForm">
<div class="register-brand">欢迎成为盒子IM的用户</div>
<el-form-item label="用户名" prop="userName">
@ -25,12 +24,17 @@
</div>
</el-form>
</div>
<icp></icp>
</el-container>
</template>
<script>
import Icp from '../components/common/Icp.vue'
export default {
name: "login",
components: {
Icp
},
data() {
var checkUserName = (rule, value, callback) => {
if (!value) {
@ -52,7 +56,6 @@
};
var checkConfirmPassword = (rule, value, callback) => {
console.log("checkConfirmPassword");
if (value === '') {
return callback(new Error('请输入密码'));
}

2
im-uniapp/App.vue

@ -73,7 +73,6 @@
this.insertPrivateMessage(friend,msgInfo);
}
})
store.commit("refreshChats");
if (msgInfos.length == 100) {
//
this.loadPrivateMessage(msgInfos[99].id);
@ -96,7 +95,6 @@
this.insertGroupMessage(group,msgInfo);
}
})
store.commit("refreshChats");
if (msgInfos.length == 100) {
//
this.loadGroupMessage(msgInfos[99].id);

2
im-uniapp/components/chat-message-item/chat-message-item.vue

@ -7,7 +7,7 @@
<view class="chat-msg-normal" v-if="msgInfo.type>=0 && msgInfo.type<10"
:class="{'chat-msg-mine':msgInfo.selfSend}">
<head-image class="avatar" @longpress="$emit('longPressHead')" :id="msgInfo.sendId" :url="headImage" :name="showName" :size="80"></head-image>
<head-image class="avatar" @longpress.prevent="$emit('longPressHead')" :id="msgInfo.sendId" :url="headImage" :name="showName" :size="80"></head-image>
<view class="chat-msg-content" @longpress="onShowMenu($event)">
<view v-if="msgInfo.groupId && !msgInfo.selfSend" class="chat-msg-top">
<text>{{showName}}</text>

50
im-uniapp/pages/chat/chat-box.vue

@ -11,8 +11,8 @@
<view v-for="(msgInfo,idx) in chat.messages" :key="idx">
<chat-message-item v-if="idx>=showMinIdx" :headImage="headImage(msgInfo)"
:showName="showName(msgInfo)" @recall="onRecallMessage" @delete="onDeleteMessage"
@longPressHead="onLongPressHead(msgInfo)"
@download="onDownloadFile" :id="'chat-item-'+idx" :msgInfo="msgInfo">
@longPressHead="onLongPressHead(msgInfo)" @download="onDownloadFile" :id="'chat-item-'+idx"
:msgInfo="msgInfo">
</chat-message-item>
</view>
</scroll-view>
@ -31,14 +31,14 @@
<view class="send-text">
<textarea class="send-text-area" v-model="sendText" auto-height :show-confirm-bar="false"
:adjust-position="false" @confirm="sendTextMessage()" @keyboardheightchange="onKeyboardheightchange"
confirm-type="send" confirm-hold :hold-keyboard="true"></textarea>
@input="onTextInput" confirm-type="send" confirm-hold :hold-keyboard="true"></textarea>
</view>
<view v-if="chat.type=='GROUP'" class="iconfont icon-at" @click="openAtBox()"></view>
<view class="iconfont icon-icon_emoji" @click="switchChatTabBox('emo',true)"></view>
<view v-if="sendText==''" class="iconfont icon-add" @click="switchChatTabBox('tools',true)">
</view>
<button v-if="sendText!=''||atUserIds.length>0" class="btn-send" type="primary" @touchend.prevent="sendTextMessage()"
size="mini">发送</button>
<button v-if="sendText!=''||atUserIds.length>0" class="btn-send" type="primary"
@touchend.prevent="sendTextMessage()" size="mini">发送</button>
</view>
<view class="chat-tab-bar" v-show="chatTabBox!='none' ||showKeyBoard " :style="{height:`${keyboardHeight}px`}">
@ -119,8 +119,8 @@
onAtComplete(atUserIds) {
this.atUserIds = atUserIds;
},
onLongPressHead(msgInfo){
if(!msgInfo.selfSend && this.chat.type=="GROUP" && this.atUserIds.indexOf(msgInfo.sendId)<0){
onLongPressHead(msgInfo) {
if (!msgInfo.selfSend && this.chat.type == "GROUP" && this.atUserIds.indexOf(msgInfo.sendId) < 0) {
this.atUserIds.push(msgInfo.sendId);
}
},
@ -141,12 +141,12 @@
}
},
sendTextMessage() {
if (!this.sendText.trim() && this.atUserIds.length==0) {
if (!this.sendText.trim() && this.atUserIds.length == 0) {
return uni.showToast({
title: "不能发送空白信息",
icon: "none"
});
}
let atText = this.createAtText()
let msgInfo = {
@ -179,10 +179,10 @@
createAtText() {
let atText = "";
this.atUserIds.forEach((id) => {
if(id==-1){
if (id == -1) {
atText += ` @全体成员`;
}else{
let member = this.groupMembers.find((m)=>m.userId==id);
} else {
let member = this.groupMembers.find((m) => m.userId == id);
if (member) {
atText += ` @${member.aliasName}`;
}
@ -414,6 +414,16 @@
})
}
},
onTextInput(e) {
let idx = e.detail.cursor - 1;
if (this.chat.type == 'GROUP' && e.detail.value[idx] == '@') {
this.openAtBox();
let sendText = e.detail.value.replace("@", '');
this.$nextTick(() => {
this.sendText = sendText;
})
}
},
readedMessage() {
if (this.chat.type == "GROUP") {
var url = `/message/group/readed?groupId=${this.chat.targetId}`
@ -491,15 +501,18 @@
unreadCount() {
return this.chat.unreadCount;
},
atUserItems(){
atUserItems() {
let atUsers = [];
this.atUserIds.forEach((id)=>{
if(id==-1){
atUsers.push({id:-1,aliasName:"全体成员"})
this.atUserIds.forEach((id) => {
if (id == -1) {
atUsers.push({
id: -1,
aliasName: "全体成员"
})
return;
}
let member = this.groupMembers.find((m)=>m.userId==id);
if(member){
let member = this.groupMembers.find((m) => m.userId == id);
if (member) {
atUsers.push(member);
}
})
@ -612,6 +625,7 @@
.chat-at-scroll-box {
flex: 1;
width: 80%;
.chat-at-items {
display: flex;
align-items: center;

45
im-uniapp/store/chatStore.js

@ -31,15 +31,12 @@ export default {
},
openChat(state, chatInfo) {
let chat = null;
for (let i in state.chats) {
if (state.chats[i].type == chatInfo.type &&
state.chats[i].targetId === chatInfo.targetId) {
chat = state.chats[i];
// 放置头部(这个操作非常耗资源,正在加载消息时不执行)
if(!state.loadingPrivateMsg && !state.loadingPrivateMsg){
state.chats.splice(i, 1);
state.chats.unshift(chat);
}
for (let idx in state.chats) {
if (state.chats[idx].type == chatInfo.type &&
state.chats[idx].targetId === chatInfo.targetId) {
chat = state.chats[idx];
// 放置头部
this.commit("moveTop",idx)
break;
}
}
@ -54,6 +51,8 @@ export default {
lastSendTime: new Date().getTime(),
unreadCount: 0,
messages: [],
atMe: false,
atAll: false
};
state.chats.unshift(chat);
}
@ -110,22 +109,27 @@ export default {
}
},
moveTop(state, idx) {
let chat = state.chats[idx];
// 放置头部
state.chats.splice(idx, 1);
state.chats.unshift(chat);
// 加载中不移动,很耗性能
if(state.loadingPrivateMsg || state.loadingGroupMsg){
return ;
}
if (idx > 0) {
let chat = state.chats[idx];
state.chats.splice(idx, 1);
state.chats.unshift(chat);
this.commit("saveToStorage");
}
},
insertMessage(state, msgInfo) {
// 获取对方id或群id
let type = msgInfo.groupId ? 'GROUP' : 'PRIVATE';
let targetId = msgInfo.groupId ? msgInfo.groupId : msgInfo.selfSend ? msgInfo.recvId : msgInfo.sendId;
let chat = null;
let chatIdx = -1;
for (let idx in state.chats) {
if (state.chats[idx].type == type &&
state.chats[idx].targetId === targetId) {
chat = state.chats[idx];
chatIdx = idx;
this.commit("moveTop", idx)
break;
}
}
@ -143,13 +147,13 @@ export default {
chat.lastSendTime = msgInfo.sendTime;
chat.sendNickName = msgInfo.sendNickName;
}
// 未读加1
if (!msgInfo.selfSend && msgInfo.status != MESSAGE_STATUS.READED) {
chat.unreadCount++;
}
// 是否有人@我
if(!msgInfo.selfSend && chat.type=="GROUP" && msgInfo.atUserIds){
if(!msgInfo.selfSend && chat.type=="GROUP" && msgInfo.atUserIds
&& msgInfo.status != MESSAGE_STATUS.READED){
let userId = userStore.state.userInfo.id;
if(msgInfo.atUserIds.indexOf(userId)>=0){
chat.atMe = true;
@ -158,7 +162,6 @@ export default {
chat.atAll = true;
}
}
// 记录消息的最大id
if (msgInfo.id && type == "PRIVATE" && msgInfo.id > state.privateMsgMaxId) {
state.privateMsgMaxId = msgInfo.id;
@ -246,9 +249,15 @@ export default {
},
loadingPrivateMsg(state, loadding) {
state.loadingPrivateMsg = loadding;
if(!state.loadingPrivateMsg && !state.loadingGroupMsg){
this.commit("refreshChats")
}
},
loadingGroupMsg(state, loadding) {
state.loadingGroupMsg = loadding;
if(!state.loadingPrivateMsg && !state.loadingGroupMsg){
this.commit("refreshChats")
}
},
refreshChats(state){
state.chats.forEach((chat)=>{

8
pom.xml

@ -34,6 +34,7 @@
<zxing.version>3.3.3</zxing.version>
<commons-lang3.version>3.8.1</commons-lang3.version>
<lombok.version>1.18.16</lombok.version>
<mysql.version>5.1.46</mysql.version>
</properties>
@ -43,7 +44,7 @@
<!-- Import dependency management from Spring Boot -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.0.3.RELEASE</version>
<version>2.7.17</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@ -92,6 +93,11 @@
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>

Loading…
Cancel
Save