Commit fa474c6f03b5f3b156b5cc612bde8e6c0d9b15ee

Authored by YunaiV
1 parent 84304718

✨ feat(group): 添加群聊成员限制及邀请好友功能

新增群聊成员人数限制,最大成员数为 500。实现邀请好友功能,确保被邀请人是当前用户的好友,若不是则返回相应错误提示。更新相关提示信息以提升用户体验。
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