diff --git a/im-common/src/main/java/com/bx/imcommon/enums/IMTerminalType.java b/im-common/src/main/java/com/bx/imcommon/enums/IMTerminalType.java index 3fd3107..d5f5fac 100644 --- a/im-common/src/main/java/com/bx/imcommon/enums/IMTerminalType.java +++ b/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; diff --git a/im-platform/src/main/java/com/bx/implatform/config/MinIoClientConfig.java b/im-platform/src/main/java/com/bx/implatform/config/MinIoClientConfig.java index 16017b2..ca661b8 100644 --- a/im-platform/src/main/java/com/bx/implatform/config/MinIoClientConfig.java +++ b/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; diff --git a/im-platform/src/main/java/com/bx/implatform/contant/Constant.java b/im-platform/src/main/java/com/bx/implatform/contant/Constant.java index 08e4125..1495851 100644 --- a/im-platform/src/main/java/com/bx/implatform/contant/Constant.java +++ b/im-platform/src/main/java/com/bx/implatform/contant/Constant.java @@ -14,9 +14,15 @@ public final class Constant { * 最大上传文件大小 */ public static final Long MAX_FILE_SIZE = 20 * 1024 * 1024L; + + /** + * 大群人数上限 + */ + public static final Long MAX_LARGE_GROUP_MEMBER = 10000L; + /** - * 群聊最大人数 + * 普通群人数上限 */ - public static final Long MAX_GROUP_MEMBER = 500L; + public static final Long MAX_NORMAL_GROUP_MEMBER = 500L; } diff --git a/im-platform/src/main/java/com/bx/implatform/controller/FriendController.java b/im-platform/src/main/java/com/bx/implatform/controller/FriendController.java index c16f692..1d3236a 100644 --- a/im-platform/src/main/java/com/bx/implatform/controller/FriendController.java +++ b/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> findFriends() { - List friends = friendService.findFriendByUserId(SessionContext.getSession().getUserId()); - List 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(); - } - - } diff --git a/im-platform/src/main/java/com/bx/implatform/controller/GroupController.java b/im-platform/src/main/java/com/bx/implatform/controller/GroupController.java index dc0557b..98d3dc8 100644 --- a/im-platform/src/main/java/com/bx/implatform/controller/GroupController.java +++ b/im-platform/src/main/java/com/bx/implatform/controller/GroupController.java @@ -31,12 +31,14 @@ public class GroupController { return ResultUtils.success(groupService.createGroup(vo)); } + @RepeatSubmit @Operation(summary = "修改群聊信息", description = "修改群聊信息") @PutMapping("/modify") public Result modifyGroup(@Valid @RequestBody GroupVO vo) { return ResultUtils.success(groupService.modifyGroup(vo)); } + @RepeatSubmit @Operation(summary = "解散群聊", description = "解散群聊") @DeleteMapping("/delete/{groupId}") public Result deleteGroup(@NotNull(message = "群聊id不能为空") @PathVariable Long groupId) { @@ -44,6 +46,7 @@ public class GroupController { return ResultUtils.success(); } + @Operation(summary = "查询群聊", description = "查询单个群聊信息") @GetMapping("/find/{groupId}") public Result findGroup(@NotNull(message = "群聊id不能为空") @PathVariable Long groupId) { @@ -56,6 +59,7 @@ public class GroupController { return ResultUtils.success(groupService.findGroups()); } + @RepeatSubmit @Operation(summary = "邀请进群", description = "邀请好友进群") @PostMapping("/invite") public Result invite(@Valid @RequestBody GroupInviteVO vo) { @@ -70,6 +74,7 @@ public class GroupController { return ResultUtils.success(groupService.findGroupMembers(groupId)); } + @RepeatSubmit @Operation(summary = "退出群聊", description = "退出群聊") @DeleteMapping("/quit/{groupId}") public Result quitGroup(@NotNull(message = "群聊id不能为空") @PathVariable Long groupId) { @@ -77,6 +82,7 @@ public class GroupController { return ResultUtils.success(); } + @RepeatSubmit @Operation(summary = "踢出群聊", description = "将用户踢出群聊") @DeleteMapping("/kick/{groupId}") public Result kickGroup(@NotNull(message = "群聊id不能为空") @PathVariable Long groupId, diff --git a/im-platform/src/main/java/com/bx/implatform/controller/GroupMessageController.java b/im-platform/src/main/java/com/bx/implatform/controller/GroupMessageController.java index 007a3de..a1c5845 100644 --- a/im-platform/src/main/java/com/bx/implatform/controller/GroupMessageController.java +++ b/im-platform/src/main/java/com/bx/implatform/controller/GroupMessageController.java @@ -30,9 +30,8 @@ public class GroupMessageController { @DeleteMapping("/recall/{id}") @Operation(summary = "撤回消息", description = "撤回群聊消息") - public Result recallMessage(@NotNull(message = "消息id不能为空") @PathVariable Long id) { - groupMessageService.recallMessage(id); - return ResultUtils.success(); + public Result recallMessage(@NotNull(message = "消息id不能为空") @PathVariable Long id) { + return ResultUtils.success(groupMessageService.recallMessage(id)); } @GetMapping("/pullOfflineMessage") diff --git a/im-platform/src/main/java/com/bx/implatform/controller/LoginController.java b/im-platform/src/main/java/com/bx/implatform/controller/LoginController.java index 0f6e242..36a7a43 100644 --- a/im-platform/src/main/java/com/bx/implatform/controller/LoginController.java +++ b/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; diff --git a/im-platform/src/main/java/com/bx/implatform/controller/PrivateMessageController.java b/im-platform/src/main/java/com/bx/implatform/controller/PrivateMessageController.java index 622c5dc..19536a7 100644 --- a/im-platform/src/main/java/com/bx/implatform/controller/PrivateMessageController.java +++ b/im-platform/src/main/java/com/bx/implatform/controller/PrivateMessageController.java @@ -30,9 +30,8 @@ public class PrivateMessageController { @DeleteMapping("/recall/{id}") @Operation(summary = "撤回消息", description = "撤回私聊消息") - public Result recallMessage(@NotNull(message = "消息id不能为空") @PathVariable Long id) { - privateMessageService.recallMessage(id); - return ResultUtils.success(); + public Result recallMessage(@NotNull(message = "消息id不能为空") @PathVariable Long id) { + return ResultUtils.success( privateMessageService.recallMessage(id)); } @GetMapping("/pullOfflineMessage") diff --git a/im-platform/src/main/java/com/bx/implatform/controller/WebrtcPrivateController.java b/im-platform/src/main/java/com/bx/implatform/controller/WebrtcPrivateController.java index 7911999..2ce13b4 100644 --- a/im-platform/src/main/java/com/bx/implatform/controller/WebrtcPrivateController.java +++ b/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; diff --git a/im-platform/src/main/java/com/bx/implatform/entity/Friend.java b/im-platform/src/main/java/com/bx/implatform/entity/Friend.java index f869b81..c7451f9 100644 --- a/im-platform/src/main/java/com/bx/implatform/entity/Friend.java +++ b/im-platform/src/main/java/com/bx/implatform/entity/Friend.java @@ -45,6 +45,11 @@ public class Friend{ */ private String friendHeadImage; + /** + * 是否已删除 + */ + private Boolean deleted; + /** * 创建时间 */ diff --git a/im-platform/src/main/java/com/bx/implatform/enums/MessageType.java b/im-platform/src/main/java/com/bx/implatform/enums/MessageType.java index 7a69c2c..6d12868 100644 --- a/im-platform/src/main/java/com/bx/implatform/enums/MessageType.java +++ b/im-platform/src/main/java/com/bx/implatform/enums/MessageType.java @@ -10,6 +10,8 @@ import lombok.AllArgsConstructor; * 30-39: UI交互类消息: 显示加载状态等 * 40-49: 操作交互类消息: 语音通话、视频通话消息等 * 50-60: 后台操作类消息: 用户封禁、群组封禁等 + * 80-89: 好友变化消息 + * 90-99: 群聊变化消息 * 100-199: 单人语音通话rtc信令 * 200-299: 多人语音通话rtc信令 * @@ -33,6 +35,10 @@ public enum MessageType { USER_BANNED(50,"用户封禁"), GROUP_BANNED(51,"群聊封禁"), GROUP_UNBAN(52,"群聊解封"), + FRIEND_NEW(80, "新增好友"), + FRIEND_DEL(81, "删除好友"), + GROUP_NEW(90, "新增群聊"), + GROUP_DEL(91, "删除群聊"), RTC_CALL_VOICE(100, "语音呼叫"), RTC_CALL_VIDEO(101, "视频呼叫"), RTC_ACCEPT(102, "接受"), diff --git a/im-platform/src/main/java/com/bx/implatform/listener/PrivateMessageListener.java b/im-platform/src/main/java/com/bx/implatform/listener/PrivateMessageListener.java index 6417274..b59db7a 100644 --- a/im-platform/src/main/java/com/bx/implatform/listener/PrivateMessageListener.java +++ b/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 for(IMSendResult 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()); } diff --git a/im-platform/src/main/java/com/bx/implatform/service/FriendService.java b/im-platform/src/main/java/com/bx/implatform/service/FriendService.java index 4fdf32f..70088a4 100644 --- a/im-platform/src/main/java/com/bx/implatform/service/FriendService.java +++ b/im-platform/src/main/java/com/bx/implatform/service/FriendService.java @@ -17,14 +17,27 @@ public interface FriendService extends IService { */ Boolean isFriend(Long userId1, Long userId2); + /** + * 查询用户的所有好友,包括已删除的 + * + * @return 好友列表 + */ + List findAllFriends(); /** * 查询用户的所有好友 * - * @param userId 用户id + * @param friendIds 好友id + * @return 好友列表 + */ + List findByFriendIds(List friendIds); + + /** + * 查询当前用户的所有好友 + * * @return 好友列表 */ - List findFriendByUserId(Long userId); + List findFriends(); /** * 添加好友,互相建立好友关系 @@ -41,17 +54,20 @@ public interface FriendService extends IService { 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); + +} \ No newline at end of file diff --git a/im-platform/src/main/java/com/bx/implatform/service/GroupMessageService.java b/im-platform/src/main/java/com/bx/implatform/service/GroupMessageService.java index dc4efda..1a3ed38 100644 --- a/im-platform/src/main/java/com/bx/implatform/service/GroupMessageService.java +++ b/im-platform/src/main/java/com/bx/implatform/service/GroupMessageService.java @@ -22,7 +22,7 @@ public interface GroupMessageService extends IService { * * @param id 消息id */ - void recallMessage(Long id); + GroupMessageVO recallMessage(Long id); /** * 拉取离线消息,只能拉取最近1个月的消息,最多拉取1000条 diff --git a/im-platform/src/main/java/com/bx/implatform/service/PrivateMessageService.java b/im-platform/src/main/java/com/bx/implatform/service/PrivateMessageService.java index 261a5e0..8ebed75 100644 --- a/im-platform/src/main/java/com/bx/implatform/service/PrivateMessageService.java +++ b/im-platform/src/main/java/com/bx/implatform/service/PrivateMessageService.java @@ -23,7 +23,7 @@ public interface PrivateMessageService extends IService { * * @param id 消息id */ - void recallMessage(Long id); + PrivateMessageVO recallMessage(Long id); /** * 拉取历史聊天记录 diff --git a/im-platform/src/main/java/com/bx/implatform/service/SensitiveWordService.java b/im-platform/src/main/java/com/bx/implatform/service/SensitiveWordService.java index f6a4b86..2a4f099 100644 --- a/im-platform/src/main/java/com/bx/implatform/service/SensitiveWordService.java +++ b/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; diff --git a/im-platform/src/main/java/com/bx/implatform/service/impl/FriendServiceImpl.java b/im-platform/src/main/java/com/bx/implatform/service/impl/FriendServiceImpl.java index c67a0d0..a302596 100644 --- a/im-platform/src/main/java/com/bx/implatform/service/impl/FriendServiceImpl.java +++ b/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 implements FriendService { + private final PrivateMessageMapper privateMessageMapper; private final UserMapper userMapper; + private final IMClient imClient; @Override - public List findFriendByUserId(Long userId) { - LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery(); - queryWrapper.eq(Friend::getUserId, userId); - return this.list(queryWrapper); + public List findAllFriends() { + Long userId = SessionContext.getSession().getUserId(); + LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); + wrapper.eq(Friend::getUserId, userId); + return this.list(wrapper); } + @Override + public List findByFriendIds(List friendIds) { + Long userId = SessionContext.getSession().getUserId(); + LambdaQueryWrapper 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 findFriends() { + List 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 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 queryWrapper = new QueryWrapper<>(); - queryWrapper.lambda() - .eq(Friend::getUserId, userId1) - .eq(Friend::getFriendId, userId2); - return this.count(queryWrapper) > 0; + LambdaQueryWrapper 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 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 impleme */ @CacheEvict(key = "#userId+':'+#friendId") public void bindFriend(Long userId, Long friendId) { - QueryWrapper 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 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 impleme */ @CacheEvict(key = "#userId+':'+#friendId") public void unbindFriend(Long userId, Long friendId) { - QueryWrapper queryWrapper = new QueryWrapper<>(); - queryWrapper.lambda() - .eq(Friend::getUserId, userId) - .eq(Friend::getFriendId, friendId); - List friends = this.list(queryWrapper); - friends.forEach(friend -> this.removeById(friend.getId())); + // 逻辑删除 + LambdaUpdateWrapper 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 wrapper = new QueryWrapper<>(); - wrapper.lambda() - .eq(Friend::getUserId, session.getUserId()) - .eq(Friend::getFriendId, friendId); + LambdaQueryWrapper 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 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 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 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 sendMessage = new IMPrivateMessage<>(); + sendMessage.setSender(new IMUserInfo(friendId, IMTerminalType.UNKNOW.code())); + sendMessage.setRecvId(friendId); + sendMessage.setData(messageInfo); + imClient.sendPrivateMessage(sendMessage); + } } diff --git a/im-platform/src/main/java/com/bx/implatform/service/impl/GroupMessageServiceImpl.java b/im-platform/src/main/java/com/bx/implatform/service/impl/GroupMessageServiceImpl.java index 66c0c88..5aa5857 100644 --- a/im-platform/src/main/java/com/bx/implatform/service/impl/GroupMessageServiceImpl.java +++ b/im-platform/src/main/java/com/bx/implatform/service/impl/GroupMessageServiceImpl.java @@ -14,6 +14,7 @@ import com.bx.imcommon.enums.IMTerminalType; import com.bx.imcommon.model.IMGroupMessage; import com.bx.imcommon.model.IMUserInfo; import com.bx.imcommon.util.CommaTextUtils; +import com.bx.implatform.contant.Constant; import com.bx.implatform.contant.RedisKey; import com.bx.implatform.dto.GroupMessageDTO; import com.bx.implatform.entity.Group; @@ -31,13 +32,12 @@ 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; +import org.springframework.transaction.annotation.Transactional; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; @@ -65,6 +65,10 @@ public class GroupMessageServiceImpl extends ServiceImpl userIds = groupMemberService.findUserIdsByGroupId(group.getId()); + if (dto.getReceipt() && userIds.size() > Constant.MAX_LARGE_GROUP_MEMBER) { + // 大群的回执消息过于消耗资源,不允许发送 + throw new GlobalException(String.format("当前群聊大于%s人,不支持发送回执消息", Constant.MAX_LARGE_GROUP_MEMBER)); + } // 不用发给自己 userIds = userIds.stream().filter(id -> !session.getUserId().equals(id)).collect(Collectors.toList()); // 保存消息 @@ -92,8 +96,9 @@ public class GroupMessageServiceImpl extends ServiceImpl userIds = groupMemberService.findUserIdsByGroupId(msg.getGroupId()); - // 不用发给自己 - userIds = userIds.stream().filter(uid -> !session.getUserId().equals(uid)).collect(Collectors.toList()); - GroupMessageVO msgInfo = BeanUtils.copyProperties(msg, GroupMessageVO.class); - msgInfo.setType(MessageType.RECALL.code()); - String content = String.format("'%s'撤回了一条消息", member.getShowNickName()); - msgInfo.setContent(content); - msgInfo.setSendTime(new Date()); - + GroupMessageVO msgInfo = BeanUtils.copyProperties(recallMsg, GroupMessageVO.class); IMGroupMessage sendMessage = new IMGroupMessage<>(); sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal())); sendMessage.setRecvIds(userIds); sendMessage.setData(msgInfo); - sendMessage.setSendResult(false); - sendMessage.setSendToSelf(false); - imClient.sendGroupMessage(sendMessage); - - // 推给自己其他终端 - msgInfo.setContent("你撤回了一条消息"); - sendMessage.setSendToSelf(true); - sendMessage.setRecvIds(Collections.emptyList()); - sendMessage.setRecvTerminals(Collections.emptyList()); imClient.sendGroupMessage(sendMessage); log.info("撤回群聊消息,发送id:{},群聊id:{},内容:{}", session.getUserId(), msg.getGroupId(), msg.getContent()); + return msgInfo; } @@ -165,7 +165,6 @@ public class GroupMessageServiceImpl extends ServiceImpl messages = this.list(wrapper); // 通过群聊对消息进行分组 diff --git a/im-platform/src/main/java/com/bx/implatform/service/impl/GroupServiceImpl.java b/im-platform/src/main/java/com/bx/implatform/service/impl/GroupServiceImpl.java index 1389b89..809b9ac 100644 --- a/im-platform/src/main/java/com/bx/implatform/service/impl/GroupServiceImpl.java +++ b/im-platform/src/main/java/com/bx/implatform/service/impl/GroupServiceImpl.java @@ -2,6 +2,7 @@ package com.bx.implatform.service.impl; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSON; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; @@ -30,6 +31,7 @@ import com.bx.implatform.vo.GroupMessageVO; import com.bx.implatform.vo.GroupVO; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.compress.utils.Lists; import org.springframework.cache.annotation.CacheConfig; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; @@ -69,9 +71,12 @@ public class GroupServiceImpl extends ServiceImpl implements member.setRemarkNickName(vo.getRemarkNickName()); member.setRemarkGroupName(vo.getRemarkGroupName()); groupMemberService.save(member); + GroupVO groupVo = findById(group.getId()); + // 推送同步消息给自己的其他终端 + sendAddGroupMessage(groupVo, Lists.newArrayList(), true); // 返回 log.info("创建群聊,群聊id:{},群聊名称:{}", group.getId(), group.getName()); - return findById(group.getId()); + return groupVo; } @CacheEvict(key = "#vo.getId()") @@ -95,7 +100,7 @@ public class GroupServiceImpl extends ServiceImpl implements this.updateById(group); } log.info("修改群聊,群聊id:{},群聊名称:{}", group.getId(), group.getName()); - return convert(group,member); + return convert(group, member); } @Transactional(rollbackFor = Exception.class) @@ -118,7 +123,10 @@ public class GroupServiceImpl extends ServiceImpl implements String key = StrUtil.join(":", RedisKey.IM_GROUP_READED_POSITION, groupId); redisTemplate.delete(key); // 推送解散群聊提示 - this.sendTipMessage(groupId, userIds, String.format("'%s'解散了群聊", session.getNickName())); + String content = String.format("'%s'解散了群聊", session.getNickName()); + this.sendTipMessage(groupId, userIds, content, true); + // 推送同步消息 + this.sendDelGroupMessage(groupId, userIds, false); log.info("删除群聊,群聊id:{},群聊名称:{}", group.getId(), group.getName()); } @@ -135,7 +143,9 @@ public class GroupServiceImpl extends ServiceImpl implements String key = StrUtil.join(":", RedisKey.IM_GROUP_READED_POSITION, groupId); redisTemplate.opsForHash().delete(key, userId.toString()); // 推送退出群聊提示 - this.sendTipMessage(groupId, List.of(userId), "您已退出群聊"); + this.sendTipMessage(groupId, List.of(userId), "您已退出群聊", false); + // 推送同步消息 + this.sendDelGroupMessage(groupId, Lists.newArrayList(), true); log.info("退出群聊,群聊id:{},群聊名称:{},用户id:{}", group.getId(), group.getName(), userId); } @@ -155,7 +165,9 @@ public class GroupServiceImpl extends ServiceImpl implements String key = StrUtil.join(":", RedisKey.IM_GROUP_READED_POSITION, groupId); redisTemplate.opsForHash().delete(key, userId.toString()); // 推送踢出群聊提示 - this.sendTipMessage(groupId, List.of(userId), "您已被移出群聊"); + this.sendTipMessage(groupId, List.of(userId), "您已被移出群聊", false); + // 推送同步消息 + this.sendDelGroupMessage(groupId, List.of(userId), false); log.info("踢出群聊,群聊id:{},群聊名称:{},用户id:{}", group.getId(), group.getName(), userId); } @@ -170,7 +182,7 @@ public class GroupServiceImpl extends ServiceImpl implements if (Objects.isNull(member)) { throw new GlobalException("您未加入群聊"); } - return convert(group,member); + return convert(group, member); } @Cacheable(key = "#groupId") @@ -223,18 +235,16 @@ public class GroupServiceImpl extends ServiceImpl implements // 群聊人数校验 List 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("群聊人数不能大于" + Constant.MAX_GROUP_MEMBER + "人"); + if (vo.getFriendIds().size() + size > Constant.MAX_LARGE_GROUP_MEMBER) { + throw new GlobalException("群聊人数不能大于" + Constant.MAX_LARGE_GROUP_MEMBER + "人"); } // 找出好友信息 - List friends = friendsService.findFriendByUserId(session.getUserId()); - List friendsList = vo.getFriendIds().stream() - .map(id -> friends.stream().filter(f -> f.getFriendId().equals(id)).findFirst().get()).toList(); - if (friendsList.size() != vo.getFriendIds().size()) { + List friends = friendsService.findByFriendIds(vo.getFriendIds()); + if (vo.getFriendIds().size() != friends.size()) { throw new GlobalException("部分用户不是您的好友,邀请失败"); } // 批量保存成员数据 - List groupMembers = friendsList.stream().map(f -> { + List groupMembers = friends.stream().map(f -> { Optional optional = members.stream().filter(m -> m.getUserId().equals(f.getFriendId())).findFirst(); GroupMember groupMember = optional.orElseGet(GroupMember::new); @@ -249,11 +259,16 @@ public class GroupServiceImpl extends ServiceImpl implements if (!groupMembers.isEmpty()) { groupMemberService.saveOrUpdateBatch(group.getId(), groupMembers); } + // 推送同步消息给被邀请人 + for (GroupMember m : groupMembers) { + GroupVO groupVo = convert(group, m); + sendAddGroupMessage(groupVo, List.of(m.getUserId()), false); + } // 推送进入群聊消息 List userIds = groupMemberService.findUserIdsByGroupId(vo.getGroupId()); String memberNames = groupMembers.stream().map(GroupMember::getShowNickName).collect(Collectors.joining(",")); String content = String.format("'%s'邀请'%s'加入了群聊", session.getNickName(), memberNames); - this.sendTipMessage(vo.getGroupId(), userIds, content); + this.sendTipMessage(vo.getGroupId(), userIds, content, true); log.info("邀请进入群聊,群聊id:{},群聊名称:{},被邀请用户id:{}", group.getId(), group.getName(), vo.getFriendIds()); } @@ -273,7 +288,7 @@ public class GroupServiceImpl extends ServiceImpl implements }).sorted((m1, m2) -> m2.getOnline().compareTo(m1.getOnline())).collect(Collectors.toList()); } - private void sendTipMessage(Long groupId, List recvIds, String content) { + private void sendTipMessage(Long groupId, List recvIds, String content, Boolean sendToAll) { UserSession session = SessionContext.getSession(); // 消息入库 GroupMessage message = new GroupMessage(); @@ -284,7 +299,7 @@ public class GroupServiceImpl extends ServiceImpl implements message.setSendNickName(session.getNickName()); message.setGroupId(groupId); message.setSendId(session.getUserId()); - message.setRecvIds(CommaTextUtils.asText(recvIds)); + message.setRecvIds(sendToAll ? "" : CommaTextUtils.asText(recvIds)); groupMessageMapper.insert(message); // 推送 GroupMessageVO msgInfo = BeanUtils.copyProperties(message, GroupMessageVO.class); @@ -312,4 +327,37 @@ public class GroupServiceImpl extends ServiceImpl implements vo.setQuit(member.getQuit()); return vo; } + + private void sendAddGroupMessage(GroupVO group, List recvIds, Boolean sendToSelf) { + UserSession session = SessionContext.getSession(); + GroupMessageVO msgInfo = new GroupMessageVO(); + msgInfo.setContent(JSON.toJSONString(group)); + msgInfo.setType(MessageType.GROUP_NEW.code()); + msgInfo.setSendTime(new Date()); + msgInfo.setGroupId(group.getId()); + msgInfo.setSendId(session.getUserId()); + IMGroupMessage sendMessage = new IMGroupMessage<>(); + sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal())); + sendMessage.setRecvIds(recvIds); + sendMessage.setData(msgInfo); + sendMessage.setSendResult(false); + sendMessage.setSendToSelf(sendToSelf); + imClient.sendGroupMessage(sendMessage); + } + + private void sendDelGroupMessage(Long groupId, List recvIds, Boolean sendToSelf) { + UserSession session = SessionContext.getSession(); + GroupMessageVO msgInfo = new GroupMessageVO(); + msgInfo.setType(MessageType.GROUP_DEL.code()); + msgInfo.setSendTime(new Date()); + msgInfo.setGroupId(groupId); + msgInfo.setSendId(session.getUserId()); + IMGroupMessage sendMessage = new IMGroupMessage<>(); + sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal())); + sendMessage.setRecvIds(recvIds); + sendMessage.setData(msgInfo); + sendMessage.setSendResult(false); + sendMessage.setSendToSelf(sendToSelf); + imClient.sendGroupMessage(sendMessage); + } } diff --git a/im-platform/src/main/java/com/bx/implatform/service/impl/PrivateMessageServiceImpl.java b/im-platform/src/main/java/com/bx/implatform/service/impl/PrivateMessageServiceImpl.java index 8380184..ef5222c 100644 --- a/im-platform/src/main/java/com/bx/implatform/service/impl/PrivateMessageServiceImpl.java +++ b/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 @@ -74,8 +74,9 @@ public class PrivateMessageServiceImpl extends ServiceImpl sendMessage = new IMPrivateMessage<>(); sendMessage.setSender(new IMUserInfo(session.getUserId(), session.getTerminal())); sendMessage.setRecvId(msgInfo.getRecvId()); - sendMessage.setSendToSelf(false); sendMessage.setData(msgInfo); - sendMessage.setSendResult(false); - imClient.sendPrivateMessage(sendMessage); - - // 推给自己其他终端 - msgInfo.setContent("你撤回了一条消息"); - sendMessage.setSendToSelf(true); - sendMessage.setRecvTerminals(Collections.emptyList()); imClient.sendPrivateMessage(sendMessage); log.info("撤回私聊消息,发送id:{},接收id:{},内容:{}", msg.getSendId(), msg.getRecvId(), msg.getContent()); + return msgInfo; } @Override @@ -136,30 +135,19 @@ public class PrivateMessageServiceImpl extends ServiceImpl friends = friendService.findFriendByUserId(session.getUserId()); - if (friends.isEmpty()) { - // 关闭加载中标志 - this.sendLoadingMessage(false); - return; - } // 开启加载中标志 this.sendLoadingMessage(true); - List friendIds = friends.stream().map(Friend::getFriendId).collect(Collectors.toList()); // 获取当前用户的消息 - LambdaQueryWrapper queryWrapper = Wrappers.lambdaQuery(); + LambdaQueryWrapper 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) - .ne(PrivateMessage::getStatus, MessageStatus.RECALL.code()).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 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 messages = this.list(wrapper); // 推送消息 for (PrivateMessage m : messages) { PrivateMessageVO vo = BeanUtils.copyProperties(m, PrivateMessageVO.class); diff --git a/im-platform/src/main/java/com/bx/implatform/task/consumer/GroupBannedConsumerTask.java b/im-platform/src/main/java/com/bx/implatform/task/consumer/GroupBannedConsumerTask.java index 1fc7f57..27c8853 100644 --- a/im-platform/src/main/java/com/bx/implatform/task/consumer/GroupBannedConsumerTask.java +++ b/im-platform/src/main/java/com/bx/implatform/task/consumer/GroupBannedConsumerTask.java @@ -61,7 +61,6 @@ public class GroupBannedConsumerTask extends RedisMQConsumer { IMGroupMessage 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); diff --git a/im-platform/src/main/java/com/bx/implatform/task/consumer/GroupUnbanConsumerTask.java b/im-platform/src/main/java/com/bx/implatform/task/consumer/GroupUnbanConsumerTask.java index e2c99c2..67b3993 100644 --- a/im-platform/src/main/java/com/bx/implatform/task/consumer/GroupUnbanConsumerTask.java +++ b/im-platform/src/main/java/com/bx/implatform/task/consumer/GroupUnbanConsumerTask.java @@ -60,7 +60,6 @@ public class GroupUnbanConsumerTask extends RedisMQConsumer { IMGroupMessage 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); diff --git a/im-platform/src/main/java/com/bx/implatform/vo/FriendVO.java b/im-platform/src/main/java/com/bx/implatform/vo/FriendVO.java index 7b40fb4..e36e6e3 100644 --- a/im-platform/src/main/java/com/bx/implatform/vo/FriendVO.java +++ b/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; } diff --git a/im-platform/src/main/java/com/bx/implatform/vo/GroupInviteVO.java b/im-platform/src/main/java/com/bx/implatform/vo/GroupInviteVO.java index 7c86962..b78f43f 100644 --- a/im-platform/src/main/java/com/bx/implatform/vo/GroupInviteVO.java +++ b/im-platform/src/main/java/com/bx/implatform/vo/GroupInviteVO.java @@ -3,6 +3,7 @@ package com.bx.implatform.vo; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; import lombok.Data; import java.util.List; @@ -15,6 +16,7 @@ public class GroupInviteVO { @Schema(description = "群id") private Long groupId; + @Size(max = 50, message = "一次最多只能邀请50位用户") @NotEmpty(message = "群id不可为空") @Schema(description = "好友id列表不可为空") private List friendIds; diff --git a/im-platform/src/main/resources/application-dev.yml b/im-platform/src/main/resources/application-dev.yml index 618bcbb..aff630b 100644 --- a/im-platform/src/main/resources/application-dev.yml +++ b/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 #内网地址 diff --git a/im-server/src/main/resources/application-dev.yml b/im-server/src/main/resources/application-dev.yml index 305d30c..a06ec18 100644 --- a/im-server/src/main/resources/application-dev.yml +++ b/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 \ No newline at end of file diff --git a/im-uniapp/.env.js b/im-uniapp/.env.js index b6b9ff4..eb43c91 100644 --- a/im-uniapp/.env.js +++ b/im-uniapp/.env.js @@ -1,12 +1,22 @@ //设置环境(打包前修改此变量) const ENV = "DEV"; const UNI_APP = {} + +// 每个会话最大消息缓存数量,-1表示不限制 +UNI_APP.MAX_MESSAGE_SIZE = 3000; +// 表情包路径 +UNI_APP.EMO_URL = "/static/emoji/"; +// #ifdef MP-WEIXIN +// 微信小程序的本地表情包经常莫名失效,建议将表情放到服务器中 +// UNI_APP.EMO_URL = "https://www.boxim.online/emoji/"; +// #endif + if(ENV=="DEV"){ UNI_APP.BASE_URL = "http://127.0.0.1:8888"; UNI_APP.WS_URL = "ws://127.0.0.1:8878/im"; // H5 走本地代理解决跨域问题 // #ifdef H5 - UNI_APP.BASE_URL = "/api"; + UNI_APP.BASE_URL = "/api"; // #endif } if(ENV=="PROD"){ diff --git a/im-uniapp/App.vue b/im-uniapp/App.vue index 0f62016..c5dec44 100644 --- a/im-uniapp/App.vue +++ b/im-uniapp/App.vue @@ -17,6 +17,7 @@ export default { }, methods: { init() { + this.reconnecting = false; this.isExit = false; // 加载数据 this.loadStore().then(() => { @@ -30,20 +31,17 @@ export default { }, initWebSocket() { let loginInfo = uni.getStorageSync("loginInfo") - wsApi.init(); wsApi.connect(UNI_APP.WS_URL, loginInfo.accessToken); wsApi.onConnect(() => { - // 重连成功提示 if (this.reconnecting) { - this.reconnecting = false; - uni.showToast({ - title: "已重新连接", - icon: 'none' - }) + // 重连成功 + this.onReconnectWs(); + } else { + // 加载离线消息 + this.pullPrivateOfflineMessage(this.chatStore.privateMsgMaxId); + this.pullGroupOfflineMessage(this.chatStore.groupMsgMaxId); + } - // 加载离线消息 - this.pullPrivateOfflineMessage(this.chatStore.privateMsgMaxId); - this.pullGroupOfflineMessage(this.chatStore.groupMsgMaxId); }); wsApi.onMessage((cmd, msgInfo) => { if (cmd == 2) { @@ -107,6 +105,15 @@ export default { }) }, handlePrivateMessage(msg) { + // 标记这条消息是不是自己发的 + msg.selfSend = msg.sendId == this.userStore.userInfo.id; + // 好友id + let friendId = msg.selfSend ? msg.recvId : msg.sendId; + // 会话信息 + let chatInfo = { + type: 'PRIVATE', + targetId: friendId + } // 消息加载标志 if (msg.type == enums.MESSAGE_TYPE.LOADING) { this.chatStore.setLoadingPrivateMsg(JSON.parse(msg.content)) @@ -114,10 +121,7 @@ export default { } // 消息已读处理,清空已读数量 if (msg.type == enums.MESSAGE_TYPE.READED) { - this.chatStore.resetUnreadCount({ - type: 'PRIVATE', - targetId: msg.recvId - }) + this.chatStore.resetUnreadCount(chatInfo); return; } // 消息回执处理,改消息状态为已读 @@ -127,13 +131,24 @@ export default { }) return; } - // 标记这条消息是不是自己发的 - msg.selfSend = msg.sendId == this.userStore.userInfo.id; - // 好友id - let friendId = msg.selfSend ? msg.recvId : msg.sendId; - this.loadFriendInfo(friendId, (friend) => { - this.insertPrivateMessage(friend, msg); - }) + // 消息撤回 + if (msg.type == enums.MESSAGE_TYPE.RECALL) { + this.chatStore.recallMessage(msg, chatInfo); + return; + } + // 新增好友 + if (msg.type == enums.MESSAGE_TYPE.FRIEND_NEW) { + this.friendStore.addFriend(JSON.parse(msg.content)); + return; + } + // 删除好友 + if (msg.type == enums.MESSAGE_TYPE.FRIEND_DEL) { + this.friendStore.removeFriend(friendId); + return; + } + // 消息插入 + let friend = this.loadFriendInfo(friendId); + this.insertPrivateMessage(friend, msg); }, insertPrivateMessage(friend, msg) { // 单人视频信令 @@ -162,21 +177,31 @@ export default { }, delayTime) return; } - let chatInfo = { - type: 'PRIVATE', - targetId: friend.id, - showName: friend.nickName, - headImage: friend.headImage - }; - // 打开会话 - this.chatStore.openChat(chatInfo); // 插入消息 - this.chatStore.insertMessage(msg, chatInfo); - // 播放提示音 - this.playAudioTip(); + if (msgType.isNormal(msg.type) || msgType.isTip(msg.type) || msgType.isAction(msg.type)) { + let chatInfo = { + type: 'PRIVATE', + targetId: friend.id, + showName: friend.nickName, + headImage: friend.headImage + }; + // 打开会话 + this.chatStore.openChat(chatInfo); + // 插入消息 + this.chatStore.insertMessage(msg, chatInfo); + // 播放提示音 + this.playAudioTip(); + } + }, handleGroupMessage(msg) { + // 标记这条消息是不是自己发的 + msg.selfSend = msg.sendId == this.userStore.userInfo.id; + let chatInfo = { + type: 'GROUP', + targetId: msg.groupId + } // 消息加载标志 if (msg.type == enums.MESSAGE_TYPE.LOADING) { this.chatStore.setLoadingGroupMsg(JSON.parse(msg.content)) @@ -185,19 +210,11 @@ export default { // 消息已读处理 if (msg.type == enums.MESSAGE_TYPE.READED) { // 我已读对方的消息,清空已读数量 - let chatInfo = { - type: 'GROUP', - targetId: msg.groupId - } this.chatStore.resetUnreadCount(chatInfo) return; } // 消息回执处理 if (msg.type == enums.MESSAGE_TYPE.RECEIPT) { - let chatInfo = { - type: 'GROUP', - targetId: msg.groupId - } // 更新消息已读人数 let msgInfo = { id: msg.id, @@ -205,15 +222,28 @@ export default { readedCount: msg.readedCount, receiptOk: msg.receiptOk }; - this.chatStore.updateMessage(msgInfo,chatInfo) + this.chatStore.updateMessage(msgInfo, chatInfo) return; } - // 标记这条消息是不是自己发的 - msg.selfSend = msg.sendId == this.userStore.userInfo.id; - this.loadGroupInfo(msg.groupId, (group) => { - // 插入群聊消息 - this.insertGroupMessage(group, msg); - }) + // 消息撤回 + if (msg.type == enums.MESSAGE_TYPE.RECALL) { + this.chatStore.recallMessage(msg, chatInfo) + return; + } + // 新增群 + if (msg.type == enums.MESSAGE_TYPE.GROUP_NEW) { + this.groupStore.addGroup(JSON.parse(msg.content)); + return; + } + // 删除群 + if (msg.type == enums.MESSAGE_TYPE.GROUP_DEL) { + this.groupStore.removeGroup(msg.groupId); + return; + } + // 插入消息 + let group = this.loadGroupInfo(msg.groupId); + this.insertGroupMessage(group, msg); + }, handleSystemMessage(msg) { if (msg.type == enums.MESSAGE_TYPE.USER_BANNED) { @@ -255,47 +285,45 @@ export default { }, delayTime) return; } - - let chatInfo = { - type: 'GROUP', - targetId: group.id, - showName: group.showGroupName, - headImage: group.headImageThumb - }; - // 打开会话 - this.chatStore.openChat(chatInfo); // 插入消息 - this.chatStore.insertMessage(msg, chatInfo); - // 播放提示音 - this.playAudioTip(); + if (msgType.isNormal(msg.type) || msgType.isTip(msg.type) || msgType.isAction(msg.type)) { + let chatInfo = { + type: 'GROUP', + targetId: group.id, + showName: group.showGroupName, + headImage: group.headImageThumb + }; + // 打开会话 + this.chatStore.openChat(chatInfo); + // 插入消息 + this.chatStore.insertMessage(msg, chatInfo); + // 播放提示音 + this.playAudioTip(); + } + }, 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) { + loadGroupInfo(id) { let group = this.groupStore.findGroup(id); - if (group) { - callback(group); - } else { - http({ - url: `/group/find/${id}`, - method: 'GET' - }).then((group) => { - this.groupStore.addGroup(group); - callback(group) - }) + if (!group) { + group = { + id: id, + showGroupName: "未知群聊", + headImageThumb: "" + } } + return group; }, exit() { console.log("exit"); @@ -341,12 +369,11 @@ export default { // 记录标志 this.reconnecting = true; // 重新加载一次个人信息,目的是为了保证网络已经正常且token有效 - this.reloadUserInfo().then((userInfo) => { + this.userStore.loadUser().then((userInfo) => { uni.showToast({ title: '连接已断开,尝试重新连接...', - icon: 'none', + icon: 'none' }) - this.userStore.setUserInfo(userInfo); // 重新连接 let loginInfo = uni.getStorageSync("loginInfo") wsApi.reconnect(UNI_APP.WS_URL, loginInfo.accessToken); @@ -357,10 +384,23 @@ export default { }, 5000) }) }, - reloadUserInfo() { - return http({ - url: '/user/self', - method: 'GET' + onReconnectWs() { + this.reconnecting = false; + // 重新加载好友和群聊 + const promises = []; + promises.push(this.friendStore.loadFriend()); + promises.push(this.groupStore.loadGroup()); + Promise.all(promises).then(() => { + uni.showToast({ + title: "已重新连接", + icon: 'none' + }) + // 加载离线消息 + this.pullPrivateOfflineMessage(this.chatStore.privateMsgMaxId); + this.pullGroupOfflineMessage(this.chatStore.groupMsgMaxId); + }).catch((e) => { + console.log(e); + this.exit(); }) }, closeSplashscreen(delay) { diff --git a/im-uniapp/common/emotion.js b/im-uniapp/common/emotion.js index 8c7674e..f7dfe37 100644 --- a/im-uniapp/common/emotion.js +++ b/im-uniapp/common/emotion.js @@ -1,3 +1,6 @@ +import UNI_APP from '@/.env.js' + + const emoTextList = ['憨笑', '媚眼', '开心', '坏笑', '可怜', '爱心', '笑哭', '拍手', '惊喜', '打气', '大哭', '流泪', '饥饿', '难受', '健身', '示爱', '色色', '眨眼', '暴怒', '惊恐', '思考', '头晕', '大吐', '酷笑', '翻滚', '享受', '鼻涕', '快乐', '雀跃', '微笑', @@ -13,30 +16,23 @@ let containEmoji = (content) => { } let transform = (content, extClass) => { - return content.replace(regex, (emoText) => { + return content.replace(regex, (emoText)=>{ // 将匹配结果替换表情图片 let word = emoText.replace(/\#|\;/gi, ''); let idx = emoTextList.indexOf(word); if (idx == -1) { return emoText; } - let path = textToPath(emoText, true); + let path = textToPath(emoText); let img = ``; return img; }); } -let textToPath = (emoText, isRichText) => { +let textToPath = (emoText) => { let word = emoText.replace(/\#|\;/gi, ''); let idx = emoTextList.indexOf(word); - let path = `/static/emoji/${idx}.gif`; - // #ifdef MP-WEIXIN - // 小程序的表情要去掉最前面"/"(但有的时候又不能去掉,十分奇怪) - if (isRichText) { - path = path.slice(1); - } - // #endif - return path; + return UNI_APP.EMO_URL + idx + ".gif"; } export default { diff --git a/im-uniapp/common/enums.js b/im-uniapp/common/enums.js index 08510be..fbdb7a3 100644 --- a/im-uniapp/common/enums.js +++ b/im-uniapp/common/enums.js @@ -14,6 +14,10 @@ const MESSAGE_TYPE = { ACT_RT_VOICE: 40, ACT_RT_VIDEO: 41, USER_BANNED: 50, + FRIEND_NEW: 80, + FRIEND_DEL: 81, + GROUP_NEW: 90, + GROUP_DEL: 91, RTC_CALL_VOICE: 100, RTC_CALL_VIDEO: 101, RTC_ACCEPT: 102, diff --git a/im-uniapp/common/wssocket.js b/im-uniapp/common/wssocket.js index 144c746..f0aaafe 100644 --- a/im-uniapp/common/wssocket.js +++ b/im-uniapp/common/wssocket.js @@ -1,20 +1,33 @@ -let wsurl = ""; let accessToken = ""; let messageCallBack = null; let closeCallBack = null; let connectCallBack = null; let isConnect = false; //连接标识 避免重复连接 let rec = null; -let isInit = false; let lastConnectTime = new Date(); // 最后一次连接时间 +let socketTask = null; -let init = () => { - // 防止重复初始化 - if (isInit) { +let connect = (wsurl, token) => { + accessToken = token; + if (isConnect) { return; } - isInit = true; - uni.onSocketOpen((res) => { + lastConnectTime = new Date(); + socketTask = uni.connectSocket({ + url: wsurl, + success: (res) => { + console.log("websocket连接成功"); + }, + fail: (e) => { + console.log(e); + console.log("websocket连接失败,10s后重连"); + setTimeout(() => { + connect(); + }, 10000) + } + }); + + socketTask.onOpen((res) => { console.log("WebSocket连接已打开"); isConnect = true; // 发送登录命令 @@ -24,12 +37,12 @@ let init = () => { accessToken: accessToken } }; - uni.sendSocketMessage({ + socketTask.send({ data: JSON.stringify(loginInfo) }); }) - uni.onSocketMessage((res) => { + socketTask.onMessage((res) => { let sendInfo = JSON.parse(res.data) if (sendInfo.cmd == 0) { heartCheck.start() @@ -45,54 +58,31 @@ let init = () => { } }) - uni.onSocketClose((res) => { + socketTask.onClose((res) => { console.log('WebSocket连接关闭') isConnect = false; closeCallBack && closeCallBack(res); }) - uni.onSocketError((e) => { + socketTask.onError((e) => { console.log(e) isConnect = false; // APP 应用切出超过一定时间(约1分钟)会触发报错,此处回调给应用进行重连 closeCallBack && closeCallBack({ code: 1006 }); }) -}; - -let connect = (url, token) => { - wsurl = url; - accessToken = token; - if (isConnect) { - return; - } - lastConnectTime = new Date(); - uni.connectSocket({ - url: wsurl, - success: (res) => { - console.log("websocket连接成功"); - }, - fail: (e) => { - console.log(e); - console.log("websocket连接失败,10s后重连"); - setTimeout(() => { - connect(); - }, 10000) - } - }); } //定义重连函数 let reconnect = (wsurl, accessToken) => { console.log("尝试重新连接"); if (isConnect) { - //如果已经连上就不在重连了 return; } // 延迟10秒重连 避免过多次过频繁请求重连 let timeDiff = new Date().getTime() - lastConnectTime.getTime() let delay = timeDiff < 10000 ? 10000 - timeDiff : 0; rec && clearTimeout(rec); - rec = setTimeout(function () { + rec = setTimeout(function() { connect(wsurl, accessToken); }, delay); }; @@ -102,7 +92,7 @@ let close = (code) => { if (!isConnect) { return; } - uni.closeSocket({ + socketTask.close({ code: code, complete: (res) => { console.log("关闭websocket连接"); @@ -115,39 +105,28 @@ let close = (code) => { }; -//心跳设置 -var heartCheck = { - timeout: 10000, //每段时间发送一次心跳包 这里设置为30s - timeoutObj: null, //延时发送消息对象(启动心跳新建这个对象,收到消息后重置对象) - start: function () { +// 心跳设置 +let heartCheck = { + timeout: 20000, // 每段时间发送一次心跳包 这里设置为20s + timeoutObj: null, // 延时发送消息对象(启动心跳新建这个对象,收到消息后重置对象) + start: function() { if (isConnect) { console.log('发送WebSocket心跳') let heartBeat = { cmd: 1, data: {} }; - uni.sendSocketMessage({ - data: JSON.stringify(heartBeat), - fail(res) { - console.log(res); - } - }) + sendMessage(JSON.stringify(heartBeat)) } }, - reset: function () { + reset: function() { clearTimeout(this.timeoutObj); - this.timeoutObj = setTimeout(function () { - heartCheck.start(); - }, this.timeout); + this.timeoutObj = setTimeout(() => heartCheck.start(), this.timeout); } +}; -} - -// 实际调用的方法 -function sendMessage(agentData) { - uni.sendSocketMessage({ - data: agentData - }) +let sendMessage = (message) => { + socketTask.send({ data: message }) } let onConnect = (callback) => { @@ -155,19 +134,18 @@ let onConnect = (callback) => { } -function onMessage(callback) { +let onMessage = (callback) => { messageCallBack = callback; } -function onClose(callback) { +let onClose = (callback) => { closeCallBack = callback; } // 将方法暴露出去 export { - init, connect, reconnect, close, diff --git a/im-uniapp/components/chat-at-box/chat-at-box.vue b/im-uniapp/components/chat-at-box/chat-at-box.vue index ad1a9ed..f056c91 100644 --- a/im-uniapp/components/chat-at-box/chat-at-box.vue +++ b/im-uniapp/components/chat-at-box/chat-at-box.vue @@ -9,7 +9,7 @@ - + @@ -18,18 +18,15 @@ - - - - + + @@ -91,13 +88,13 @@ export default { }, computed: { atUserIds() { - let ids = []; - this.showMembers.forEach((m) => { - if (m.checked) { - ids.push(m.userId); - } - }) - return ids; + return this.showMembers.filter(m => m.checked).map(m => m.userId); + }, + checkedMembers() { + return this.showMembers.filter(m => m.checked); + }, + memberItems() { + return this.showMembers.filter(m => m.showNickName.includes(this.searchText)); } } } diff --git a/im-uniapp/components/chat-group-readed/chat-group-readed.vue b/im-uniapp/components/chat-group-readed/chat-group-readed.vue index 4ae285e..13b67ac 100644 --- a/im-uniapp/components/chat-group-readed/chat-group-readed.vue +++ b/im-uniapp/components/chat-group-readed/chat-group-readed.vue @@ -7,26 +7,26 @@ - - + + + - - + + + diff --git a/im-uniapp/components/chat-message-item/chat-message-item.vue b/im-uniapp/components/chat-message-item/chat-message-item.vue index ae6f0c2..0904bad 100644 --- a/im-uniapp/components/chat-message-item/chat-message-item.vue +++ b/im-uniapp/components/chat-message-item/chat-message-item.vue @@ -1,13 +1,12 @@ - \ No newline at end of file diff --git a/im-uniapp/pages/common/user-info.vue b/im-uniapp/pages/common/user-info.vue index b0ea1aa..0db26a7 100644 --- a/im-uniapp/pages/common/user-info.vue +++ b/im-uniapp/pages/common/user-info.vue @@ -9,17 +9,17 @@ - {{ userInfo.userName }} + {{ userInfo.nickName }} - 昵称: + 用户名: - {{ userInfo.nickName }} + {{ userInfo.userName }} @@ -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) { diff --git a/im-uniapp/pages/friend/friend-add.vue b/im-uniapp/pages/friend/friend-add.vue index ea21de4..8956e21 100644 --- a/im-uniapp/pages/friend/friend-add.vue +++ b/im-uniapp/pages/friend/friend-add.vue @@ -14,11 +14,11 @@