Commit fa474c6f03b5f3b156b5cc612bde8e6c0d9b15ee
1 parent
84304718
✨ feat(group): 添加群聊成员限制及邀请好友功能
新增群聊成员人数限制,最大成员数为 500。实现邀请好友功能,确保被邀请人是当前用户的好友,若不是则返回相应错误提示。更新相关提示信息以提升用户体验。
Showing
6 changed files
with
78 additions
and
17 deletions
yudao-module-im/yudao-module-im-api/src/main/java/cn/iocoder/yudao/module/im/enums/ErrorCodeConstants.java
| ... | ... | @@ -37,6 +37,8 @@ public interface ErrorCodeConstants { |
| 37 | 37 | ErrorCode GROUP_MEMBER_ALREADY_QUIT = new ErrorCode(1_040_500_002, "您已退出该群"); |
| 38 | 38 | ErrorCode GROUP_OWNER_CANNOT_QUIT = new ErrorCode(1_040_500_003, "您是群主,不可退出群聊"); |
| 39 | 39 | ErrorCode GROUP_CANNOT_REMOVE_SELF = new ErrorCode(1_040_500_004, "不能移除自己"); |
| 40 | + ErrorCode GROUP_MEMBER_EXCEED = new ErrorCode(1_040_500_005, "群聊人数不能超过{}人"); | |
| 41 | + ErrorCode GROUP_INVITE_NOT_FRIEND = new ErrorCode(1_040_500_006, "'{}' 不是您的好友,邀请失败"); | |
| 40 | 42 | |
| 41 | 43 | // ========== 好友 (1-040-600-000) ========== |
| 42 | 44 | ErrorCode FRIEND_NOT_EXISTS = new ErrorCode(1_040_600_000, "好友关系不存在"); | ... | ... |
yudao-module-im/yudao-module-im-api/src/main/java/cn/iocoder/yudao/module/im/enums/ImCommonConstants.java
| ... | ... | @@ -13,6 +13,11 @@ public interface ImCommonConstants { |
| 13 | 13 | int MESSAGE_MAX_PULL_SIZE = 1000; |
| 14 | 14 | |
| 15 | 15 | /** |
| 16 | + * 群最大成员人数 | |
| 17 | + */ | |
| 18 | + int MAX_GROUP_MEMBER = 500; | |
| 19 | + | |
| 20 | + /** | |
| 16 | 21 | * 消息撤回时间限制(分钟) |
| 17 | 22 | */ |
| 18 | 23 | int MESSAGE_RECALL_TIMEOUT_MINUTES = 5; |
| ... | ... | @@ -50,14 +55,18 @@ public interface ImCommonConstants { |
| 50 | 55 | /** |
| 51 | 56 | * 群解散系统提示 |
| 52 | 57 | */ |
| 53 | - String GROUP_DISSOLVE_TIP_MESSAGE = "'{}'解散了群聊"; | |
| 58 | + String GROUP_DISSOLVE_TIP_MESSAGE = "\"{}\"解散了群聊"; | |
| 54 | 59 | /** |
| 55 | 60 | * 退群系统提示 |
| 56 | 61 | */ |
| 57 | - String GROUP_QUIT_TIP_MESSAGE = "您已退出群聊"; | |
| 62 | + String GROUP_QUIT_TIP_MESSAGE = "你已退出群聊"; | |
| 58 | 63 | /** |
| 59 | 64 | * 踢出群系统提示 |
| 60 | 65 | */ |
| 61 | - String GROUP_REMOVE_TIP_MESSAGE = "您已被移出群聊"; | |
| 66 | + String GROUP_REMOVE_TIP_MESSAGE = "你已被移出群聊"; | |
| 67 | + /** | |
| 68 | + * 邀请入群系统提示 | |
| 69 | + */ | |
| 70 | + String GROUP_INVITE_TIP_MESSAGE = "\"{}\"邀请\"{}\"加入了群聊"; | |
| 62 | 71 | |
| 63 | 72 | } | ... | ... |
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/friend/ImFriendMapper.java
| ... | ... | @@ -5,6 +5,7 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| 5 | 5 | import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; |
| 6 | 6 | import org.apache.ibatis.annotations.Mapper; |
| 7 | 7 | |
| 8 | +import java.util.Collection; | |
| 8 | 9 | import java.util.List; |
| 9 | 10 | |
| 10 | 11 | /** |
| ... | ... | @@ -26,5 +27,20 @@ public interface ImFriendMapper extends BaseMapperX<ImFriendDO> { |
| 26 | 27 | .eq(ImFriendDO::getUserId, userId) |
| 27 | 28 | .orderByDesc(ImFriendDO::getId)); |
| 28 | 29 | } |
| 30 | + default List<ImFriendDO> selectListByUserIdAndStatus(Long userId, Integer status) { | |
| 31 | + return selectList(new LambdaQueryWrapperX<ImFriendDO>() | |
| 32 | + .eq(ImFriendDO::getUserId, userId) | |
| 33 | + .eq(ImFriendDO::getStatus, status) | |
| 34 | + .orderByDesc(ImFriendDO::getId)); | |
| 35 | + } | |
| 36 | + | |
| 37 | + default List<ImFriendDO> selectListByUserIdAndFriendUserIdsAndStatus(Long userId, | |
| 38 | + Collection<Long> friendUserIds, | |
| 39 | + Integer status) { | |
| 40 | + return selectList(new LambdaQueryWrapperX<ImFriendDO>() | |
| 41 | + .eq(ImFriendDO::getUserId, userId) | |
| 42 | + .in(ImFriendDO::getFriendUserId, friendUserIds) | |
| 43 | + .eq(ImFriendDO::getStatus, status)); | |
| 44 | + } | |
| 29 | 45 | |
| 30 | 46 | } | ... | ... |
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendService.java
| ... | ... | @@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.im.service.friend; |
| 3 | 3 | import cn.iocoder.yudao.module.im.controller.admin.friend.vo.ImFriendUpdateReqVO; |
| 4 | 4 | import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; |
| 5 | 5 | |
| 6 | +import java.util.Collection; | |
| 6 | 7 | import java.util.List; |
| 7 | 8 | |
| 8 | 9 | /** |
| ... | ... | @@ -33,6 +34,15 @@ public interface ImFriendService { |
| 33 | 34 | List<ImFriendDO> getFriendList(Long userId); |
| 34 | 35 | |
| 35 | 36 | /** |
| 37 | + * 获得当前用户与指定用户之间的有效好友列表(仅 ENABLE 状态) | |
| 38 | + * | |
| 39 | + * @param userId 用户编号 | |
| 40 | + * @param friendUserIds 需要校验的好友用户编号集合 | |
| 41 | + * @return 有效好友关系列表 | |
| 42 | + */ | |
| 43 | + List<ImFriendDO> getActiveFriendList(Long userId, Collection<Long> friendUserIds); | |
| 44 | + | |
| 45 | + /** | |
| 36 | 46 | * 查询一个好友关系记录(仅限本人视角) |
| 37 | 47 | * |
| 38 | 48 | * @param userId 当前登录用户编号 | ... | ... |
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImpl.java
| ... | ... | @@ -21,6 +21,7 @@ import org.springframework.transaction.annotation.Transactional; |
| 21 | 21 | import org.springframework.validation.annotation.Validated; |
| 22 | 22 | |
| 23 | 23 | import java.time.LocalDateTime; |
| 24 | +import java.util.Collection; | |
| 24 | 25 | import java.util.List; |
| 25 | 26 | import java.util.Objects; |
| 26 | 27 | |
| ... | ... | @@ -66,6 +67,12 @@ public class ImFriendServiceImpl implements ImFriendService { |
| 66 | 67 | } |
| 67 | 68 | |
| 68 | 69 | @Override |
| 70 | + public List<ImFriendDO> getActiveFriendList(Long userId, Collection<Long> friendUserIds) { | |
| 71 | + return imFriendMapper.selectListByUserIdAndFriendUserIdsAndStatus(userId, friendUserIds, | |
| 72 | + CommonStatusEnum.ENABLE.getStatus()); | |
| 73 | + } | |
| 74 | + | |
| 75 | + @Override | |
| 69 | 76 | public ImFriendDO getFriend(Long userId, Long friendUserId) { |
| 70 | 77 | return imFriendMapper.selectByUserIdAndFriendUserId(userId, friendUserId); |
| 71 | 78 | } | ... | ... |
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImpl.java
| ... | ... | @@ -13,14 +13,16 @@ import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupUpdateReqVO; |
| 13 | 13 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberInviteReqVO; |
| 14 | 14 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberRemoveReqVO; |
| 15 | 15 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberUpdateReqVO; |
| 16 | +import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; | |
| 16 | 17 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; |
| 17 | 18 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; |
| 18 | 19 | import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMapper; |
| 20 | +import cn.iocoder.yudao.module.im.service.friend.ImFriendService; | |
| 19 | 21 | import cn.iocoder.yudao.module.im.service.message.ImGroupMessageService; |
| 20 | -import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | |
| 21 | -import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; | |
| 22 | 22 | import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; |
| 23 | 23 | import cn.iocoder.yudao.module.im.service.websocket.dto.ImGroupMessageDTO; |
| 24 | +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | |
| 25 | +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; | |
| 24 | 26 | import jakarta.annotation.Resource; |
| 25 | 27 | import org.springframework.cache.annotation.CacheEvict; |
| 26 | 28 | import org.springframework.cache.annotation.Cacheable; |
| ... | ... | @@ -30,11 +32,7 @@ import org.springframework.transaction.annotation.Transactional; |
| 30 | 32 | import org.springframework.validation.annotation.Validated; |
| 31 | 33 | |
| 32 | 34 | import java.time.LocalDateTime; |
| 33 | -import java.util.Collections; | |
| 34 | -import java.util.HashSet; | |
| 35 | -import java.util.List; | |
| 36 | -import java.util.Map; | |
| 37 | -import java.util.Set; | |
| 35 | +import java.util.*; | |
| 38 | 36 | import java.util.stream.Collectors; |
| 39 | 37 | |
| 40 | 38 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| ... | ... | @@ -66,6 +64,9 @@ public class ImGroupServiceImpl implements ImGroupService { |
| 66 | 64 | private ImWebSocketService webSocketService; |
| 67 | 65 | |
| 68 | 66 | @Resource |
| 67 | + private ImFriendService friendService; | |
| 68 | + | |
| 69 | + @Resource | |
| 69 | 70 | private AdminUserApi adminUserApi; |
| 70 | 71 | |
| 71 | 72 | // ==================== 群的写操作 ==================== |
| ... | ... | @@ -164,8 +165,15 @@ public class ImGroupServiceImpl implements ImGroupService { |
| 164 | 165 | if (CollUtil.isEmpty(inviteReqVO.getMemberUserIds())) { |
| 165 | 166 | return; |
| 166 | 167 | } |
| 167 | - // 1.3 校验群人数上限 | |
| 168 | + // 1.3 校验被邀请人都是当前用户的好友 | |
| 168 | 169 | List<Long> memberUserIds = inviteReqVO.getMemberUserIds(); |
| 170 | + List<ImFriendDO> friends = friendService.getActiveFriendList(userId, memberUserIds); | |
| 171 | + Set<Long> friendUserIds = convertSet(friends, ImFriendDO::getFriendUserId); | |
| 172 | + Collection<Long> notFriendUserIds = CollUtil.subtract(memberUserIds, friendUserIds); | |
| 173 | + if (CollUtil.isNotEmpty(notFriendUserIds)) { | |
| 174 | + throw exception(GROUP_INVITE_NOT_FRIEND, getUserNicknames(notFriendUserIds)); | |
| 175 | + } | |
| 176 | + // 1.4 校验群人数上限 | |
| 169 | 177 | if (activeMembers.size() + memberUserIds.size() > MAX_GROUP_MEMBER) { |
| 170 | 178 | throw exception(GROUP_MEMBER_EXCEED, MAX_GROUP_MEMBER); |
| 171 | 179 | } |
| ... | ... | @@ -177,12 +185,8 @@ public class ImGroupServiceImpl implements ImGroupService { |
| 177 | 185 | webSocketService.sendGroupMessageAsync(new HashSet<>(memberUserIds), |
| 178 | 186 | ImGroupMessageDTO.ofGroupCreate(userId, groupId)); |
| 179 | 187 | // 3.2 发送邀请提示消息给所有群成员 |
| 180 | - AdminUserRespDTO inviter = adminUserApi.getUser(userId); | |
| 181 | - Map<Long, AdminUserRespDTO> invitedUserMap = adminUserApi.getUserMap(memberUserIds); | |
| 182 | - String invitedNames = memberUserIds.stream() | |
| 183 | - .map(id -> invitedUserMap.containsKey(id) ? invitedUserMap.get(id).getNickname() : String.valueOf(id)) | |
| 184 | - .collect(Collectors.joining(",")); | |
| 185 | - String tipContent = StrUtil.format(GROUP_INVITE_TIP_MESSAGE, inviter.getNickname(), invitedNames); | |
| 188 | + String tipContent = StrUtil.format(GROUP_INVITE_TIP_MESSAGE, | |
| 189 | + getUserNicknames(List.of(userId)), getUserNicknames(memberUserIds)); | |
| 186 | 190 | List<ImGroupMemberDO> allMembers = groupMemberService.getActiveGroupMemberListByGroupId(groupId); |
| 187 | 191 | Set<Long> allMemberUserIds = convertSet(allMembers, ImGroupMemberDO::getUserId); |
| 188 | 192 | groupMessageService.sendTipGroupMessage(userId, groupId, allMemberUserIds, tipContent); |
| ... | ... | @@ -284,4 +288,17 @@ public class ImGroupServiceImpl implements ImGroupService { |
| 284 | 288 | return SpringUtil.getBean(getClass()); |
| 285 | 289 | } |
| 286 | 290 | |
| 291 | + /** | |
| 292 | + * 根据用户编号集合,拼接用户昵称字符串(逗号分隔) | |
| 293 | + * | |
| 294 | + * @param userIds 用户编号集合 | |
| 295 | + * @return 昵称字符串,如 "张三,李四" | |
| 296 | + */ | |
| 297 | + private String getUserNicknames(Collection<Long> userIds) { | |
| 298 | + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds); | |
| 299 | + return userIds.stream() | |
| 300 | + .map(id -> userMap.containsKey(id) ? userMap.get(id).getNickname() : String.valueOf(id)) | |
| 301 | + .collect(Collectors.joining(",")); | |
| 302 | + } | |
| 303 | + | |
| 287 | 304 | } |
| 288 | 305 | \ No newline at end of file | ... | ... |