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,6 +37,8 @@ public interface ErrorCodeConstants { | ||
| 37 | ErrorCode GROUP_MEMBER_ALREADY_QUIT = new ErrorCode(1_040_500_002, "您已退出该群"); | 37 | ErrorCode GROUP_MEMBER_ALREADY_QUIT = new ErrorCode(1_040_500_002, "您已退出该群"); |
| 38 | ErrorCode GROUP_OWNER_CANNOT_QUIT = new ErrorCode(1_040_500_003, "您是群主,不可退出群聊"); | 38 | ErrorCode GROUP_OWNER_CANNOT_QUIT = new ErrorCode(1_040_500_003, "您是群主,不可退出群聊"); |
| 39 | ErrorCode GROUP_CANNOT_REMOVE_SELF = new ErrorCode(1_040_500_004, "不能移除自己"); | 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 | // ========== 好友 (1-040-600-000) ========== | 43 | // ========== 好友 (1-040-600-000) ========== |
| 42 | ErrorCode FRIEND_NOT_EXISTS = new ErrorCode(1_040_600_000, "好友关系不存在"); | 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,6 +13,11 @@ public interface ImCommonConstants { | ||
| 13 | int MESSAGE_MAX_PULL_SIZE = 1000; | 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 | int MESSAGE_RECALL_TIMEOUT_MINUTES = 5; | 23 | int MESSAGE_RECALL_TIMEOUT_MINUTES = 5; |
| @@ -50,14 +55,18 @@ public interface ImCommonConstants { | @@ -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,6 +5,7 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; | ||
| 5 | import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; | 5 | import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; |
| 6 | import org.apache.ibatis.annotations.Mapper; | 6 | import org.apache.ibatis.annotations.Mapper; |
| 7 | 7 | ||
| 8 | +import java.util.Collection; | ||
| 8 | import java.util.List; | 9 | import java.util.List; |
| 9 | 10 | ||
| 10 | /** | 11 | /** |
| @@ -26,5 +27,20 @@ public interface ImFriendMapper extends BaseMapperX<ImFriendDO> { | @@ -26,5 +27,20 @@ public interface ImFriendMapper extends BaseMapperX<ImFriendDO> { | ||
| 26 | .eq(ImFriendDO::getUserId, userId) | 27 | .eq(ImFriendDO::getUserId, userId) |
| 27 | .orderByDesc(ImFriendDO::getId)); | 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,6 +3,7 @@ package cn.iocoder.yudao.module.im.service.friend; | ||
| 3 | import cn.iocoder.yudao.module.im.controller.admin.friend.vo.ImFriendUpdateReqVO; | 3 | import cn.iocoder.yudao.module.im.controller.admin.friend.vo.ImFriendUpdateReqVO; |
| 4 | import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; | 4 | import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; |
| 5 | 5 | ||
| 6 | +import java.util.Collection; | ||
| 6 | import java.util.List; | 7 | import java.util.List; |
| 7 | 8 | ||
| 8 | /** | 9 | /** |
| @@ -33,6 +34,15 @@ public interface ImFriendService { | @@ -33,6 +34,15 @@ public interface ImFriendService { | ||
| 33 | List<ImFriendDO> getFriendList(Long userId); | 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 | * @param userId 当前登录用户编号 | 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,6 +21,7 @@ import org.springframework.transaction.annotation.Transactional; | ||
| 21 | import org.springframework.validation.annotation.Validated; | 21 | import org.springframework.validation.annotation.Validated; |
| 22 | 22 | ||
| 23 | import java.time.LocalDateTime; | 23 | import java.time.LocalDateTime; |
| 24 | +import java.util.Collection; | ||
| 24 | import java.util.List; | 25 | import java.util.List; |
| 25 | import java.util.Objects; | 26 | import java.util.Objects; |
| 26 | 27 | ||
| @@ -66,6 +67,12 @@ public class ImFriendServiceImpl implements ImFriendService { | @@ -66,6 +67,12 @@ public class ImFriendServiceImpl implements ImFriendService { | ||
| 66 | } | 67 | } |
| 67 | 68 | ||
| 68 | @Override | 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 | public ImFriendDO getFriend(Long userId, Long friendUserId) { | 76 | public ImFriendDO getFriend(Long userId, Long friendUserId) { |
| 70 | return imFriendMapper.selectByUserIdAndFriendUserId(userId, friendUserId); | 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,14 +13,16 @@ import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupUpdateReqVO; | ||
| 13 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberInviteReqVO; | 13 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberInviteReqVO; |
| 14 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberRemoveReqVO; | 14 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberRemoveReqVO; |
| 15 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberUpdateReqVO; | 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 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; | 17 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; |
| 17 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; | 18 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; |
| 18 | import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMapper; | 19 | import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMapper; |
| 20 | +import cn.iocoder.yudao.module.im.service.friend.ImFriendService; | ||
| 19 | import cn.iocoder.yudao.module.im.service.message.ImGroupMessageService; | 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 | import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; | 22 | import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; |
| 23 | import cn.iocoder.yudao.module.im.service.websocket.dto.ImGroupMessageDTO; | 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 | import jakarta.annotation.Resource; | 26 | import jakarta.annotation.Resource; |
| 25 | import org.springframework.cache.annotation.CacheEvict; | 27 | import org.springframework.cache.annotation.CacheEvict; |
| 26 | import org.springframework.cache.annotation.Cacheable; | 28 | import org.springframework.cache.annotation.Cacheable; |
| @@ -30,11 +32,7 @@ import org.springframework.transaction.annotation.Transactional; | @@ -30,11 +32,7 @@ import org.springframework.transaction.annotation.Transactional; | ||
| 30 | import org.springframework.validation.annotation.Validated; | 32 | import org.springframework.validation.annotation.Validated; |
| 31 | 33 | ||
| 32 | import java.time.LocalDateTime; | 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 | import java.util.stream.Collectors; | 36 | import java.util.stream.Collectors; |
| 39 | 37 | ||
| 40 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; | 38 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| @@ -66,6 +64,9 @@ public class ImGroupServiceImpl implements ImGroupService { | @@ -66,6 +64,9 @@ public class ImGroupServiceImpl implements ImGroupService { | ||
| 66 | private ImWebSocketService webSocketService; | 64 | private ImWebSocketService webSocketService; |
| 67 | 65 | ||
| 68 | @Resource | 66 | @Resource |
| 67 | + private ImFriendService friendService; | ||
| 68 | + | ||
| 69 | + @Resource | ||
| 69 | private AdminUserApi adminUserApi; | 70 | private AdminUserApi adminUserApi; |
| 70 | 71 | ||
| 71 | // ==================== 群的写操作 ==================== | 72 | // ==================== 群的写操作 ==================== |
| @@ -164,8 +165,15 @@ public class ImGroupServiceImpl implements ImGroupService { | @@ -164,8 +165,15 @@ public class ImGroupServiceImpl implements ImGroupService { | ||
| 164 | if (CollUtil.isEmpty(inviteReqVO.getMemberUserIds())) { | 165 | if (CollUtil.isEmpty(inviteReqVO.getMemberUserIds())) { |
| 165 | return; | 166 | return; |
| 166 | } | 167 | } |
| 167 | - // 1.3 校验群人数上限 | 168 | + // 1.3 校验被邀请人都是当前用户的好友 |
| 168 | List<Long> memberUserIds = inviteReqVO.getMemberUserIds(); | 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 | if (activeMembers.size() + memberUserIds.size() > MAX_GROUP_MEMBER) { | 177 | if (activeMembers.size() + memberUserIds.size() > MAX_GROUP_MEMBER) { |
| 170 | throw exception(GROUP_MEMBER_EXCEED, MAX_GROUP_MEMBER); | 178 | throw exception(GROUP_MEMBER_EXCEED, MAX_GROUP_MEMBER); |
| 171 | } | 179 | } |
| @@ -177,12 +185,8 @@ public class ImGroupServiceImpl implements ImGroupService { | @@ -177,12 +185,8 @@ public class ImGroupServiceImpl implements ImGroupService { | ||
| 177 | webSocketService.sendGroupMessageAsync(new HashSet<>(memberUserIds), | 185 | webSocketService.sendGroupMessageAsync(new HashSet<>(memberUserIds), |
| 178 | ImGroupMessageDTO.ofGroupCreate(userId, groupId)); | 186 | ImGroupMessageDTO.ofGroupCreate(userId, groupId)); |
| 179 | // 3.2 发送邀请提示消息给所有群成员 | 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 | List<ImGroupMemberDO> allMembers = groupMemberService.getActiveGroupMemberListByGroupId(groupId); | 190 | List<ImGroupMemberDO> allMembers = groupMemberService.getActiveGroupMemberListByGroupId(groupId); |
| 187 | Set<Long> allMemberUserIds = convertSet(allMembers, ImGroupMemberDO::getUserId); | 191 | Set<Long> allMemberUserIds = convertSet(allMembers, ImGroupMemberDO::getUserId); |
| 188 | groupMessageService.sendTipGroupMessage(userId, groupId, allMemberUserIds, tipContent); | 192 | groupMessageService.sendTipGroupMessage(userId, groupId, allMemberUserIds, tipContent); |
| @@ -284,4 +288,17 @@ public class ImGroupServiceImpl implements ImGroupService { | @@ -284,4 +288,17 @@ public class ImGroupServiceImpl implements ImGroupService { | ||
| 284 | return SpringUtil.getBean(getClass()); | 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 | \ No newline at end of file | 305 | \ No newline at end of file |