Commit 736a1a2f16cc1cb3139dd9d42871ce795b9c159d

Authored by YunaiV
1 parent 2ab72872

✨ feat(group): 添加用户侧群组和成员接口

新增获取当前登录用户的群列表和指定群的成员列表接口,支持聚合用户昵称和头像信息。优化群组成员查询逻辑,确保返回有效的群成员数据。

- 实现用户侧接口 `/im/group/list-my` 和 `/im/group-member/list`
- 聚合 AdminUser 的昵称和头像信息
- 过滤已封禁和解散的群组
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/friend/ImFriendController.java
@@ -84,7 +84,7 @@ public class ImFriendController { @@ -84,7 +84,7 @@ public class ImFriendController {
84 // ========== 私有方法:VO 组装 ========== 84 // ========== 私有方法:VO 组装 ==========
85 85
86 private List<ImFriendRespVO> buildFriendRespVOList(Collection<ImFriendDO> friends) { 86 private List<ImFriendRespVO> buildFriendRespVOList(Collection<ImFriendDO> friends) {
87 - if (CollUtil.isNotEmpty(friends)) { 87 + if (CollUtil.isEmpty(friends)) {
88 return Collections.emptyList(); 88 return Collections.emptyList();
89 } 89 }
90 // 批量聚合 AdminUser 信息(昵称 / 头像),避免 N+1 90 // 批量聚合 AdminUser 信息(昵称 / 头像),避免 N+1
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupController.java
1 package cn.iocoder.yudao.module.im.controller.admin.group; 1 package cn.iocoder.yudao.module.im.controller.admin.group;
2 2
3 -import org.springframework.web.bind.annotation.*; 3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  6 +import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
  7 +import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupPageReqVO;
  8 +import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupRespVO;
  9 +import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupSaveReqVO;
  10 +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
  11 +import cn.iocoder.yudao.module.im.service.group.ImGroupService;
  12 +import io.swagger.v3.oas.annotations.Operation;
  13 +import io.swagger.v3.oas.annotations.Parameter;
  14 +import io.swagger.v3.oas.annotations.tags.Tag;
4 import jakarta.annotation.Resource; 15 import jakarta.annotation.Resource;
5 -import org.springframework.validation.annotation.Validated; 16 +import jakarta.validation.Valid;
6 import org.springframework.security.access.prepost.PreAuthorize; 17 import org.springframework.security.access.prepost.PreAuthorize;
7 -import io.swagger.v3.oas.annotations.tags.Tag;  
8 -import io.swagger.v3.oas.annotations.Parameter;  
9 -import io.swagger.v3.oas.annotations.Operation; 18 +import org.springframework.validation.annotation.Validated;
  19 +import org.springframework.web.bind.annotation.*;
10 20
11 -import jakarta.validation.*; 21 +import java.util.List;
12 22
13 -import cn.iocoder.yudao.framework.common.pojo.PageResult;  
14 -import cn.iocoder.yudao.framework.common.pojo.CommonResult;  
15 -import cn.iocoder.yudao.framework.common.util.object.BeanUtils;  
16 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 23 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
17 24
18 -import cn.iocoder.yudao.module.im.controller.admin.group.vo.*;  
19 -import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;  
20 -import cn.iocoder.yudao.module.im.service.group.ImGroupService;  
21 -  
22 -// DONE @芋艿:create/update/delete/get/page 全部保留,管理后台群管理需要  
23 @Tag(name = "管理后台 - 群") 25 @Tag(name = "管理后台 - 群")
24 @RestController 26 @RestController
25 @RequestMapping("/im/group") 27 @RequestMapping("/im/group")
@@ -62,6 +64,7 @@ public class ImGroupController { @@ -62,6 +64,7 @@ public class ImGroupController {
62 return success(BeanUtils.toBean(group, ImGroupRespVO.class)); 64 return success(BeanUtils.toBean(group, ImGroupRespVO.class));
63 } 65 }
64 66
  67 + // TODO @AI:去掉这个接口;
65 @GetMapping("/page") 68 @GetMapping("/page")
66 @Operation(summary = "获得群分页") 69 @Operation(summary = "获得群分页")
67 @PreAuthorize("@ss.hasPermission('im:group:query')") 70 @PreAuthorize("@ss.hasPermission('im:group:query')")
@@ -70,4 +73,26 @@ public class ImGroupController { @@ -70,4 +73,26 @@ public class ImGroupController {
70 return success(BeanUtils.toBean(pageResult, ImGroupRespVO.class)); 73 return success(BeanUtils.toBean(pageResult, ImGroupRespVO.class));
71 } 74 }
72 75
73 -}  
74 \ No newline at end of file 76 \ No newline at end of file
  77 + // ========== 用户侧接口(登录用户自己的群,无需后台权限) ==========
  78 +
  79 + // TODO @AI:直接 list;
  80 + @GetMapping("/list-my")
  81 + @Operation(summary = "获得当前登录用户的群列表")
  82 + public CommonResult<List<ImGroupRespVO>> getMyGroupList() {
  83 + Long userId = SecurityFrameworkUtils.getLoginUserId();
  84 + List<ImGroupDO> groups = imGroupService.getMyGroupList(userId);
  85 + return success(BeanUtils.toBean(groups, ImGroupRespVO.class));
  86 + }
  87 +
  88 + // TODO @芋艿:【对齐】/group/create、/group/modify、/group/invite、/group/quit、/group/delete/{id}
  89 + // 这些用户侧接口第一期暂未实现。需要:
  90 + // 1. createGroup(vo):需同时插入 im_group + 批量插入 im_group_member;还要给群成员推送 FRIEND_INVITE 类消息
  91 + // 2. modifyGroup(vo):允许修改 name / notice / avatar / remarkGroupName / remarkNickName
  92 + // (其中 remarkGroupName / remarkNickName 是 im_group_member 的字段,不是 im_group 的)
  93 + // 3. invite(groupId, userIds):批量插入 im_group_member 为 ENABLE;给被邀请人推送入群通知
  94 + // 4. quitGroup(groupId):校验非群主才能退,然后把自己的 im_group_member 标为 DISABLE + quitTime
  95 + // 5. removeGroupMembers(groupId, userIds):群主权限校验 + 把成员标为 DISABLE
  96 + // 以上都需要 WebSocket 推送对应事件(FRIEND_INVITE / MEMBER_QUIT / MEMBER_REMOVE 等),
  97 + // 涉及 `ImWsEventType` 扩展 + `ImWebSocketPusher` 新增事件推送方法,待统一设计后补上。
  98 +
  99 +}
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupMemberController.java
@@ -2,12 +2,15 @@ package cn.iocoder.yudao.module.im.controller.admin.group; @@ -2,12 +2,15 @@ package cn.iocoder.yudao.module.im.controller.admin.group;
2 2
3 import cn.iocoder.yudao.framework.common.pojo.CommonResult; 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4 import cn.iocoder.yudao.framework.common.pojo.PageResult; 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
5 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 6 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
6 import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberPageReqVO; 7 import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberPageReqVO;
7 import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberRespVO; 8 import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberRespVO;
8 import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberSaveReqVO; 9 import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberSaveReqVO;
9 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; 10 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
10 import cn.iocoder.yudao.module.im.service.group.ImGroupMemberService; 11 import cn.iocoder.yudao.module.im.service.group.ImGroupMemberService;
  12 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  13 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
11 import io.swagger.v3.oas.annotations.Operation; 14 import io.swagger.v3.oas.annotations.Operation;
12 import io.swagger.v3.oas.annotations.Parameter; 15 import io.swagger.v3.oas.annotations.Parameter;
13 import io.swagger.v3.oas.annotations.tags.Tag; 16 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -17,9 +20,12 @@ import org.springframework.security.access.prepost.PreAuthorize; @@ -17,9 +20,12 @@ import org.springframework.security.access.prepost.PreAuthorize;
17 import org.springframework.validation.annotation.Validated; 20 import org.springframework.validation.annotation.Validated;
18 import org.springframework.web.bind.annotation.*; 21 import org.springframework.web.bind.annotation.*;
19 22
  23 +import java.util.Collections;
  24 +import java.util.List;
  25 +import java.util.Map;
  26 +
20 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 27 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
21 28
22 -// DONE @芋艿:create/update/delete/get/page 全部保留,管理后台群成员管理需要  
23 @Tag(name = "管理后台 - 群成员") 29 @Tag(name = "管理后台 - 群成员")
24 @RestController 30 @RestController
25 @RequestMapping("/im/group-member") 31 @RequestMapping("/im/group-member")
@@ -29,6 +35,10 @@ public class ImGroupMemberController { @@ -29,6 +35,10 @@ public class ImGroupMemberController {
29 @Resource 35 @Resource
30 private ImGroupMemberService imGroupMemberService; 36 private ImGroupMemberService imGroupMemberService;
31 37
  38 + @Resource
  39 + private AdminUserApi adminUserApi;
  40 +
  41 + // TODO @AI:add 加人;
32 @PostMapping("/create") 42 @PostMapping("/create")
33 @Operation(summary = "创建群成员") 43 @Operation(summary = "创建群成员")
34 @PreAuthorize("@ss.hasPermission('im:group-member:create')") 44 @PreAuthorize("@ss.hasPermission('im:group-member:create')")
@@ -36,6 +46,10 @@ public class ImGroupMemberController { @@ -36,6 +46,10 @@ public class ImGroupMemberController {
36 return success(imGroupMemberService.createGroupMember(createReqVO)); 46 return success(imGroupMemberService.createGroupMember(createReqVO));
37 } 47 }
38 48
  49 + // TODO @AI:踢人;
  50 +
  51 + // TODO @AI:退群;放在这里;避免 group 里面逻辑太多;
  52 +
39 @PutMapping("/update") 53 @PutMapping("/update")
40 @Operation(summary = "更新群成员") 54 @Operation(summary = "更新群成员")
41 @PreAuthorize("@ss.hasPermission('im:group-member:update')") 55 @PreAuthorize("@ss.hasPermission('im:group-member:update')")
@@ -70,4 +84,37 @@ public class ImGroupMemberController { @@ -70,4 +84,37 @@ public class ImGroupMemberController {
70 return success(BeanUtils.toBean(pageResult, ImGroupMemberRespVO.class)); 84 return success(BeanUtils.toBean(pageResult, ImGroupMemberRespVO.class));
71 } 85 }
72 86
73 -}  
74 \ No newline at end of file 87 \ No newline at end of file
  88 + // ========== 用户侧接口(登录用户自己看群成员,无需后台权限) ==========
  89 +
  90 + @GetMapping("/list")
  91 + @Operation(summary = "获得指定群的成员列表")
  92 + @Parameter(name = "groupId", description = "群编号", required = true, example = "1024")
  93 + public CommonResult<List<ImGroupMemberRespVO>> getGroupMemberList(@RequestParam("groupId") Long groupId) {
  94 + List<ImGroupMemberDO> members = imGroupMemberService.getGroupMemberListByGroupId(groupId);
  95 + if (members.isEmpty()) {
  96 + return success(Collections.emptyList());
  97 + }
  98 + // 聚合 AdminUser 的昵称 + 头像
  99 + List<Long> userIds = CollectionUtils.convertList(members, ImGroupMemberDO::getUserId);
  100 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
  101 + return success(CollectionUtils.convertList(members, m -> {
  102 + ImGroupMemberRespVO vo = BeanUtils.toBean(m, ImGroupMemberRespVO.class);
  103 + AdminUserRespDTO user = userMap.get(m.getUserId());
  104 + if (user != null) {
  105 + vo.setNickname(user.getNickname());
  106 + vo.setAvatar(user.getAvatar());
  107 + }
  108 + // showNickname 回落:优先群内备注 displayUserName,否则用用户昵称
  109 + if (m.getDisplayUserName() != null && !m.getDisplayUserName().isEmpty()) {
  110 + vo.setShowNickname(m.getDisplayUserName());
  111 + } else if (user != null) {
  112 + vo.setShowNickname(user.getNickname());
  113 + }
  114 + return vo;
  115 + }));
  116 + }
  117 +
  118 + // TODO @芋艿:【对齐】/group/members/remove 批量踢人接口第一期未实现。
  119 + // 需要:校验群主权限、批量更新 im_group_member 为 DISABLE + 设置 quitTime、通过 WebSocket 推送给被踢成员。
  120 +
  121 +}
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupRespVO.java
@@ -27,9 +27,15 @@ public class ImGroupRespVO { @@ -27,9 +27,15 @@ public class ImGroupRespVO {
27 @Schema(description = "是否封禁") 27 @Schema(description = "是否封禁")
28 private Boolean banned; 28 private Boolean banned;
29 29
  30 + @Schema(description = "封禁时间")
  31 + private LocalDateTime bannedTime;
  32 +
30 @Schema(description = "是否解散") 33 @Schema(description = "是否解散")
31 private Boolean dissolved; 34 private Boolean dissolved;
32 35
  36 + @Schema(description = "解散时间")
  37 + private LocalDateTime dissolvedTime;
  38 +
33 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) 39 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
34 private LocalDateTime createTime; 40 private LocalDateTime createTime;
35 41
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/member/ImGroupMemberRespVO.java
@@ -39,4 +39,15 @@ public class ImGroupMemberRespVO { @@ -39,4 +39,15 @@ public class ImGroupMemberRespVO {
39 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) 39 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
40 private LocalDateTime createTime; 40 private LocalDateTime createTime;
41 41
  42 + // ========== 聚合字段:用户侧接口按需填充(admin CRUD 查询不填) ==========
  43 +
  44 + @Schema(description = "用户昵称(聚合自 AdminUser,用户侧接口返回)", example = "芋道")
  45 + private String nickname;
  46 +
  47 + @Schema(description = "用户头像(聚合自 AdminUser,用户侧接口返回)")
  48 + private String avatar;
  49 +
  50 + @Schema(description = "展示昵称:优先 displayUserName,回落 nickname", example = "芋道")
  51 + private String showNickname;
  52 +
42 } 53 }
43 \ No newline at end of file 54 \ No newline at end of file
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupMemberMapper.java
1 package cn.iocoder.yudao.module.im.dal.mysql.group; 1 package cn.iocoder.yudao.module.im.dal.mysql.group;
2 2
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
3 import cn.iocoder.yudao.framework.common.pojo.PageResult; 4 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; 5 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; 6 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
@@ -7,6 +8,7 @@ import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMember @@ -7,6 +8,7 @@ import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMember
7 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; 8 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
8 import org.apache.ibatis.annotations.Mapper; 9 import org.apache.ibatis.annotations.Mapper;
9 10
  11 +import java.time.LocalDateTime;
10 import java.util.List; 12 import java.util.List;
11 13
12 /** 14 /**
@@ -39,4 +41,35 @@ public interface ImGroupMemberMapper extends BaseMapperX&lt;ImGroupMemberDO&gt; { @@ -39,4 +41,35 @@ public interface ImGroupMemberMapper extends BaseMapperX&lt;ImGroupMemberDO&gt; {
39 default List<ImGroupMemberDO> selectListByUserId(Long userId) { 41 default List<ImGroupMemberDO> selectListByUserId(Long userId) {
40 return selectList(new LambdaQueryWrapperX<ImGroupMemberDO>().eq(ImGroupMemberDO::getUserId, userId)); 42 return selectList(new LambdaQueryWrapperX<ImGroupMemberDO>().eq(ImGroupMemberDO::getUserId, userId));
41 } 43 }
  44 +
  45 + // TODO @AI:selectListByXXX;enabled 传入进来
  46 + default List<ImGroupMemberDO> selectEnabledListByGroupId(Long groupId) {
  47 + return selectList(new LambdaQueryWrapperX<ImGroupMemberDO>()
  48 + .eq(ImGroupMemberDO::getGroupId, groupId)
  49 + .eq(ImGroupMemberDO::getStatus, CommonStatusEnum.ENABLE.getStatus()));
  50 + }
  51 +
  52 + // TODO @AI:selectListByXXX;enabled 传入进来
  53 + default List<ImGroupMemberDO> selectEnabledListByUserId(Long userId) {
  54 + return selectList(new LambdaQueryWrapperX<ImGroupMemberDO>()
  55 + .eq(ImGroupMemberDO::getUserId, userId)
  56 + .eq(ImGroupMemberDO::getStatus, CommonStatusEnum.ENABLE.getStatus()));
  57 + }
  58 +
  59 + /**
  60 + * 查询用户已退群的成员记录
  61 + * <p>
  62 + * 当 {@code minQuitTime} 非空时额外按 {@code quitTime ≥ minQuitTime} 过滤。
  63 + *
  64 + * @param userId 用户编号
  65 + * @param minQuitTime 最早退群时间(含),可空
  66 + * @return 已退群成员记录列表
  67 + */
  68 + default List<ImGroupMemberDO> selectQuitListByUserId(Long userId, LocalDateTime minQuitTime) {
  69 + return selectList(new LambdaQueryWrapperX<ImGroupMemberDO>()
  70 + .eq(ImGroupMemberDO::getUserId, userId)
  71 + .eq(ImGroupMemberDO::getStatus, CommonStatusEnum.DISABLE.getStatus())
  72 + .geIfPresent(ImGroupMemberDO::getQuitTime, minQuitTime));
  73 + }
  74 +
42 } 75 }
43 \ No newline at end of file 76 \ No newline at end of file
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImpl.java
@@ -11,8 +11,10 @@ import cn.iocoder.yudao.module.im.service.websocket.dto.friend.FriendUpdateMessa @@ -11,8 +11,10 @@ import cn.iocoder.yudao.module.im.service.websocket.dto.friend.FriendUpdateMessa
11 import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO; 11 import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO;
12 import cn.iocoder.yudao.module.system.api.user.AdminUserApi; 12 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
13 import jakarta.annotation.Resource; 13 import jakarta.annotation.Resource;
  14 +import lombok.extern.slf4j.Slf4j;
14 import org.springframework.cache.annotation.CacheEvict; 15 import org.springframework.cache.annotation.CacheEvict;
15 import org.springframework.cache.annotation.Cacheable; 16 import org.springframework.cache.annotation.Cacheable;
  17 +import org.springframework.dao.DuplicateKeyException;
16 import org.springframework.stereotype.Service; 18 import org.springframework.stereotype.Service;
17 import org.springframework.transaction.annotation.Transactional; 19 import org.springframework.transaction.annotation.Transactional;
18 import org.springframework.validation.annotation.Validated; 20 import org.springframework.validation.annotation.Validated;
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberService.java
@@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMember @@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMember
6 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; 6 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
7 import jakarta.validation.Valid; 7 import jakarta.validation.Valid;
8 8
  9 +import java.time.LocalDateTime;
9 import java.util.List; 10 import java.util.List;
10 11
11 /** 12 /**
@@ -54,12 +55,12 @@ public interface ImGroupMemberService { @@ -54,12 +55,12 @@ public interface ImGroupMemberService {
54 PageResult<ImGroupMemberDO> getGroupMemberPage(ImGroupMemberPageReqVO pageReqVO); 55 PageResult<ImGroupMemberDO> getGroupMemberPage(ImGroupMemberPageReqVO pageReqVO);
55 56
56 /** 57 /**
57 - * 根据群组id查询群成员 58 + * 根据群组 id 查询群成员(包含所有状态)
58 * 59 *
59 * @param groupId 群组id 60 * @param groupId 群组id
60 * @return 群成员列表 61 * @return 群成员列表
61 */ 62 */
62 - List<ImGroupMemberDO> selectByGroupId(Long groupId); 63 + List<ImGroupMemberDO> getGroupMemberListByGroupId(Long groupId);
63 64
64 /** 65 /**
65 * 根据群编号和用户编号查询群成员 66 * 根据群编号和用户编号查询群成员
@@ -76,5 +77,39 @@ public interface ImGroupMemberService { @@ -76,5 +77,39 @@ public interface ImGroupMemberService {
76 * @param userId 用户编号 77 * @param userId 用户编号
77 * @return 群成员记录列表 78 * @return 群成员记录列表
78 */ 79 */
79 - List<ImGroupMemberDO> getGroupMembersByUserId(Long userId); 80 + List<ImGroupMemberDO> getGroupMemberListByUserId(Long userId);
  81 +
  82 + /**
  83 + * 根据群编号查询有效成员列表(仅 ENABLE 状态)
  84 + *
  85 + * @param groupId 群编号
  86 + * @return 有效群成员列表
  87 + */
  88 + List<ImGroupMemberDO> getActiveGroupMemberListByGroupId(Long groupId);
  89 +
  90 + /**
  91 + * 查询用户所在的所有群的有效成员记录(仅 ENABLE 状态)
  92 + *
  93 + * @param userId 用户编号
  94 + * @return 有效群成员记录列表
  95 + */
  96 + List<ImGroupMemberDO> getActiveGroupMemberListByUserId(Long userId);
  97 +
  98 + /**
  99 + * 查询用户已退群的群成员记录(DISABLE 状态)
  100 + *
  101 + * @param userId 用户编号,必传
  102 + * @param minQuitTime 最早退群时间(含),可空
  103 + * @return 已退群成员记录列表
  104 + */
  105 + List<ImGroupMemberDO> getQuitGroupMemberListByUserId(Long userId, LocalDateTime minQuitTime);
  106 +
  107 + /**
  108 + * 校验用户是否为群的有效成员
  109 + *
  110 + * @param groupId 群编号
  111 + * @param userId 用户编号
  112 + * @return 群成员记录
  113 + */
  114 + ImGroupMemberDO validateMemberInGroup(Long groupId, Long userId);
80 } 115 }
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberServiceImpl.java
1 package cn.iocoder.yudao.module.im.service.group; 1 package cn.iocoder.yudao.module.im.service.group;
2 2
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
3 import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberPageReqVO; 4 import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberPageReqVO;
4 import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberSaveReqVO; 5 import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberSaveReqVO;
5 import org.springframework.stereotype.Service; 6 import org.springframework.stereotype.Service;
@@ -12,6 +13,7 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils; @@ -12,6 +13,7 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
12 13
13 import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMemberMapper; 14 import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMemberMapper;
14 15
  16 +import java.time.LocalDateTime;
15 import java.util.List; 17 import java.util.List;
16 18
17 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 19 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -72,7 +74,7 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService { @@ -72,7 +74,7 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService {
72 } 74 }
73 75
74 @Override 76 @Override
75 - public List<ImGroupMemberDO> selectByGroupId(Long groupId) { 77 + public List<ImGroupMemberDO> getGroupMemberListByGroupId(Long groupId) {
76 return imGroupMemberMapper.selectListByGroupId(groupId); 78 return imGroupMemberMapper.selectListByGroupId(groupId);
77 } 79 }
78 80
@@ -82,8 +84,32 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService { @@ -82,8 +84,32 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService {
82 } 84 }
83 85
84 @Override 86 @Override
85 - public List<ImGroupMemberDO> getGroupMembersByUserId(Long userId) { 87 + public List<ImGroupMemberDO> getGroupMemberListByUserId(Long userId) {
86 return imGroupMemberMapper.selectListByUserId(userId); 88 return imGroupMemberMapper.selectListByUserId(userId);
87 } 89 }
88 90
  91 + @Override
  92 + public List<ImGroupMemberDO> getActiveGroupMemberListByGroupId(Long groupId) {
  93 + return imGroupMemberMapper.selectEnabledListByGroupId(groupId);
  94 + }
  95 +
  96 + @Override
  97 + public List<ImGroupMemberDO> getActiveGroupMemberListByUserId(Long userId) {
  98 + return imGroupMemberMapper.selectEnabledListByUserId(userId);
  99 + }
  100 +
  101 + @Override
  102 + public List<ImGroupMemberDO> getQuitGroupMemberListByUserId(Long userId, LocalDateTime minQuitTime) {
  103 + return imGroupMemberMapper.selectQuitListByUserId(userId, minQuitTime);
  104 + }
  105 +
  106 + @Override
  107 + public ImGroupMemberDO validateMemberInGroup(Long groupId, Long userId) {
  108 + ImGroupMemberDO member = imGroupMemberMapper.selectByGroupIdAndUserId(groupId, userId);
  109 + if (member == null || CommonStatusEnum.DISABLE.getStatus().equals(member.getStatus())) {
  110 + throw exception(GROUP_MEMBER_NOT_IN_GROUP);
  111 + }
  112 + return member;
  113 + }
  114 +
89 } 115 }
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupService.java
@@ -5,6 +5,8 @@ import cn.iocoder.yudao.module.im.controller.admin.group.vo.*; @@ -5,6 +5,8 @@ import cn.iocoder.yudao.module.im.controller.admin.group.vo.*;
5 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; 5 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
6 import cn.iocoder.yudao.framework.common.pojo.PageResult; 6 import cn.iocoder.yudao.framework.common.pojo.PageResult;
7 7
  8 +import java.util.List;
  9 +
8 /** 10 /**
9 * 群 Service 接口 11 * 群 Service 接口
10 * 12 *
@@ -58,4 +60,14 @@ public interface ImGroupService { @@ -58,4 +60,14 @@ public interface ImGroupService {
58 */ 60 */
59 ImGroupDO validateGroupExists(Long groupId); 61 ImGroupDO validateGroupExists(Long groupId);
60 62
61 -}  
62 \ No newline at end of file 63 \ No newline at end of file
  64 + /**
  65 + * 获取指定用户加入的所有"有效"群(未退群且未封禁/解散)
  66 + * <p>
  67 + * 返回当前登录用户在群内身份仍有效的所有群。
  68 + *
  69 + * @param userId 用户编号
  70 + * @return 群列表
  71 + */
  72 + List<ImGroupDO> getMyGroupList(Long userId);
  73 +
  74 +}
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImpl.java
1 package cn.iocoder.yudao.module.im.service.group; 1 package cn.iocoder.yudao.module.im.service.group;
2 2
  3 +import cn.hutool.core.collection.CollUtil;
3 import org.springframework.stereotype.Service; 4 import org.springframework.stereotype.Service;
4 import jakarta.annotation.Resource; 5 import jakarta.annotation.Resource;
5 import org.springframework.validation.annotation.Validated; 6 import org.springframework.validation.annotation.Validated;
@@ -22,6 +23,7 @@ import java.util.Set; @@ -22,6 +23,7 @@ import java.util.Set;
22 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 23 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
23 import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*; 24 import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*;
24 25
  26 +// TODO @芋艿:群主的调整,暂时不考虑。后续在弄;
25 /** 27 /**
26 * 群 Service 实现类 28 * 群 Service 实现类
27 * 29 *
@@ -37,32 +39,53 @@ public class ImGroupServiceImpl implements ImGroupService { @@ -37,32 +39,53 @@ public class ImGroupServiceImpl implements ImGroupService {
37 @Resource 39 @Resource
38 private ImGroupMemberMapper imGroupMemberMapper; 40 private ImGroupMemberMapper imGroupMemberMapper;
39 41
  42 + // TODO @AI:返回对象;
  43 + // TODO @AI:要不要拆分 VO;ImGroupCreateReqVO(name)
40 @Override 44 @Override
41 public Long createGroup(ImGroupSaveReqVO createReqVO) { 45 public Long createGroup(ImGroupSaveReqVO createReqVO) {
42 // 插入 46 // 插入
43 ImGroupDO group = BeanUtils.toBean(createReqVO, ImGroupDO.class); 47 ImGroupDO group = BeanUtils.toBean(createReqVO, ImGroupDO.class);
44 imGroupMapper.insert(group); 48 imGroupMapper.insert(group);
45 - // 返回 49 +
  50 + // TODO @AI:群主 GroupMember;
  51 +
  52 + // TODO 多终端推送消息(group message)
46 return group.getId(); 53 return group.getId();
47 } 54 }
48 55
  56 + // TODO @AI:要不要拆分 VO;ImGroupUpdateReqVO(name、avatar、notice);我在本群的昵称/群名备注;
  57 + // TODO 1. 需要校验群主:name、avatar、notice;如果修改群信息,是不是要群发消息?(群信息变更消息);
  58 + // TODO 2. 需要校验群成员:我在本群的昵称/群名备注
  59 + // TODO @AI:返回 返回对象;
49 @Override 60 @Override
50 public void updateGroup(ImGroupSaveReqVO updateReqVO) { 61 public void updateGroup(ImGroupSaveReqVO updateReqVO) {
51 // 校验存在 62 // 校验存在
52 validateGroupExists(updateReqVO.getId()); 63 validateGroupExists(updateReqVO.getId());
  64 +
53 // 更新 65 // 更新
54 ImGroupDO updateObj = BeanUtils.toBean(updateReqVO, ImGroupDO.class); 66 ImGroupDO updateObj = BeanUtils.toBean(updateReqVO, ImGroupDO.class);
55 imGroupMapper.updateById(updateObj); 67 imGroupMapper.updateById(updateObj);
56 } 68 }
57 69
  70 + // TODO @AI:dissolve 解散群;
58 @Override 71 @Override
59 public void deleteGroup(Long id) { 72 public void deleteGroup(Long id) {
60 // 校验存在 73 // 校验存在
61 validateGroupExists(id); 74 validateGroupExists(id);
  75 + // TODO @AI:群主才可以;
  76 +
  77 + // TODO @AI:清理已读缓存;
  78 +
  79 + // TODO @AI:推送群解散的提示;
  80 +
  81 + // TODO @AI:推送群信息变化;
  82 +
62 // 删除 83 // 删除
63 imGroupMapper.deleteById(id); 84 imGroupMapper.deleteById(id);
64 } 85 }
65 86
  87 + // TODO @AI:quit 退群;
  88 +
66 @Override 89 @Override
67 public ImGroupDO validateGroupExists(Long id) { 90 public ImGroupDO validateGroupExists(Long id) {
68 ImGroupDO group = imGroupMapper.selectById(id); 91 ImGroupDO group = imGroupMapper.selectById(id);
@@ -78,6 +101,10 @@ public class ImGroupServiceImpl implements ImGroupService { @@ -78,6 +101,10 @@ public class ImGroupServiceImpl implements ImGroupService {
78 return group; 101 return group;
79 } 102 }
80 103
  104 + // TODO @AI:add 加群;
  105 +
  106 + // TODO @AI:remove 踢人;
  107 +
81 @Override 108 @Override
82 public ImGroupDO getGroup(Long id) { 109 public ImGroupDO getGroup(Long id) {
83 return imGroupMapper.selectById(id); 110 return imGroupMapper.selectById(id);
@@ -92,14 +119,15 @@ public class ImGroupServiceImpl implements ImGroupService { @@ -92,14 +119,15 @@ public class ImGroupServiceImpl implements ImGroupService {
92 public List<ImGroupDO> getMyGroupList(Long userId) { 119 public List<ImGroupDO> getMyGroupList(Long userId) {
93 // 1. 查用户所在的、仍有效的群成员记录(仅 ENABLE 状态) 120 // 1. 查用户所在的、仍有效的群成员记录(仅 ENABLE 状态)
94 List<ImGroupMemberDO> members = imGroupMemberMapper.selectEnabledListByUserId(userId); 121 List<ImGroupMemberDO> members = imGroupMemberMapper.selectEnabledListByUserId(userId);
95 - if (members.isEmpty()) { 122 + if (CollUtil.isNotEmpty(members)) {
96 return Collections.emptyList(); 123 return Collections.emptyList();
97 } 124 }
98 Set<Long> groupIds = CollectionUtils.convertSet(members, ImGroupMemberDO::getGroupId); 125 Set<Long> groupIds = CollectionUtils.convertSet(members, ImGroupMemberDO::getGroupId);
99 - if (groupIds.isEmpty()) { 126 + if (CollUtil.isEmpty(groupIds)) {
100 return Collections.emptyList(); 127 return Collections.emptyList();
101 } 128 }
102 // 2. 查群并过滤掉已封禁/解散的 129 // 2. 查群并过滤掉已封禁/解散的
  130 + // TODO @AI:查询的时候,就过滤掉!不要内存过滤;
103 List<ImGroupDO> groups = imGroupMapper.selectByIds(groupIds); 131 List<ImGroupDO> groups = imGroupMapper.selectByIds(groupIds);
104 groups.removeIf(g -> Boolean.TRUE.equals(g.getBanned()) || Boolean.TRUE.equals(g.getDissolved())); 132 groups.removeIf(g -> Boolean.TRUE.equals(g.getBanned()) || Boolean.TRUE.equals(g.getDissolved()));
105 return groups; 133 return groups;