Эх сурвалжийг харах

好友优化(改为逻辑删除)

xsx 11 сар өмнө
parent
commit
729fab4ef1
38 өөрчлөгдсөн 453 нэмэгдсэн , 287 устгасан
  1. 5 1
      im-common/src/main/java/com/bx/imcommon/enums/IMTerminalType.java
  2. 0 1
      im-platform/src/main/java/com/bx/implatform/config/MinIoClientConfig.java
  3. 1 20
      im-platform/src/main/java/com/bx/implatform/controller/FriendController.java
  4. 0 1
      im-platform/src/main/java/com/bx/implatform/controller/LoginController.java
  5. 0 1
      im-platform/src/main/java/com/bx/implatform/controller/WebrtcPrivateController.java
  6. 5 0
      im-platform/src/main/java/com/bx/implatform/entity/Friend.java
  7. 2 0
      im-platform/src/main/java/com/bx/implatform/enums/MessageType.java
  8. 2 1
      im-platform/src/main/java/com/bx/implatform/listener/PrivateMessageListener.java
  9. 24 8
      im-platform/src/main/java/com/bx/implatform/service/FriendService.java
  10. 0 7
      im-platform/src/main/java/com/bx/implatform/service/SensitiveWordService.java
  11. 158 59
      im-platform/src/main/java/com/bx/implatform/service/impl/FriendServiceImpl.java
  12. 0 2
      im-platform/src/main/java/com/bx/implatform/service/impl/GroupMessageServiceImpl.java
  13. 3 5
      im-platform/src/main/java/com/bx/implatform/service/impl/GroupServiceImpl.java
  14. 10 20
      im-platform/src/main/java/com/bx/implatform/service/impl/PrivateMessageServiceImpl.java
  15. 0 1
      im-platform/src/main/java/com/bx/implatform/task/consumer/GroupBannedConsumerTask.java
  16. 0 1
      im-platform/src/main/java/com/bx/implatform/task/consumer/GroupUnbanConsumerTask.java
  17. 3 0
      im-platform/src/main/java/com/bx/implatform/vo/FriendVO.java
  18. 0 1
      im-platform/src/main/resources/application-dev.yml
  19. 1 3
      im-server/src/main/resources/application-dev.yml
  20. 20 13
      im-uniapp/App.vue
  21. 2 0
      im-uniapp/common/enums.js
  22. 26 9
      im-uniapp/pages/chat/chat-box.vue
  23. 13 19
      im-uniapp/pages/common/user-info.vue
  24. 3 4
      im-uniapp/pages/friend/friend-add.vue
  25. 3 6
      im-uniapp/pages/friend/friend.vue
  26. 1 2
      im-uniapp/pages/group/group-info.vue
  27. 4 4
      im-uniapp/pages/group/group-invite.vue
  28. 17 4
      im-uniapp/store/chatStore.js
  29. 18 11
      im-uniapp/store/friendStore.js
  30. 2 0
      im-web/src/api/enums.js
  31. 27 9
      im-web/src/components/chat/ChatBox.vue
  32. 4 5
      im-web/src/components/common/UserInfo.vue
  33. 4 5
      im-web/src/components/friend/AddFriend.vue
  34. 3 1
      im-web/src/components/group/AddGroupMember.vue
  35. 15 5
      im-web/src/store/chatStore.js
  36. 30 11
      im-web/src/store/friendStore.js
  37. 26 30
      im-web/src/view/Friend.vue
  38. 21 17
      im-web/src/view/Home.vue

+ 5 - 1
im-common/src/main/java/com/bx/imcommon/enums/IMTerminalType.java

@@ -20,7 +20,11 @@ public enum IMTerminalType {
     /**
      * pc
      */
-    PC(2, "pc");
+    PC(2, "pc"),
+    /**
+     * 未知
+     */
+    UNKNOW(-1, "未知");
 
     private final Integer code;
 

+ 0 - 1
im-platform/src/main/java/com/bx/implatform/config/MinIoClientConfig.java

@@ -2,7 +2,6 @@ package com.bx.implatform.config;
 
 import com.bx.implatform.config.props.MinioProperties;
 import io.minio.MinioClient;
-import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 

+ 1 - 20
im-platform/src/main/java/com/bx/implatform/controller/FriendController.java

@@ -1,11 +1,9 @@
 package com.bx.implatform.controller;
 
 import com.bx.implatform.annotation.RepeatSubmit;
-import com.bx.implatform.entity.Friend;
 import com.bx.implatform.result.Result;
 import com.bx.implatform.result.ResultUtils;
 import com.bx.implatform.service.FriendService;
-import com.bx.implatform.session.SessionContext;
 import com.bx.implatform.vo.FriendVO;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -15,7 +13,6 @@ import lombok.RequiredArgsConstructor;
 import org.springframework.web.bind.annotation.*;
 
 import java.util.List;
-import java.util.stream.Collectors;
 
 @Tag(name = "好友")
 @RestController
@@ -28,15 +25,7 @@ public class FriendController {
     @GetMapping("/list")
     @Operation(summary = "好友列表", description = "获取好友列表")
     public Result<List<FriendVO>> findFriends() {
-        List<Friend> friends = friendService.findFriendByUserId(SessionContext.getSession().getUserId());
-        List<FriendVO> vos = friends.stream().map(f -> {
-            FriendVO vo = new FriendVO();
-            vo.setId(f.getFriendId());
-            vo.setHeadImage(f.getFriendHeadImage());
-            vo.setNickName(f.getFriendNickName());
-            return vo;
-        }).collect(Collectors.toList());
-        return ResultUtils.success(vos);
+        return ResultUtils.success(friendService.findFriends());
     }
 
 
@@ -62,13 +51,5 @@ public class FriendController {
         return ResultUtils.success();
     }
 
-    @PutMapping("/update")
-    @Operation(summary = "更新好友信息", description = "更新好友头像或昵称")
-    public Result modifyFriend(@Valid @RequestBody FriendVO vo) {
-        friendService.update(vo);
-        return ResultUtils.success();
-    }
-
-
 }
 

+ 0 - 1
im-platform/src/main/java/com/bx/implatform/controller/LoginController.java

@@ -1,6 +1,5 @@
 package com.bx.implatform.controller;
 
-import com.bx.implatform.annotation.RepeatSubmit;
 import com.bx.implatform.dto.LoginDTO;
 import com.bx.implatform.dto.ModifyPwdDTO;
 import com.bx.implatform.dto.RegisterDTO;

+ 0 - 1
im-platform/src/main/java/com/bx/implatform/controller/WebrtcPrivateController.java

@@ -1,6 +1,5 @@
 package com.bx.implatform.controller;
 
-import com.bx.implatform.annotation.OnlineCheck;
 import com.bx.implatform.result.Result;
 import com.bx.implatform.result.ResultUtils;
 import com.bx.implatform.service.WebrtcPrivateService;

+ 5 - 0
im-platform/src/main/java/com/bx/implatform/entity/Friend.java

@@ -45,6 +45,11 @@ public class Friend{
      */
     private String friendHeadImage;
 
+    /**
+     * 是否已删除
+     */
+    private Boolean deleted;
+
     /**
      * 创建时间
      */

+ 2 - 0
im-platform/src/main/java/com/bx/implatform/enums/MessageType.java

@@ -33,6 +33,8 @@ public enum MessageType {
     USER_BANNED(50,"用户封禁"),
     GROUP_BANNED(51,"群聊封禁"),
     GROUP_UNBAN(52,"群聊解封"),
+    FRIEND_NEW(80, "新增好友"),
+    FRIEND_DEL(81, "删除好友"),
     RTC_CALL_VOICE(100, "语音呼叫"),
     RTC_CALL_VIDEO(101, "视频呼叫"),
     RTC_ACCEPT(102, "接受"),

+ 2 - 1
im-platform/src/main/java/com/bx/implatform/listener/PrivateMessageListener.java

@@ -17,6 +17,7 @@ import org.springframework.context.annotation.Lazy;
 
 import java.util.HashSet;
 import java.util.List;
+import java.util.Objects;
 import java.util.Set;
 
 @Slf4j
@@ -31,7 +32,7 @@ public class PrivateMessageListener implements MessageListener<PrivateMessageVO>
         for(IMSendResult<PrivateMessageVO> result : results){
             PrivateMessageVO messageInfo = result.getData();
             // 更新消息状态,这里只处理成功消息,失败的消息继续保持未读状态
-            if (result.getCode().equals(IMSendCode.SUCCESS.code())) {
+            if (result.getCode().equals(IMSendCode.SUCCESS.code()) && !Objects.isNull(messageInfo.getId())) {
                 messageIds.add(messageInfo.getId());
                 log.info("消息送达,消息id:{},发送者:{},接收者:{},终端:{}", messageInfo.getId(), result.getSender().getId(), result.getReceiver().getId(), result.getReceiver().getTerminal());
             }

+ 24 - 8
im-platform/src/main/java/com/bx/implatform/service/FriendService.java

@@ -17,14 +17,27 @@ public interface FriendService extends IService<Friend> {
      */
     Boolean isFriend(Long userId1, Long userId2);
 
+    /**
+     * 查询用户的所有好友,包括已删除的
+     *
+     * @return 好友列表
+     */
+    List<Friend> findAllFriends();
 
     /**
      * 查询用户的所有好友
      *
-     * @param userId 用户id
+     * @param friendIds 好友id
+     * @return 好友列表
+     */
+    List<Friend> findByFriendIds(List<Long> friendIds);
+
+    /**
+     * 查询当前用户的所有好友
+     *
      * @return 好友列表
      */
-    List<Friend> findFriendByUserId(Long userId);
+    List<FriendVO> findFriends();
 
     /**
      * 添加好友,互相建立好友关系
@@ -41,17 +54,20 @@ public interface FriendService extends IService<Friend> {
     void delFriend(Long friendId);
 
     /**
-     * 更新好友信息,主要是头像和昵称
+     * 查询指定的某个好友信息
      *
-     * @param vo 好友vo
+     * @param friendId 好友的用户id
+     * @return 好友信息
      */
-    void update(FriendVO vo);
+    FriendVO findFriend(Long friendId);
 
     /**
-     * 查询指定的某个好友信息
+     * 绑定好友关系
      *
+     * @param userId   好友的id
      * @param friendId 好友的用户id
      * @return 好友信息
      */
-    FriendVO findFriend(Long friendId);
-}
+    void bindFriend(Long userId, Long friendId);
+
+}

+ 0 - 7
im-platform/src/main/java/com/bx/implatform/service/SensitiveWordService.java

@@ -1,14 +1,7 @@
 package com.bx.implatform.service;
 
 import com.baomidou.mybatisplus.extension.service.IService;
-import com.bx.implatform.dto.LoginDTO;
-import com.bx.implatform.dto.ModifyPwdDTO;
-import com.bx.implatform.dto.RegisterDTO;
 import com.bx.implatform.entity.SensitiveWord;
-import com.bx.implatform.entity.User;
-import com.bx.implatform.vo.LoginVO;
-import com.bx.implatform.vo.OnlineTerminalVO;
-import com.bx.implatform.vo.UserVO;
 
 import java.util.List;
 

+ 158 - 59
im-platform/src/main/java/com/bx/implatform/service/impl/FriendServiceImpl.java

@@ -1,19 +1,31 @@
 package com.bx.implatform.service.impl;
 
+import com.alibaba.fastjson.JSON;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.bx.imclient.IMClient;
+import com.bx.imcommon.enums.IMTerminalType;
+import com.bx.imcommon.model.IMPrivateMessage;
+import com.bx.imcommon.model.IMUserInfo;
 import com.bx.implatform.contant.RedisKey;
 import com.bx.implatform.entity.Friend;
+import com.bx.implatform.entity.PrivateMessage;
 import com.bx.implatform.entity.User;
+import com.bx.implatform.enums.MessageStatus;
+import com.bx.implatform.enums.MessageType;
 import com.bx.implatform.exception.GlobalException;
 import com.bx.implatform.mapper.FriendMapper;
+import com.bx.implatform.mapper.PrivateMessageMapper;
 import com.bx.implatform.mapper.UserMapper;
 import com.bx.implatform.service.FriendService;
 import com.bx.implatform.session.SessionContext;
 import com.bx.implatform.session.UserSession;
+import com.bx.implatform.util.BeanUtils;
 import com.bx.implatform.vo.FriendVO;
+import com.bx.implatform.vo.PrivateMessageVO;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.aop.framework.AopContext;
@@ -23,8 +35,10 @@ import org.springframework.cache.annotation.Cacheable;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.util.Date;
 import java.util.List;
 import java.util.Objects;
+import java.util.stream.Collectors;
 
 @Slf4j
 @Service
@@ -32,15 +46,33 @@ import java.util.Objects;
 @CacheConfig(cacheNames = RedisKey.IM_CACHE_FRIEND)
 public class FriendServiceImpl extends ServiceImpl<FriendMapper, Friend> implements FriendService {
 
+    private final PrivateMessageMapper privateMessageMapper;
     private final UserMapper userMapper;
+    private final IMClient imClient;
 
     @Override
-    public List<Friend> findFriendByUserId(Long userId) {
-        LambdaQueryWrapper<Friend> queryWrapper = Wrappers.lambdaQuery();
-        queryWrapper.eq(Friend::getUserId, userId);
-        return this.list(queryWrapper);
+    public List<Friend> findAllFriends() {
+        Long userId = SessionContext.getSession().getUserId();
+        LambdaQueryWrapper<Friend> wrapper = Wrappers.lambdaQuery();
+        wrapper.eq(Friend::getUserId, userId);
+        return this.list(wrapper);
     }
 
+    @Override
+    public List<Friend> findByFriendIds(List<Long> friendIds) {
+        Long userId = SessionContext.getSession().getUserId();
+        LambdaQueryWrapper<Friend> wrapper = Wrappers.lambdaQuery();
+        wrapper.eq(Friend::getUserId, userId);
+        wrapper.in(Friend::getFriendId, friendIds);
+        wrapper.eq(Friend::getDeleted,false);
+        return this.list(wrapper);
+    }
+
+    @Override
+    public List<FriendVO> findFriends() {
+        List<Friend> friends = this.findAllFriends();
+        return friends.stream().map(this::conver).collect(Collectors.toList());
+    }
 
     @Transactional(rollbackFor = Exception.class)
     @Override
@@ -50,53 +82,37 @@ public class FriendServiceImpl extends ServiceImpl<FriendMapper, Friend> impleme
             throw new GlobalException("不允许添加自己为好友");
         }
         // 互相绑定好友关系
-        FriendServiceImpl proxy = (FriendServiceImpl) AopContext.currentProxy();
+        FriendServiceImpl proxy = (FriendServiceImpl)AopContext.currentProxy();
         proxy.bindFriend(userId, friendId);
         proxy.bindFriend(friendId, userId);
+        // 推送添加好友提示
+        sendAddTipMessage(friendId);
         log.info("添加好友,用户id:{},好友id:{}", userId, friendId);
     }
 
-
     @Transactional(rollbackFor = Exception.class)
     @Override
     public void delFriend(Long friendId) {
-        long userId = SessionContext.getSession().getUserId();
+        Long userId = SessionContext.getSession().getUserId();
         // 互相解除好友关系,走代理清理缓存
-        FriendServiceImpl proxy = (FriendServiceImpl) AopContext.currentProxy();
+        FriendServiceImpl proxy = (FriendServiceImpl)AopContext.currentProxy();
         proxy.unbindFriend(userId, friendId);
         proxy.unbindFriend(friendId, userId);
+        // 推送解除好友提示
+        sendDelTipMessage(friendId);
         log.info("删除好友,用户id:{},好友id:{}", userId, friendId);
     }
 
-
     @Cacheable(key = "#userId1+':'+#userId2")
     @Override
     public Boolean isFriend(Long userId1, Long userId2) {
-        QueryWrapper<Friend> queryWrapper = new QueryWrapper<>();
-        queryWrapper.lambda()
-                .eq(Friend::getUserId, userId1)
-                .eq(Friend::getFriendId, userId2);
-        return this.count(queryWrapper) > 0;
+        LambdaQueryWrapper<Friend> wrapper = Wrappers.lambdaQuery();
+        wrapper.eq(Friend::getUserId, userId1);
+        wrapper.eq(Friend::getFriendId, userId2);
+        wrapper.eq(Friend::getDeleted,false);
+        return this.exists(wrapper);
     }
 
-
-    @Override
-    public void update(FriendVO vo) {
-        long userId = SessionContext.getSession().getUserId();
-        QueryWrapper<Friend> queryWrapper = new QueryWrapper<>();
-        queryWrapper.lambda()
-                .eq(Friend::getUserId, userId)
-                .eq(Friend::getFriendId, vo.getId());
-        Friend f = this.getOne(queryWrapper);
-        if (Objects.isNull(f)) {
-            throw new GlobalException("对方不是您的好友");
-        }
-        f.setFriendHeadImage(vo.getHeadImage());
-        f.setFriendNickName(vo.getNickName());
-        this.updateById(f);
-    }
-
-
     /**
      * 单向绑定好友关系
      *
@@ -105,22 +121,23 @@ public class FriendServiceImpl extends ServiceImpl<FriendMapper, Friend> impleme
      */
     @CacheEvict(key = "#userId+':'+#friendId")
     public void bindFriend(Long userId, Long friendId) {
-        QueryWrapper<Friend> queryWrapper = new QueryWrapper<>();
-        queryWrapper.lambda()
-                .eq(Friend::getUserId, userId)
-                .eq(Friend::getFriendId, friendId);
-        if (this.count(queryWrapper) == 0) {
-            Friend friend = new Friend();
-            friend.setUserId(userId);
-            friend.setFriendId(friendId);
-            User friendInfo = userMapper.selectById(friendId);
-            friend.setFriendHeadImage(friendInfo.getHeadImage());
-            friend.setFriendNickName(friendInfo.getNickName());
-            this.save(friend);
+        QueryWrapper<Friend> wrapper = new QueryWrapper<>();
+        wrapper.lambda().eq(Friend::getUserId, userId).eq(Friend::getFriendId, friendId);
+        Friend friend = this.getOne(wrapper);
+        if (Objects.isNull(friend)) {
+            friend = new Friend();
         }
+        friend.setUserId(userId);
+        friend.setFriendId(friendId);
+        User friendInfo = userMapper.selectById(friendId);
+        friend.setFriendHeadImage(friendInfo.getHeadImage());
+        friend.setFriendNickName(friendInfo.getNickName());
+        friend.setDeleted(false);
+        this.saveOrUpdate(friend);
+        // 推送好友变化信息s
+        sendAddFriendMessage(userId, friendId, friend);
     }
 
-
     /**
      * 单向解除好友关系
      *
@@ -129,30 +146,112 @@ public class FriendServiceImpl extends ServiceImpl<FriendMapper, Friend> impleme
      */
     @CacheEvict(key = "#userId+':'+#friendId")
     public void unbindFriend(Long userId, Long friendId) {
-        QueryWrapper<Friend> queryWrapper = new QueryWrapper<>();
-        queryWrapper.lambda()
-                .eq(Friend::getUserId, userId)
-                .eq(Friend::getFriendId, friendId);
-        List<Friend> friends = this.list(queryWrapper);
-        friends.forEach(friend -> this.removeById(friend.getId()));
+        // 逻辑删除
+        LambdaUpdateWrapper<Friend> wrapper = Wrappers.lambdaUpdate();
+        wrapper.eq(Friend::getUserId, userId);
+        wrapper.eq(Friend::getFriendId, friendId);
+        wrapper.set(Friend::getDeleted,true);
+        this.update(wrapper);
+        // 推送好友变化信息
+        sendDelFriendMessage(userId, friendId);
     }
 
-
     @Override
     public FriendVO findFriend(Long friendId) {
         UserSession session = SessionContext.getSession();
-        QueryWrapper<Friend> wrapper = new QueryWrapper<>();
-        wrapper.lambda()
-                .eq(Friend::getUserId, session.getUserId())
-                .eq(Friend::getFriendId, friendId);
+        LambdaQueryWrapper<Friend> wrapper = Wrappers.lambdaQuery();
+        wrapper.eq(Friend::getUserId, session.getUserId());
+        wrapper.eq(Friend::getFriendId, friendId);
         Friend friend = this.getOne(wrapper);
         if (Objects.isNull(friend)) {
             throw new GlobalException("对方不是您的好友");
         }
+        return conver(friend);
+    }
+
+    private FriendVO conver(Friend f) {
         FriendVO vo = new FriendVO();
-        vo.setId(friend.getFriendId());
-        vo.setHeadImage(friend.getFriendHeadImage());
-        vo.setNickName(friend.getFriendNickName());
+        vo.setId(f.getFriendId());
+        vo.setHeadImage(f.getFriendHeadImage());
+        vo.setNickName(f.getFriendNickName());
+        vo.setDeleted(f.getDeleted());
         return vo;
     }
+
+    void sendAddFriendMessage(Long userId, Long friendId, Friend friend) {
+        // 推送好友状态信息
+        PrivateMessageVO msgInfo = new PrivateMessageVO();
+        msgInfo.setSendId(friendId);
+        msgInfo.setRecvId(userId);
+        msgInfo.setSendTime(new Date());
+        msgInfo.setType(MessageType.FRIEND_NEW.code());
+        FriendVO vo = conver(friend);
+        msgInfo.setContent(JSON.toJSONString(vo));
+        IMPrivateMessage<PrivateMessageVO> sendMessage = new IMPrivateMessage<>();
+        sendMessage.setSender(new IMUserInfo(friendId, IMTerminalType.UNKNOW.code()));
+        sendMessage.setRecvId(userId);
+        sendMessage.setData(msgInfo);
+        sendMessage.setSendToSelf(false);
+        sendMessage.setSendResult(false);
+        imClient.sendPrivateMessage(sendMessage);
+    }
+
+    void sendDelFriendMessage(Long userId, Long friendId) {
+        // 推送好友状态信息
+        PrivateMessageVO msgInfo = new PrivateMessageVO();
+        msgInfo.setSendId(friendId);
+        msgInfo.setRecvId(userId);
+        msgInfo.setSendTime(new Date());
+        msgInfo.setType(MessageType.FRIEND_DEL.code());
+        IMPrivateMessage<PrivateMessageVO> sendMessage = new IMPrivateMessage<>();
+        sendMessage.setSender(new IMUserInfo(friendId, IMTerminalType.UNKNOW.code()));
+        sendMessage.setRecvId(userId);
+        sendMessage.setData(msgInfo);
+        sendMessage.setSendToSelf(false);
+        sendMessage.setSendResult(false);
+        imClient.sendPrivateMessage(sendMessage);
+    }
+
+    void sendAddTipMessage(Long friendId) {
+        UserSession session = SessionContext.getSession();
+        PrivateMessage msg = new PrivateMessage();
+        msg.setSendId(session.getUserId());
+        msg.setRecvId(friendId);
+        msg.setContent("你们已成为好友,现在可以开始聊天了");
+        msg.setSendTime(new Date());
+        msg.setStatus(MessageStatus.UNSEND.code());
+        msg.setType(MessageType.TIP_TEXT.code());
+        privateMessageMapper.insert(msg);
+        // 推给对方
+        PrivateMessageVO messageInfo = BeanUtils.copyProperties(msg, PrivateMessageVO.class);
+        IMPrivateMessage<PrivateMessageVO> sendMessage = new IMPrivateMessage<>();
+        sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal()));
+        sendMessage.setRecvId(friendId);
+        sendMessage.setSendToSelf(false);
+        sendMessage.setData(messageInfo);
+        imClient.sendPrivateMessage(sendMessage);
+        // 推给自己
+        sendMessage.setRecvId(session.getUserId());
+        imClient.sendPrivateMessage(sendMessage);
+    }
+
+    void sendDelTipMessage(Long friendId){
+        UserSession session = SessionContext.getSession();
+        // 推送好友状态信息
+        PrivateMessage msg = new PrivateMessage();
+        msg.setSendId(session.getUserId());
+        msg.setRecvId(friendId);
+        msg.setSendTime(new Date());
+        msg.setType(MessageType.TIP_TEXT.code());
+        msg.setStatus(MessageStatus.UNSEND.code());
+        msg.setContent("你们的好友关系已被解除");
+        privateMessageMapper.insert(msg);
+        // 推送
+        PrivateMessageVO messageInfo = BeanUtils.copyProperties(msg, PrivateMessageVO.class);
+        IMPrivateMessage<PrivateMessageVO> sendMessage = new IMPrivateMessage<>();
+        sendMessage.setSender(new IMUserInfo(friendId, IMTerminalType.UNKNOW.code()));
+        sendMessage.setRecvId(friendId);
+        sendMessage.setData(messageInfo);
+        imClient.sendPrivateMessage(sendMessage);
+    }
 }

+ 0 - 2
im-platform/src/main/java/com/bx/implatform/service/impl/GroupMessageServiceImpl.java

@@ -31,10 +31,8 @@ import com.bx.implatform.session.UserSession;
 import com.bx.implatform.util.BeanUtils;
 import com.bx.implatform.util.SensitiveFilterUtil;
 import com.bx.implatform.vo.GroupMessageVO;
-import com.google.common.base.Splitter;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
-import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.time.DateUtils;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.stereotype.Service;

+ 3 - 5
im-platform/src/main/java/com/bx/implatform/service/impl/GroupServiceImpl.java

@@ -227,14 +227,12 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
             throw new GlobalException("群聊人数不能大于" + 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()).toList();
-        if (friendsList.size() != vo.getFriendIds().size()) {
+        List<Friend> friends = friendsService.findByFriendIds(vo.getFriendIds());
+        if (vo.getFriendIds().size() != friends.size()) {
             throw new GlobalException("部分用户不是您的好友,邀请失败");
         }
         // 批量保存成员数据
-        List<GroupMember> groupMembers = friendsList.stream().map(f -> {
+        List<GroupMember> groupMembers = friends.stream().map(f -> {
             Optional<GroupMember> optional =
                 members.stream().filter(m -> m.getUserId().equals(f.getFriendId())).findFirst();
             GroupMember groupMember = optional.orElseGet(GroupMember::new);

+ 10 - 20
im-platform/src/main/java/com/bx/implatform/service/impl/PrivateMessageServiceImpl.java

@@ -1,6 +1,5 @@
 package com.bx.implatform.service.impl;
 
-import cn.hutool.core.collection.CollectionUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
@@ -12,7 +11,6 @@ import com.bx.imcommon.enums.IMTerminalType;
 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.entity.PrivateMessage;
 import com.bx.implatform.enums.MessageStatus;
 import com.bx.implatform.enums.MessageType;
@@ -31,7 +29,9 @@ import org.apache.commons.lang3.time.DateUtils;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
-import java.util.*;
+import java.util.Date;
+import java.util.List;
+import java.util.Objects;
 import java.util.stream.Collectors;
 
 @Slf4j
@@ -135,29 +135,19 @@ public class PrivateMessageServiceImpl extends ServiceImpl<PrivateMessageMapper,
     @Override
     public void pullOfflineMessage(Long minId) {
         UserSession session = SessionContext.getSession();
-        if (!imClient.isOnline(session.getUserId())) {
-            throw new GlobalException("网络连接失败,无法拉取离线消息");
-        }
-        // 查询用户好友列表
-        List<Friend> friends = friendService.findFriendByUserId(session.getUserId());
-        if (friends.isEmpty()) {
-            // 关闭加载中标志
-            this.sendLoadingMessage(false);
-            return;
-        }
         // 开启加载中标志
         this.sendLoadingMessage(true);
-        List<Long> friendIds = friends.stream().map(Friend::getFriendId).collect(Collectors.toList());
         // 获取当前用户的消息
-        LambdaQueryWrapper<PrivateMessage> queryWrapper = Wrappers.lambdaQuery();
+        LambdaQueryWrapper<PrivateMessage> wrapper = Wrappers.lambdaQuery();
         // 只能拉取最近3个月的消息,移动端只拉取一个月消息
         int months = session.getTerminal().equals(IMTerminalType.APP.code()) ? 1 : 3;
         Date minDate = DateUtils.addMonths(new Date(), -months);
-        queryWrapper.gt(PrivateMessage::getId, minId).ge(PrivateMessage::getSendTime, minDate).and(wrap -> wrap.and(
-                    wp -> wp.eq(PrivateMessage::getSendId, session.getUserId()).in(PrivateMessage::getRecvId, friendIds))
-                .or(wp -> wp.eq(PrivateMessage::getRecvId, session.getUserId()).in(PrivateMessage::getSendId, friendIds)))
-            .orderByAsc(PrivateMessage::getId);
-        List<PrivateMessage> messages = this.list(queryWrapper);
+        wrapper.gt(PrivateMessage::getId, minId);
+        wrapper.ge(PrivateMessage::getSendTime, minDate);
+        wrapper.and(wp -> wp.eq(PrivateMessage::getSendId, session.getUserId()).or()
+            .eq(PrivateMessage::getRecvId, session.getUserId()));
+        wrapper.orderByAsc(PrivateMessage::getId);
+        List<PrivateMessage> messages = this.list(wrapper);
         // 推送消息
         for (PrivateMessage m : messages) {
             PrivateMessageVO vo = BeanUtils.copyProperties(m, PrivateMessageVO.class);

+ 0 - 1
im-platform/src/main/java/com/bx/implatform/task/consumer/GroupBannedConsumerTask.java

@@ -61,7 +61,6 @@ public class GroupBannedConsumerTask extends RedisMQConsumer<GroupBanDTO> {
         IMGroupMessage<GroupMessageVO> sendMessage = new IMGroupMessage<>();
         sendMessage.setSender(new IMUserInfo(Constant.SYS_USER_ID, IMTerminalType.PC.code()));
         sendMessage.setRecvIds(userIds);
-        sendMessage.setSendResult(true);
         sendMessage.setSendToSelf(false);
         sendMessage.setData(msgInfo);
         imClient.sendGroupMessage(sendMessage);

+ 0 - 1
im-platform/src/main/java/com/bx/implatform/task/consumer/GroupUnbanConsumerTask.java

@@ -60,7 +60,6 @@ public class GroupUnbanConsumerTask extends RedisMQConsumer<GroupUnbanDTO> {
         IMGroupMessage<GroupMessageVO> sendMessage = new IMGroupMessage<>();
         sendMessage.setSender(new IMUserInfo(Constant.SYS_USER_ID, IMTerminalType.PC.code()));
         sendMessage.setRecvIds(userIds);
-        sendMessage.setSendResult(true);
         sendMessage.setSendToSelf(false);
         sendMessage.setData(msgInfo);
         imClient.sendGroupMessage(sendMessage);

+ 3 - 0
im-platform/src/main/java/com/bx/implatform/vo/FriendVO.java

@@ -19,4 +19,7 @@ public class FriendVO {
 
     @Schema(description = "好友头像")
     private String headImage;
+
+    @Schema(description = "是否已删除")
+    private Boolean deleted;
 }

+ 0 - 1
im-platform/src/main/resources/application-dev.yml

@@ -8,7 +8,6 @@ spring:
     redis:
       host: localhost
       port: 6379
-      database: 1
 
 minio:
   endpoint: http://127.0.0.1:9000 #内网地址

+ 1 - 3
im-server/src/main/resources/application-dev.yml

@@ -2,6 +2,4 @@ spring:
   data:
     redis:
       host: 127.0.0.1
-      port: 6379
-      database: 1
-
+      port: 6379

+ 20 - 13
im-uniapp/App.vue

@@ -138,10 +138,19 @@ export default {
 				this.chatStore.recallMessage(msg, chatInfo);
 				return;
 			}
+			// 新增好友
+			if (msg.type == this.$enums.MESSAGE_TYPE.FRIEND_NEW) {
+				this.friendStore.addFriend(JSON.parse(msg.content));
+				return;
+			}
+			// 删除好友
+			if (msg.type == this.$enums.MESSAGE_TYPE.FRIEND_DEL) {
+				this.friendStore.removeFriend(friendId);
+				return;
+			}
 			// 消息插入
-			this.loadFriendInfo(friendId, (friend) => {
-				this.insertPrivateMessage(friend, msg);
-			})
+			let friend = this.loadFriendInfo(friendId);
+			this.insertPrivateMessage(friend, msg);
 		},
 		insertPrivateMessage(friend, msg) {
 			// 单人视频信令
@@ -280,17 +289,15 @@ export default {
 		},
 		loadFriendInfo(id, callback) {
 			let friend = this.friendStore.findFriend(id);
-			if (friend) {
-				callback(friend);
-			} else {
-				http({
-					url: `/friend/find/${id}`,
-					method: 'GET'
-				}).then((friend) => {
-					this.friendStore.addFriend(friend);
-					callback(friend)
-				})
+			if (!friend) {
+				console.log("未知用户:", id)
+				friend = {
+					id: id,
+					showNickName: "未知用户",
+					headImage: ""
+				}
 			}
+			return friend;
 		},
 		loadGroupInfo(id, callback) {
 			let group = this.groupStore.findGroup(id);

+ 2 - 0
im-uniapp/common/enums.js

@@ -14,6 +14,8 @@ const MESSAGE_TYPE = {
 	ACT_RT_VOICE: 40,
 	ACT_RT_VIDEO: 41,
 	USER_BANNED: 50,
+	FRIEND_NEW: 80,
+	FRIEND_DEL: 81,
 	RTC_CALL_VOICE: 100,
 	RTC_CALL_VIDEO: 101,
 	RTC_ACCEPT: 102,

+ 26 - 9
im-uniapp/pages/chat/chat-box.vue

@@ -119,7 +119,7 @@ export default {
 	data() {
 		return {
 			chat: {},
-			friend: {},
+			userInfo: {},
 			group: {},
 			groupMembers: [],
 			isReceipt: false, // 是否回执消息
@@ -578,7 +578,7 @@ export default {
 				})
 			} else {
 				uni.navigateTo({
-					url: "/pages/common/user-info?id=" + this.friend.id
+					url: "/pages/common/user-info?id=" + this.userInfo.id
 				})
 			}
 		},
@@ -657,15 +657,29 @@ export default {
 				this.groupMembers = groupMembers;
 			});
 		},
+		updateFriendInfo() {
+			if (this.isFriend) {
+				// store的数据不能直接修改,深拷贝一份store的数据
+				let friend = JSON.parse(JSON.stringify(this.friend));
+				friend.headImage = this.userInfo.headImageThumb;
+				friend.nickName = this.userInfo.nickName;
+				friend.showNickName = friend.remarkNickName ? friend.remarkNickName : friend.nickName;
+				// 更新好友列表中的昵称和头像
+				this.friendStore.updateFriend(friend);
+				// 更新会话中的头像和昵称
+				this.chatStore.updateChatFromFriend(friend);
+			} else {
+				this.chatStore.updateChatFromUser(this.userInfo);
+			}
+		},
 		loadFriend(friendId) {
-			// 获取对方最新信息
+			// 获取好友用户信息
 			this.$http({
 				url: `/user/find/${friendId}`,
 				method: 'GET'
-			}).then((friend) => {
-				this.friend = friend;
-				this.chatStore.updateChatFromFriend(friend);
-				this.friendStore.updateFriend(friend);
+			}).then((userInfo) => {
+				this.userInfo = userInfo;
+				this.updateFriendInfo();
 			})
 		},
 		rpxTopx(rpx) {
@@ -807,7 +821,7 @@ export default {
 			}
 			if (this.chat.type == "PRIVATE") {
 				msgInfo.recvId = this.mine.id
-				msgInfo.content = "该用户已被管理员封禁,原因:" + this.friend.reason
+				msgInfo.content = "该用户已被管理员封禁,原因:" + this.userInfo.reason
 			} else {
 				msgInfo.groupId = this.group.id;
 				msgInfo.content = "本群聊已被管理员封禁,原因:" + this.group.reason
@@ -823,6 +837,9 @@ export default {
 		mine() {
 			return this.userStore.userInfo;
 		},
+		friend() {
+			return this.friendStore.findFriend(this.userInfo.id);
+		},
 		title() {
 			if (!this.chat) {
 				return "";
@@ -850,7 +867,7 @@ export default {
 			return this.chat.unreadCount;
 		},
 		isBanned() {
-			return (this.chat.type == "PRIVATE" && this.friend.isBanned) ||
+			return (this.chat.type == "PRIVATE" && this.userInfo.isBanned) ||
 				(this.chat.type == "GROUP" && this.group.isBanned)
 		},
 		atUserItems() {

+ 13 - 19
im-uniapp/pages/common/user-info.vue

@@ -82,7 +82,8 @@ export default {
 					id: this.userInfo.id,
 					nickName: this.userInfo.nickName,
 					headImage: this.userInfo.headImageThumb,
-					online: this.userInfo.online
+					online: this.userInfo.online,
+					deleted: false
 				}
 				this.friendStore.addFriend(friend);
 				uni.showToast({
@@ -113,20 +114,17 @@ export default {
 			})
 		},
 		updateFriendInfo() {
-			// store的数据不能直接修改,深拷贝一份store的数据
-			let friend = JSON.parse(JSON.stringify(this.friendInfo));
-			friend.headImage = this.userInfo.headImageThumb;
-			friend.nickName = this.userInfo.nickName;
-			this.$http({
-				url: "/friend/update",
-				method: "PUT",
-				data: friend
-			}).then(() => {
+			if (this.isFriend) {
+				// store的数据不能直接修改,深拷贝一份store的数据
+				let friend = JSON.parse(JSON.stringify(this.friendInfo));
+				friend.headImage = this.userInfo.headImageThumb;
+				friend.nickName = this.userInfo.nickName;
+
 				// 更新好友列表中的昵称和头像
 				this.friendStore.updateFriend(friend);
 				// 更新会话中的头像和昵称
 				this.chatStore.updateChatFromFriend(this.userInfo);
-			})
+			}
 		},
 		loadUserInfo(id) {
 			this.$http({
@@ -135,21 +133,17 @@ export default {
 			}).then((user) => {
 				this.userInfo = user;
 				// 如果发现好友的头像和昵称改了,进行更新
-				if (this.isFriend && (this.userInfo.headImageThumb != this.friendInfo.headImage ||
-					this.userInfo.nickName != this.friendInfo.nickName)) {
-					this.updateFriendInfo()
-				}
+				this.updateFriendInfo()
+
 			})
 		}
 	},
 	computed: {
 		isFriend() {
-			return !!this.friendInfo;
+			return this.friendStore.isFriend(this.userInfo.id);
 		},
 		friendInfo() {
-			let friends = this.friendStore.friends;
-			let friend = friends.find((f) => f.id == this.userInfo.id);
-			return friend;
+			return this.friendStore.findFriend(this.userInfo.id);
 		}
 	},
 	onLoad(options) {

+ 3 - 4
im-uniapp/pages/friend/friend-add.vue

@@ -61,7 +61,8 @@ export default {
 					id: user.id,
 					nickName: user.nickName,
 					headImage: user.headImage,
-					online: user.online
+					online: user.online,
+					delete: false
 				}
 				this.friendStore.addFriend(friend);
 				uni.showToast({
@@ -76,9 +77,7 @@ export default {
 			})
 		},
 		isFriend(userId) {
-			let friends = this.friendStore.friends;
-			let friend = friends.find((f) => f.id == userId);
-			return !!friend;
+			return this.friendStore.isFriend(userId);
 		}
 	}
 }

+ 3 - 6
im-uniapp/pages/friend/friend.vue

@@ -8,7 +8,7 @@
 					placeholder="点击搜索好友"></uni-search-bar>
 			</view>
 		</view>
-		<view class="friend-tip" v-if="friends.length == 0">
+		<view class="friend-tip" v-if="friendIdx.length == 0">
 			温馨提示:您现在还没有任何好友,快点击右上方'+'按钮添加好友吧~
 		</view>
 		<view class="friend-items" v-else>
@@ -56,14 +56,11 @@ export default {
 		}
 	},
 	computed: {
-		friends() {
-			return this.friendStore.friends;
-		},
 		friendGroupMap() {
 			// 按首字母分组
 			let groupMap = new Map();
-			this.friends.forEach((f) => {
-				if (this.searchText && !f.nickName.includes(this.searchText)) {
+			this.friendStore.friends.forEach((f) => {
+				if (f.deleted || (this.searchText && !f.nickName.includes(this.searchText))) {
 					return;
 				}
 				let letter = this.firstLetter(f.nickName).toUpperCase();

+ 1 - 2
im-uniapp/pages/group/group-info.vue

@@ -113,8 +113,7 @@ export default {
 										url: "/pages/group/group"
 									});
 									this.groupStore.removeGroup(this.groupId);
-									this.chatStore.removeGroupChat(this
-										.groupId);
+									this.chatStore.removeGroupChat(this.groupId);
 								}, 100)
 							}
 						})

+ 4 - 4
im-uniapp/pages/group/group-invite.vue

@@ -8,9 +8,9 @@
 		</view>
 		<view class="friend-items">
 			<scroll-view class="scroll-bar" scroll-with-animation="true" scroll-y="true">
-				<view v-for="friend in friendItems" v-show="!searchText || friend.nickName.includes(searchText)"
-					:key="friend.id">
-					<view class="friend-item" @click="onSwitchChecked(friend)"
+				<view v-for="friend in friendItems" :key="friend.id">
+					<view v-show="!searchText || friend.nickName.includes(searchText)" class="friend-item"
+						@click="onSwitchChecked(friend)"
 						:class="{ checked: friend.checked, disabled: friend.disabled }">
 						<head-image :name="friend.nickName" :online="friend.online"
 							:url="friend.headImage"></head-image>
@@ -79,7 +79,7 @@ export default {
 		initFriendItems() {
 			this.friendItems = [];
 			let friends = this.friendStore.friends;
-			friends.forEach((f => {
+			friends.filter(f => !f.deleted).forEach((f => {
 				let item = {
 					id: f.id,
 					headImage: f.headImage,

+ 17 - 4
im-uniapp/store/chatStore.js

@@ -180,7 +180,7 @@ export default defineStore('chatStore', {
 			chat.sendNickName = msgInfo.sendNickName;
 			// 未读加1
 			if (!msgInfo.selfSend && msgInfo.status != MESSAGE_STATUS.READED &&
-				msgInfo.type != MESSAGE_TYPE.TIP_TEXT) {
+				msgInfo.status != MESSAGE_STATUS.RECALL && msgInfo.type != MESSAGE_TYPE.TIP_TEXT) {
 				chat.unreadCount++;
 			}
 			// 是否有人@我
@@ -270,7 +270,9 @@ export default defineStore('chatStore', {
 					chat.lastContent = m.content;
 					chat.lastSendTime = msgInfo.sendTime;
 					chat.sendNickName = '';
-					chat.unreadCount++;
+					if (!msgInfo.selfSend && msgInfo.status != MESSAGE_STATUS.READED) {
+						chat.unreadCount++;
+					}
 				}
 				// 被引用的消息也要撤回
 				if (m.quoteMessage && m.quoteMessage.id == msgInfo.id) {
@@ -284,15 +286,26 @@ export default defineStore('chatStore', {
 		},
 		updateChatFromFriend(friend) {
 			let chat = this.findChatByFriend(friend.id)
-			if (chat && (chat.headImage != friend.headImageThumb ||
+			if (chat && (chat.headImage != friend.headImage ||
 					chat.showName != friend.nickName)) {
 				// 更新会话中的群名和头像
-				chat.headImage = friend.headImageThumb;
+				chat.headImage = friend.headImage;
 				chat.showName = friend.nickName;
 				chat.stored = false;
 				this.saveToStorage();
 			}
 		},
+		updateChatFromUser(user) {
+			let chat = this.findChatByFriend(user.id);
+			// 更新会话中的昵称和头像
+			if (chat && (chat.headImage != user.headImageThumb ||
+					chat.showName != user.nickName)) {
+				chat.headImage = user.headImageThumb;
+				chat.showName = user.nickName;
+				chat.stored = false;
+				this.saveToStorage();
+			}
+		},
 		updateChatFromGroup(group) {
 			let chat = this.findChatByGroup(group.id);
 			if (chat && (chat.headImage != group.headImageThumb ||

+ 18 - 11
im-uniapp/store/friendStore.js

@@ -29,12 +29,16 @@ export default defineStore('friendStore', {
 		removeFriend(id) {
 			this.friends.forEach((f, idx) => {
 				if (f.id == id) {
-					this.friends.splice(idx, 1)
+					this.friends[idx].deleted = true;
 				}
 			})
 		},
 		addFriend(friend) {
-			this.friends.push(friend);
+			if (this.friends.find((f) => f.id == friend.id)) {
+				this.updateFriend(friend)
+			} else {
+				this.friends.unshift(friend);
+			}
 		},
 		setOnlineStatus(onlineTerminals) {
 			this.friends.forEach((f) => {
@@ -51,16 +55,16 @@ export default defineStore('friendStore', {
 			});
 		},
 		refreshOnlineStatus() {
-			if (this.friends.length > 0) {
-				let userIds = [];
-				this.friends.forEach(f => userIds.push(f.id));
-				http({
-					url: '/user/terminal/online?userIds=' + userIds.join(','),
-					method: 'GET'
-				}).then((onlineTerminals) => {
-					this.setOnlineStatus(onlineTerminals);
-				})
+			let userIds = this.friends.filter((f) => !f.deleted).map((f) => f.id);
+			if (userIds.length == 0) {
+				return;
 			}
+			http({
+				url: '/user/terminal/online?userIds=' + userIds.join(','),
+				method: 'GET'
+			}).then((onlineTerminals) => {
+				this.setOnlineStatus(onlineTerminals);
+			})
 			// 30s后重新拉取
 			clearTimeout(this.timer);
 			this.timer = setTimeout(() => {
@@ -88,6 +92,9 @@ export default defineStore('friendStore', {
 		}
 	},
 	getters: {
+		isFriend: (state) => (userId) => {
+			return state.friends.filter((f) => !f.deleted).some((f) => f.id == userId);
+		},
 		findFriend: (state) => (id) => {
 			return state.friends.find((f) => f.id == id);
 		}

+ 2 - 0
im-web/src/api/enums.js

@@ -13,6 +13,8 @@ const MESSAGE_TYPE = {
 	ACT_RT_VOICE: 40,
 	ACT_RT_VIDEO: 41,
 	USER_BANNED: 50,
+	FRIEND_NEW: 80,
+	FRIEND_DEL: 81,
 	RTC_CALL_VOICE: 100,
 	RTC_CALL_VIDEO: 101,
 	RTC_ACCEPT: 102,

+ 27 - 9
im-web/src/components/chat/ChatBox.vue

@@ -117,7 +117,7 @@ export default {
 	},
 	data() {
 		return {
-			friend: {},
+			userInfo: {},
 			group: {},
 			groupMembers: [],
 			sendImageUrl: "",
@@ -540,15 +540,27 @@ export default {
 				this.groupMembers = groupMembers;
 			});
 		},
+		updateFriendInfo() {
+			if (this.isFriend) {
+				// store的数据不能直接修改,深拷贝一份store的数据
+				let friend = JSON.parse(JSON.stringify(this.friend));
+				friend.headImage = this.userInfo.headImageThumb;
+				friend.nickName = this.userInfo.nickName;
+				friend.showNickName = friend.remarkNickName ? friend.remarkNickName : friend.nickName;
+				this.$store.commit("updateChatFromFriend", friend);
+				this.$store.commit("updateFriend", friend);
+			}else {
+				this.$store.commit("updateChatFromUser", this.userInfo);
+			}
+		},
 		loadFriend(friendId) {
-			// 获取对方最新信息
+			// 获取好友信息
 			this.$http({
 				url: `/user/find/${friendId}`,
-				method: 'get'
-			}).then((friend) => {
-				this.friend = friend;
-				this.$store.commit("updateChatFromFriend", friend);
-				this.$store.commit("updateFriend", friend);
+				method: 'GET'
+			}).then((userInfo) => {
+				this.userInfo = userInfo;
+				this.updateFriendInfo();
 			})
 		},
 		showName(msgInfo) {
@@ -624,7 +636,7 @@ export default {
 			}
 			if (this.chat.type == "PRIVATE") {
 				msgInfo.recvId = this.mine.id
-				msgInfo.content = "该用户已被管理员封禁,原因:" + this.friend.reason
+				msgInfo.content = "该用户已被管理员封禁,原因:" + this.userInfo.reason
 			} else {
 				msgInfo.groupId = this.group.id;
 				msgInfo.content = "本群聊已被管理员封禁,原因:" + this.group.reason
@@ -640,6 +652,12 @@ export default {
 		mine() {
 			return this.$store.state.userStore.userInfo;
 		},
+		isFriend() {
+			return this.$store.getters.isFriend(this.userInfo.id);
+		},
+		friend() {
+			return  this.$store.getters.findFriend(this.userInfo.id)
+		},
 		title() {
 			let title = this.chat.showName;
 			if (this.chat.type == "GROUP") {
@@ -661,7 +679,7 @@ export default {
 			return this.chat.messages.length;
 		},
 		isBanned() {
-			return (this.chat.type == "PRIVATE" && this.friend.isBanned) ||
+			return (this.chat.type == "PRIVATE" && this.userInfo.isBanned) ||
 				(this.chat.type == "GROUP" && this.group.isBanned)
 		}
 	},

+ 4 - 5
im-web/src/components/common/UserInfo.vue

@@ -68,13 +68,14 @@ export default {
 				params: {
 					friendId: this.user.id
 				}
-			}).then((data) => {
+			}).then(() => {
 				this.$message.success("添加成功,对方已成为您的好友");
 				let friend = {
 					id: this.user.id,
 					nickName: this.user.nickName,
 					headImage: this.user.headImageThumb,
-					online: this.user.online
+					online: this.user.online,
+					deleted: false
 				}
 				this.$store.commit("addFriend", friend);
 			})
@@ -87,9 +88,7 @@ export default {
 	},
 	computed: {
 		isFriend() {
-			let friends = this.$store.state.friendStore.friends;
-			let friend = friends.find((f) => f.id == this.user.id);
-			return friend != undefined;
+			return this.$store.getters.isFriend(this.user.id);
 		}
 	}
 }

+ 4 - 5
im-web/src/components/friend/AddFriend.vue

@@ -74,21 +74,20 @@ export default {
 				params: {
 					friendId: user.id
 				}
-			}).then((data) => {
+			}).then(() => {
 				this.$message.success("添加成功,对方已成为您的好友");
 				let friend = {
 					id: user.id,
 					nickName: user.nickName,
 					headImage: user.headImage,
-					online: user.online
+					online: user.online,
+					deleted: false
 				}
 				this.$store.commit("addFriend", friend);
 			})
 		},
 		isFriend(userId) {
-			let friends = this.$store.state.friendStore.friends;
-			let friend = friends.find((f) => f.id == userId);
-			return friend != undefined;
+			return this.$store.getters.isFriend(userId);
 		}
 	}
 }

+ 3 - 1
im-web/src/components/group/AddGroupMember.vue

@@ -55,7 +55,6 @@ export default {
       this.$emit("close");
     },
     onOk() {
-
       let inviteVO = {
         groupId: this.groupId,
         friendIds: []
@@ -107,6 +106,9 @@ export default {
       if (newData) {
         this.friends = [];
         this.$store.state.friendStore.friends.forEach((f) => {
+          if (f.deleted) {
+            return;
+          }
           let friend = JSON.parse(JSON.stringify(f))
           let m = this.members.filter((m) => !m.quit)
             .find((m) => m.userId == f.id);

+ 15 - 5
im-web/src/store/chatStore.js

@@ -175,7 +175,7 @@ export default {
 			chat.sendNickName = msgInfo.sendNickName;
 			// 未读加1
 			if (!msgInfo.selfSend && msgInfo.status != MESSAGE_STATUS.READED &&
-				msgInfo.type != MESSAGE_TYPE.TIP_TEXT) {
+				msgInfo.status != MESSAGE_STATUS.RECALL && msgInfo.type != MESSAGE_TYPE.TIP_TEXT) {
 				chat.unreadCount++;
 			}
 			// 是否有人@我
@@ -258,7 +258,7 @@ export default {
 					chat.lastContent = m.content;
 					chat.lastSendTime = msgInfo.sendTime;
 					chat.sendNickName = '';
-					if(!msgInfo.selfSend){
+					if (!msgInfo.selfSend && msgInfo.status != MESSAGE_STATUS.READED) {
 						chat.unreadCount++;
 					}
 				}
@@ -275,14 +275,25 @@ export default {
 		updateChatFromFriend(state, friend) {
 			let chat = this.getters.findChatByFriend(friend.id);
 			// 更新会话中的群名和头像
-			if (chat && (chat.headImage != friend.headImageThumb ||
+			if (chat && (chat.headImage != friend.headImage ||
 					chat.showName != friend.nickName)) {
-				chat.headImage = friend.headImageThumb;
+				chat.headImage = friend.headImage;
 				chat.showName = friend.nickName;
 				chat.stored = false;
 				this.commit("saveToStorage")
 			}
 		},
+		updateChatFromUser(user) {
+			let chat = this.getters.findChatByFriend(user.id);
+			// 更新会话中的昵称和头像
+			if (chat && (chat.headImage != user.headImageThumb ||
+					chat.showName != user.nickName)) {
+				chat.headImage = user.headImageThumb;
+				chat.showName = user.nickName;
+				chat.stored = false;
+				this.saveToStorage();
+			}
+		},
 		updateChatFromGroup(state, group) {
 			let chat = this.getters.findChatByGroup(group.id);
 			if (chat && (chat.headImage != group.headImageThumb ||
@@ -333,7 +344,6 @@ export default {
 				// 只存储有改动的会话
 				let chatKey = `${key}-${chat.type}-${chat.targetId}`
 				if (!chat.stored) {
-					console.log(chatKey)
 					if (chat.delete) {
 						localForage.removeItem(chatKey);
 					} else {

+ 30 - 11
im-web/src/store/friendStore.js

@@ -28,23 +28,35 @@ export default {
 			})
 		},
 		activeFriend(state, idx) {
-			state.activeFriend = state.friends[idx];
-		},
-		removeFriend(state, idx) {
-			if (state.friends[idx] == state.activeFriend) {
+			if (idx < 0) {
 				state.activeFriend = null;
+			} else {
+				state.activeFriend = state.friends[idx];
+			}
+		},
+		removeFriend(state, id) {
+			for (let idx in state.friends) {
+				if (id && state.friends[idx].id == id) {
+					state.friends[idx].deleted = true;
+					if (state.friends[idx] == state.activeFriend) {
+						state.activeFriend = null;
+					}
+					return;
+				}
 			}
-			state.friends.splice(idx, 1);
 		},
 		addFriend(state, friend) {
-			state.friends.push(friend);
+			if (state.friends.find((f) => f.id == friend.id)) {
+				this.commit("updateFriend", friend)
+			} else {
+				state.friends.unshift(friend);
+			}
 		},
 		refreshOnlineStatus(state) {
-			let userIds = [];
-			if (state.friends.length == 0) {
+			let userIds = state.friends.filter((f) => !f.deleted).map((f) => f.id);
+			if (userIds.length == 0) {
 				return;
 			}
-			state.friends.forEach((f) => { userIds.push(f.id) });
 			http({
 				url: '/user/terminal/online',
 				method: 'get',
@@ -52,7 +64,6 @@ export default {
 			}).then((onlineTerminals) => {
 				this.commit("setOnlineStatus", onlineTerminals);
 			})
-
 			// 30s后重新拉取
 			state.timer && clearTimeout(state.timer);
 			state.timer = setTimeout(() => {
@@ -100,10 +111,18 @@ export default {
 					context.commit("setFriends", friends);
 					context.commit("refreshOnlineStatus");
 					resolve()
-				}).catch((res) => {
+				}).catch(() => {
 					reject();
 				})
 			});
 		}
+	},
+	getters: {
+		isFriend: (state) => (userId) => {
+			return state.friends.filter((f)=>!f.deleted).some((f)=>f.id == userId);
+		},
+		findFriend: (state) => (userId) => {
+			return state.friends.find((f)=>f.id == userId);
+		}
 	}
 }

+ 26 - 30
im-web/src/view/Friend.vue

@@ -11,9 +11,9 @@
 			</div>
 			<el-scrollbar class="friend-list-items">
 				<div v-for="(friend, index) in $store.state.friendStore.friends" :key="index">
-					<friend-item v-show="friend.nickName.includes(searchText)" :index="index"
+					<friend-item  v-if="!friend.deleted" v-show="friend.nickName.includes(searchText)" :index="index"
 						:active="friend === $store.state.friendStore.activeFriend" @chat="onSendMessage(friend)"
-						@delete="onDelItem(friend, index)" @click.native="onActiveItem(friend, index)">
+						@delete="onDelItem(friend)" @click.native="onActiveItem(friend, index)">
 					</friend-item>
 				</div>
 			</el-scrollbar>
@@ -72,7 +72,8 @@ export default {
 			searchText: "",
 			showAddFriend: false,
 			activeIdx: -1,
-			userInfo: {}
+			userInfo: {},
+			friend: {}
 		}
 	},
 	methods: {
@@ -85,9 +86,10 @@ export default {
 		onActiveItem(friend, idx) {
 			this.$store.commit("activeFriend", idx);
 			this.activeIdx = idx
-			this.loadUserInfo(friend, idx);
+			this.friend = friend;
+			this.loadUserInfo(friend.id);
 		},
-		onDelItem(friend, idx) {
+		onDelItem(friend) {
 			this.$confirm(`确认删除'${friend.nickName}',并清空聊天记录吗?`, '确认解除?', {
 				confirmButtonText: '确定',
 				cancelButtonText: '取消',
@@ -96,9 +98,9 @@ export default {
 				this.$http({
 					url: `/friend/delete/${friend.id}`,
 					method: 'delete'
-				}).then((data) => {
+				}).then(() => {
 					this.$message.success("删除好友成功");
-					this.$store.commit("removeFriend", idx);
+					this.$store.commit("removeFriend", friend.id);
 					this.$store.commit("removePrivateChat", friend.id);
 				})
 			})
@@ -110,7 +112,7 @@ export default {
 				params: {
 					friendId: user.id
 				}
-			}).then((data) => {
+			}).then(() => {
 				this.$message.success("添加成功,对方已成为您的好友");
 				let friend = {
 					id: user.id,
@@ -128,6 +130,7 @@ export default {
 				showName: user.nickName,
 				headImage: user.headImageThumb,
 			};
+			console.log("chat:",chat)
 			this.$store.commit("openChat", chat);
 			this.$store.commit("activeChat", 0);
 			this.$router.push("/home/chat");
@@ -137,31 +140,24 @@ export default {
 				this.$store.commit('showFullImageBox', this.userInfo.headImage);
 			}
 		},
-		updateFriendInfo(friend, user, index) {
-			// store的数据不能直接修改,深拷贝一份store的数据
-			friend = JSON.parse(JSON.stringify(friend));
-			friend.headImage = user.headImageThumb;
-			friend.nickName = user.nickName;
-			this.$http({
-				url: "/friend/update",
-				method: "put",
-				data: friend
-			}).then(() => {
+		updateFriendInfo() {
+			if (this.isFriend) {
+				// store的数据不能直接修改,深拷贝一份store的数据
+				let friend = JSON.parse(JSON.stringify(this.friend));
+				friend.headImage = this.userInfo.headImageThumb;
+				friend.nickName = this.userInfo.nickName;
+				this.$store.commit("updateChatFromFriend", friend);
 				this.$store.commit("updateFriend", friend);
-				this.$store.commit("updateChatFromFriend", user);
-			})
+			}
 		},
-		loadUserInfo(friend, index) {
+		loadUserInfo(id) {
+			// 获取好友用户信息
 			this.$http({
-				url: `/user/find/${friend.id}`,
-				method: 'get'
-			}).then((user) => {
-				this.userInfo = user;
-				// 如果发现好友的头像和昵称改了,进行更新
-				if (user.headImageThumb != friend.headImage ||
-					user.nickName != friend.nickName) {
-					this.updateFriendInfo(friend, user, index)
-				}
+				url: `/user/find/${id}`,
+				method: 'GET'
+			}).then((userInfo) => {
+				this.userInfo = userInfo;
+				this.updateFriendInfo();
 			})
 		}
 	},

+ 21 - 17
im-web/src/view/Home.vue

@@ -189,18 +189,27 @@ export default {
 				this.$store.commit("recallMessage", [msg, chatInfo])
 				return;
 			}
+			// 新增好友
+			if (msg.type == this.$enums.MESSAGE_TYPE.FRIEND_NEW) {
+				this.$store.commit("addFriend", JSON.parse(msg.content));
+				return;
+			}
+			// 删除好友
+			if (msg.type == this.$enums.MESSAGE_TYPE.FRIEND_DEL) {
+				this.$store.commit("removeFriend", friendId);
+				return;
+			}
 			// 单人webrtc 信令
 			if (this.$msgType.isRtcPrivate(msg.type)) {
 				this.$refs.rtcPrivateVideo.onRTCMessage(msg)
 				return;
 			}
 			// 好友id
-			this.loadFriendInfo(friendId).then((friend) => {
-				this.insertPrivateMessage(friend, msg);
-			})
+			let friend = this.loadFriendInfo(friendId);
+			this.insertPrivateMessage(friend, msg);
+
 		},
 		insertPrivateMessage(friend, msg) {
-
 			let chatInfo = {
 				type: 'PRIVATE',
 				targetId: friend.id,
@@ -322,20 +331,15 @@ export default {
 			this.showSettingDialog = false;
 		},
 		loadFriendInfo(id) {
-			return new Promise((resolve, reject) => {
-				let friend = this.$store.state.friendStore.friends.find((f) => f.id == id);
-				if (friend) {
-					resolve(friend);
-				} else {
-					this.$http({
-						url: `/friend/find/${id}`,
-						method: 'get'
-					}).then((friend) => {
-						this.$store.commit("addFriend", friend);
-						resolve(friend)
-					})
+			let friend = this.$store.state.friendStore.friends.find((f) => f.id == id);
+			if (!friend) {
+				friend = {
+					id: id,
+					showNickName: "未知用户",
+					headImage: ""
 				}
-			});
+			}
+			return friend;
 		},
 		loadGroupInfo(id) {
 			return new Promise((resolve, reject) => {