Commit 443d91d7e3a9818e88de3aa9b99489a875509ab5

Authored by YunaiV
1 parent e4a6cc93

✨ feat(group): 添加群成员邀请和更新功能

新增群成员邀请接口,允许用户将其他用户加入群组。同时,更新群成员信息的接口也进行了调整,以支持群内昵称和群名备注的修改。这些改动提升了群组管理的灵活性和用户体验。
Showing 22 changed files with 412 additions and 249 deletions
yudao-module-im/yudao-module-im-api/src/main/java/cn/iocoder/yudao/module/im/enums/ErrorCodeConstants.java
... ... @@ -29,6 +29,7 @@ public interface ErrorCodeConstants {
29 29 ErrorCode GROUP_NOT_EXISTS = new ErrorCode(1_040_400_000, "群不存在");
30 30 ErrorCode GROUP_BANNED = new ErrorCode(1_040_400_001, "群已被封禁");
31 31 ErrorCode GROUP_DISSOLVED = new ErrorCode(1_040_400_002, "群已解散");
  32 + ErrorCode GROUP_NOT_OWNER = new ErrorCode(1_040_400_003, "仅群主可执行该操作");
32 33  
33 34 // ========== 群成员 (1-040-500-000) ==========
34 35 ErrorCode GROUP_MEMBER_NOT_EXISTS = new ErrorCode(1_040_500_000, "群成员不存在");
... ...
yudao-module-im/yudao-module-im-api/src/main/java/cn/iocoder/yudao/module/im/enums/ImCommonConstants.java
... ... @@ -34,4 +34,15 @@ public interface ImCommonConstants {
34 34 */
35 35 int MESSAGE_GROUP_PULL_MAX_DAYS = 30;
36 36  
  37 + // ==================== 好友相关提示消息 ====================
  38 +
  39 + /**
  40 + * 好友添加系统提示
  41 + */
  42 + String FRIEND_ADD_TIP_MESSAGE = "你们已成为好友,现在可以开始聊天了";
  43 + /**
  44 + * 好友删除系统提示
  45 + */
  46 + String FRIEND_DELETE_TIP_MESSAGE = "你们的好友关系已被解除";
  47 +
37 48 }
... ...
yudao-module-im/yudao-module-im-api/src/main/java/cn/iocoder/yudao/module/im/enums/message/ImMessageTypeEnum.java
... ... @@ -15,11 +15,16 @@ import java.util.Arrays;
15 15 @AllArgsConstructor
16 16 public enum ImMessageTypeEnum implements ArrayValuable<Integer> {
17 17  
18   - TEXT(0, "文本"),
19   - IMAGE(1, "图片"),
20   - FILE(2, "文件"),
21   - VOICE(3, "语音"),
22   - VIDEO(4, "视频"),
  18 + TEXT(0, "文本"), // 对应 TextMessage 类
  19 + IMAGE(1, "图片"), // 对应 ImageMessage 类
  20 + FILE(2, "文件"), // 对应 FileMessage 类
  21 + VOICE(3, "语音"), // 对应 AudioMessage 类
  22 + VIDEO(4, "视频"), // 对应 VideoMessage 类
  23 +
  24 + RECALL(10, "撤回"), // 对应 RecallMessage 类
  25 + READ(11, "已读"), // 暂无
  26 + RECEIPT(12, "回执"), // 暂无
  27 + TIP_TEXT(21, "提示文本"), // 对应 TextMessage 类
23 28  
24 29 RECALL(10, "撤回"),
25 30 READ(11, "已读"),
... ...
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupController.java
1 1 package cn.iocoder.yudao.module.im.controller.admin.group;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
4   -import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 4 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;
  5 +import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupCreateReqVO;
8 6 import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupRespVO;
9   -import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupSaveReqVO;
  7 +import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupUpdateReqVO;
10 8 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
11 9 import cn.iocoder.yudao.module.im.service.group.ImGroupService;
12 10 import io.swagger.v3.oas.annotations.Operation;
... ... @@ -21,6 +19,7 @@ import org.springframework.web.bind.annotation.*;
21 19 import java.util.List;
22 20  
23 21 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  22 +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId;
24 23  
25 24 @Tag(name = "管理后台 - 群")
26 25 @RestController
... ... @@ -34,24 +33,25 @@ public class ImGroupController {
34 33 @PostMapping("/create")
35 34 @Operation(summary = "创建群")
36 35 @PreAuthorize("@ss.hasPermission('im:group:create')")
37   - public CommonResult<Long> createGroup(@Valid @RequestBody ImGroupSaveReqVO createReqVO) {
38   - return success(imGroupService.createGroup(createReqVO));
  36 + public CommonResult<ImGroupRespVO> createGroup(@Valid @RequestBody ImGroupCreateReqVO createReqVO) {
  37 + ImGroupDO group = imGroupService.createGroup(createReqVO, getLoginUserId());
  38 + return success(BeanUtils.toBean(group, ImGroupRespVO.class));
39 39 }
40 40  
41 41 @PutMapping("/update")
42 42 @Operation(summary = "更新群")
43 43 @PreAuthorize("@ss.hasPermission('im:group:update')")
44   - public CommonResult<Boolean> updateGroup(@Valid @RequestBody ImGroupSaveReqVO updateReqVO) {
45   - imGroupService.updateGroup(updateReqVO);
46   - return success(true);
  44 + public CommonResult<ImGroupRespVO> updateGroup(@Valid @RequestBody ImGroupUpdateReqVO updateReqVO) {
  45 + ImGroupDO group = imGroupService.updateGroup(updateReqVO, getLoginUserId());
  46 + return success(BeanUtils.toBean(group, ImGroupRespVO.class));
47 47 }
48 48  
49   - @DeleteMapping("/delete")
50   - @Operation(summary = "删除群")
51   - @Parameter(name = "id", description = "编号", required = true)
  49 + @DeleteMapping("/dissolve")
  50 + @Operation(summary = "解散群")
  51 + @Parameter(name = "id", description = "群编号", required = true)
52 52 @PreAuthorize("@ss.hasPermission('im:group:delete')")
53   - public CommonResult<Boolean> deleteGroup(@RequestParam("id") Long id) {
54   - imGroupService.deleteGroup(id);
  53 + public CommonResult<Boolean> dissolveGroup(@RequestParam("id") Long id) {
  54 + imGroupService.dissolveGroup(id, getLoginUserId());
55 55 return success(true);
56 56 }
57 57  
... ... @@ -64,35 +64,11 @@ public class ImGroupController {
64 64 return success(BeanUtils.toBean(group, ImGroupRespVO.class));
65 65 }
66 66  
67   - // TODO @AI:去掉这个接口;
68   - @GetMapping("/page")
69   - @Operation(summary = "获得群分页")
70   - @PreAuthorize("@ss.hasPermission('im:group:query')")
71   - public CommonResult<PageResult<ImGroupRespVO>> getGroupPage(@Valid ImGroupPageReqVO pageReqVO) {
72   - PageResult<ImGroupDO> pageResult = imGroupService.getGroupPage(pageReqVO);
73   - return success(BeanUtils.toBean(pageResult, ImGroupRespVO.class));
74   - }
75   -
76   - // ========== 用户侧接口(登录用户自己的群,无需后台权限) ==========
77   -
78   - // TODO @AI:直接 list;
79   - @GetMapping("/list-my")
  67 + @GetMapping("/list")
80 68 @Operation(summary = "获得当前登录用户的群列表")
81 69 public CommonResult<List<ImGroupRespVO>> getMyGroupList() {
82   - Long userId = SecurityFrameworkUtils.getLoginUserId();
83   - List<ImGroupDO> groups = imGroupService.getMyGroupList(userId);
  70 + List<ImGroupDO> groups = imGroupService.getActiveGroupList(getLoginUserId());
84 71 return success(BeanUtils.toBean(groups, ImGroupRespVO.class));
85 72 }
86 73  
87   - // TODO @芋艿:【对齐】/group/create、/group/modify、/group/invite、/group/quit、/group/delete/{id}
88   - // 这些用户侧接口第一期暂未实现。需要:
89   - // 1. createGroup(vo):需同时插入 im_group + 批量插入 im_group_member;还要给群成员推送 FRIEND_INVITE 类消息
90   - // 2. modifyGroup(vo):允许修改 name / notice / avatar / remarkGroupName / remarkNickName
91   - // (其中 remarkGroupName / remarkNickName 是 im_group_member 的字段,不是 im_group 的)
92   - // 3. invite(groupId, userIds):批量插入 im_group_member 为 ENABLE;给被邀请人推送入群通知
93   - // 4. quitGroup(groupId):校验非群主才能退,然后把自己的 im_group_member 标为 DISABLE + quitTime
94   - // 5. removeGroupMembers(groupId, userIds):群主权限校验 + 把成员标为 DISABLE
95   - // 以上都需要 WebSocket 推送对应事件(FRIEND_INVITE / MEMBER_QUIT / MEMBER_REMOVE 等),
96   - // 涉及 `ImWsEventType` 扩展 + `ImWebSocketPusher` 新增事件推送方法,待统一设计后补上。
97   -
98 74 }
... ...
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupMemberController.java
1 1 package cn.iocoder.yudao.module.im.controller.admin.group;
2 2  
3 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.collection.CollectionUtils;
  4 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
6 5 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
7   -import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberPageReqVO;
  6 +import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberInviteReqVO;
8 7 import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberRespVO;
9   -import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberSaveReqVO;
  8 +import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberUpdateReqVO;
10 9 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
11 10 import cn.iocoder.yudao.module.im.service.group.ImGroupMemberService;
12 11 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
... ... @@ -20,11 +19,11 @@ import org.springframework.security.access.prepost.PreAuthorize;
20 19 import org.springframework.validation.annotation.Validated;
21 20 import org.springframework.web.bind.annotation.*;
22 21  
23   -import java.util.Collections;
24 22 import java.util.List;
25 23 import java.util.Map;
26 24  
27 25 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  26 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
28 27  
29 28 @Tag(name = "管理后台 - 群成员")
30 29 @RestController
... ... @@ -38,32 +37,27 @@ public class ImGroupMemberController {
38 37 @Resource
39 38 private AdminUserApi adminUserApi;
40 39  
41   - // TODO @AI:add 加人;
42   - @PostMapping("/create")
43   - @Operation(summary = "创建群成员")
  40 + @PostMapping("/invite")
  41 + @Operation(summary = "邀请用户加入群")
44 42 @PreAuthorize("@ss.hasPermission('im:group-member:create')")
45   - public CommonResult<Long> createGroupMember(@Valid @RequestBody ImGroupMemberSaveReqVO createReqVO) {
46   - return success(imGroupMemberService.createGroupMember(createReqVO));
  43 + public CommonResult<Long> inviteGroupMember(@Valid @RequestBody ImGroupMemberInviteReqVO inviteReqVO) {
  44 + return success(imGroupMemberService.inviteGroupMember(inviteReqVO));
47 45 }
48 46  
49   - // TODO @AI:踢人;
50   -
51   - // TODO @AI:退群;放在这里;避免 group 里面逻辑太多;
52   -
53 47 @PutMapping("/update")
54 48 @Operation(summary = "更新群成员")
55 49 @PreAuthorize("@ss.hasPermission('im:group-member:update')")
56   - public CommonResult<Boolean> updateGroupMember(@Valid @RequestBody ImGroupMemberSaveReqVO updateReqVO) {
  50 + public CommonResult<Boolean> updateGroupMember(@Valid @RequestBody ImGroupMemberUpdateReqVO updateReqVO) {
57 51 imGroupMemberService.updateGroupMember(updateReqVO);
58 52 return success(true);
59 53 }
60 54  
61   - @DeleteMapping("/delete")
62   - @Operation(summary = "删除群成员")
63   - @Parameter(name = "id", description = "编号", required = true)
  55 + @DeleteMapping("/remove")
  56 + @Operation(summary = "移除群成员(踢人)")
  57 + @Parameter(name = "id", description = "群成员编号", required = true)
64 58 @PreAuthorize("@ss.hasPermission('im:group-member:delete')")
65   - public CommonResult<Boolean> deleteGroupMember(@RequestParam("id") Long id) {
66   - imGroupMemberService.deleteGroupMember(id);
  59 + public CommonResult<Boolean> removeGroupMember(@RequestParam("id") Long id) {
  60 + imGroupMemberService.removeGroupMember(id);
67 61 return success(true);
68 62 }
69 63  
... ... @@ -76,45 +70,20 @@ public class ImGroupMemberController {
76 70 return success(BeanUtils.toBean(groupMember, ImGroupMemberRespVO.class));
77 71 }
78 72  
79   - @GetMapping("/page")
80   - @Operation(summary = "获得群成员分页")
81   - @PreAuthorize("@ss.hasPermission('im:group-member:query')")
82   - public CommonResult<PageResult<ImGroupMemberRespVO>> getGroupMemberPage(@Valid ImGroupMemberPageReqVO pageReqVO) {
83   - PageResult<ImGroupMemberDO> pageResult = imGroupMemberService.getGroupMemberPage(pageReqVO);
84   - return success(BeanUtils.toBean(pageResult, ImGroupMemberRespVO.class));
85   - }
86   -
87   - // ========== 用户侧接口(登录用户自己看群成员,无需后台权限) ==========
88   -
89 73 @GetMapping("/list")
90 74 @Operation(summary = "获得指定群的成员列表")
91 75 @Parameter(name = "groupId", description = "群编号", required = true, example = "1024")
92 76 public CommonResult<List<ImGroupMemberRespVO>> getGroupMemberList(@RequestParam("groupId") Long groupId) {
93 77 List<ImGroupMemberDO> members = imGroupMemberService.getGroupMemberListByGroupId(groupId);
94   - if (members.isEmpty()) {
95   - return success(Collections.emptyList());
96   - }
97   - // 聚合 AdminUser 的昵称 + 头像
98   - List<Long> userIds = CollectionUtils.convertList(members, ImGroupMemberDO::getUserId);
99   - Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
100   - return success(CollectionUtils.convertList(members, m -> {
  78 + // 批量聚合 AdminUser 信息(昵称 / 头像)
  79 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
  80 + convertList(members, ImGroupMemberDO::getUserId));
  81 + return success(convertList(members, m -> {
101 82 ImGroupMemberRespVO vo = BeanUtils.toBean(m, ImGroupMemberRespVO.class);
102   - AdminUserRespDTO user = userMap.get(m.getUserId());
103   - if (user != null) {
104   - vo.setNickname(user.getNickname());
105   - vo.setAvatar(user.getAvatar());
106   - }
107   - // showNickname 回落:优先群内备注 displayUserName,否则用用户昵称
108   - if (m.getDisplayUserName() != null && !m.getDisplayUserName().isEmpty()) {
109   - vo.setShowNickname(m.getDisplayUserName());
110   - } else if (user != null) {
111   - vo.setShowNickname(user.getNickname());
112   - }
  83 + MapUtils.findAndThen(userMap, m.getUserId(), user ->
  84 + vo.setNickname(user.getNickname()).setAvatar(user.getAvatar()));
113 85 return vo;
114 86 }));
115 87 }
116 88  
117   - // TODO @芋艿:【对齐】/group/members/remove 批量踢人接口第一期未实现。
118   - // 需要:校验群主权限、批量更新 im_group_member 为 DISABLE + 设置 quitTime、通过 WebSocket 推送给被踢成员。
119   -
120 89 }
... ...
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupCreateReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.group.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotBlank;
  5 +import lombok.Data;
  6 +
  7 +@Schema(description = "管理后台 - 群创建 Request VO")
  8 +@Data
  9 +public class ImGroupCreateReqVO {
  10 +
  11 + @Schema(description = "群名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道技术交流群")
  12 + @NotBlank(message = "群名称不能为空")
  13 + private String name;
  14 +
  15 +}
... ...
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupRespVO.java
... ... @@ -30,8 +30,8 @@ public class ImGroupRespVO {
30 30 @Schema(description = "封禁时间")
31 31 private LocalDateTime bannedTime;
32 32  
33   - @Schema(description = "是否解散")
34   - private Boolean dissolved;
  33 + @Schema(description = "群状态", requiredMode = Schema.RequiredMode.REQUIRED)
  34 + private Integer status;
35 35  
36 36 @Schema(description = "解散时间")
37 37 private LocalDateTime dissolvedTime;
... ...
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupUpdateReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.group.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotNull;
  5 +import lombok.Data;
  6 +
  7 +@Schema(description = "管理后台 - 群更新 Request VO")
  8 +@Data
  9 +public class ImGroupUpdateReqVO {
  10 +
  11 + @Schema(description = "群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1003")
  12 + @NotNull(message = "群编号不能为空")
  13 + private Long id;
  14 +
  15 + @Schema(description = "群名称", example = "芋道技术交流群")
  16 + private String name;
  17 +
  18 + @Schema(description = "群头像")
  19 + private String avatar;
  20 +
  21 + @Schema(description = "群公告")
  22 + private String notice;
  23 +
  24 + // ========== 以下字段来自 im_group_member 表(当前用户在该群的个人设置) ==========
  25 +
  26 + @Schema(description = "我在本群的昵称")
  27 + private String displayUserName;
  28 +
  29 + @Schema(description = "群名备注")
  30 + private String displayGroupName;
  31 +
  32 +}
... ...
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/member/ImGroupMemberInviteReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.group.vo.member;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotNull;
  5 +import lombok.Data;
  6 +
  7 +@Schema(description = "管理后台 - 群成员邀请 Request VO")
  8 +@Data
  9 +public class ImGroupMemberInviteReqVO {
  10 +
  11 + @Schema(description = "群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13279")
  12 + @NotNull(message = "群编号不能为空")
  13 + private Long groupId;
  14 +
  15 + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "21730")
  16 + @NotNull(message = "用户编号不能为空")
  17 + private Long userId;
  18 +
  19 +}
... ...
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/member/ImGroupMemberRespVO.java
... ... @@ -39,15 +39,12 @@ public class ImGroupMemberRespVO {
39 39 @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
40 40 private LocalDateTime createTime;
41 41  
42   - // ========== 聚合字段:用户侧接口按需填充(admin CRUD 查询不填) ==========
  42 + // ========== 关联 AdminUser 的字段 ==========
43 43  
44   - @Schema(description = "用户昵称(聚合自 AdminUser,用户侧接口返回)", example = "芋道")
  44 + @Schema(description = "用户昵称", example = "芋道")
45 45 private String nickname;
46 46  
47   - @Schema(description = "用户头像(聚合自 AdminUser,用户侧接口返回)")
  47 + @Schema(description = "用户头像")
48 48 private String avatar;
49 49  
50   - @Schema(description = "展示昵称:优先 displayUserName,回落 nickname", example = "芋道")
51   - private String showNickname;
52   -
53 50 }
54 51 \ No newline at end of file
... ...
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/member/ImGroupMemberUpdateReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.group.vo.member;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotNull;
  5 +import lombok.Data;
  6 +
  7 +@Schema(description = "管理后台 - 群成员更新 Request VO")
  8 +@Data
  9 +public class ImGroupMemberUpdateReqVO {
  10 +
  11 + @Schema(description = "群成员编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17071")
  12 + @NotNull(message = "群成员编号不能为空")
  13 + private Long id;
  14 +
  15 + @Schema(description = "是否免打扰")
  16 + private Boolean muted;
  17 +
  18 +}
... ...
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/group/ImGroupDO.java
1 1 package cn.iocoder.yudao.module.im.dal.dataobject.group;
2 2  
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
3 4 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
4 5 import com.baomidou.mybatisplus.annotation.KeySequence;
5 6 import com.baomidou.mybatisplus.annotation.TableId;
... ... @@ -58,11 +59,13 @@ public class ImGroupDO extends BaseDO {
58 59 * 封禁时间
59 60 */
60 61 private LocalDateTime bannedTime;
61   - // TODO @AI:status 字段,枚举 CommonStatusEnum,表示正常 / 解散;如果是解散状态,则不允许任何操作了。
62 62 /**
63   - * 是否解散
  63 + * 群状态
  64 + * <p>
  65 + * 枚举 {@link CommonStatusEnum}
  66 + * ENABLE = 正常,DISABLE = 已解散
64 67 */
65   - private Boolean dissolved;
  68 + private Integer status;
66 69 /**
67 70 * 解散时间
68 71 */
... ...
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupMapper.java
1 1 package cn.iocoder.yudao.module.im.dal.mysql.group;
2 2  
3   -import cn.iocoder.yudao.framework.common.pojo.PageResult;
4   -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
5 3 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  4 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
6 5 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
7 6 import org.apache.ibatis.annotations.Mapper;
8   -import cn.iocoder.yudao.module.im.controller.admin.group.vo.*;
  7 +
  8 +import java.util.Collection;
  9 +import java.util.List;
9 10  
10 11 /**
11 12 * IM 群 Mapper
... ... @@ -15,13 +16,19 @@ import cn.iocoder.yudao.module.im.controller.admin.group.vo.*;
15 16 @Mapper
16 17 public interface ImGroupMapper extends BaseMapperX<ImGroupDO> {
17 18  
18   - default PageResult<ImGroupDO> selectPage(ImGroupPageReqVO reqVO) {
19   - return selectPage(reqVO, new LambdaQueryWrapperX<ImGroupDO>()
20   - .likeIfPresent(ImGroupDO::getName, reqVO.getName())
21   - .eqIfPresent(ImGroupDO::getOwnerUserId, reqVO.getOwnerUserId())
22   - .eqIfPresent(ImGroupDO::getNotice, reqVO.getNotice())
23   - .betweenIfPresent(ImGroupDO::getCreateTime, reqVO.getCreateTime())
24   - .orderByDesc(ImGroupDO::getId));
  19 + /**
  20 + * 根据群编号批量查询群,支持按状态和封禁过滤
  21 + *
  22 + * @param ids 群编号集合
  23 + * @param status 群状态(可空,空则不过滤)
  24 + * @param banned 是否封禁(可空,空则不过滤)
  25 + * @return 群列表
  26 + */
  27 + default List<ImGroupDO> selectListByIds(Collection<Long> ids, Integer status, Boolean banned) {
  28 + return selectList(new LambdaQueryWrapperX<ImGroupDO>()
  29 + .in(ImGroupDO::getId, ids)
  30 + .eqIfPresent(ImGroupDO::getStatus, status)
  31 + .eqIfPresent(ImGroupDO::getBanned, banned));
25 32 }
26 33  
27 34 }
28 35 \ 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 1 package cn.iocoder.yudao.module.im.dal.mysql.group;
2 2  
3 3 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
4   -import cn.iocoder.yudao.framework.common.pojo.PageResult;
5 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
6 5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
7   -import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberPageReqVO;
8 6 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
9 7 import org.apache.ibatis.annotations.Mapper;
10 8  
... ... @@ -19,15 +17,6 @@ import java.util.List;
19 17 @Mapper
20 18 public interface ImGroupMemberMapper extends BaseMapperX<ImGroupMemberDO> {
21 19  
22   - default PageResult<ImGroupMemberDO> selectPage(ImGroupMemberPageReqVO reqVO) {
23   - return selectPage(reqVO, new LambdaQueryWrapperX<ImGroupMemberDO>()
24   - .eqIfPresent(ImGroupMemberDO::getGroupId, reqVO.getGroupId())
25   - .eqIfPresent(ImGroupMemberDO::getUserId, reqVO.getUserId())
26   - .eqIfPresent(ImGroupMemberDO::getStatus, reqVO.getStatus())
27   - .betweenIfPresent(ImGroupMemberDO::getCreateTime, reqVO.getCreateTime())
28   - .orderByDesc(ImGroupMemberDO::getId));
29   - }
30   -
31 20 default List<ImGroupMemberDO> selectListByGroupId(Long groupId) {
32 21 return selectList(new LambdaQueryWrapperX<ImGroupMemberDO>().eq(ImGroupMemberDO::getGroupId, groupId));
33 22 }
... ... @@ -42,18 +31,16 @@ public interface ImGroupMemberMapper extends BaseMapperX&lt;ImGroupMemberDO&gt; {
42 31 return selectList(new LambdaQueryWrapperX<ImGroupMemberDO>().eq(ImGroupMemberDO::getUserId, userId));
43 32 }
44 33  
45   - // TODO @AI:selectListByXXX;enabled 传入进来
46   - default List<ImGroupMemberDO> selectEnabledListByGroupId(Long groupId) {
  34 + default List<ImGroupMemberDO> selectListByGroupIdAndStatus(Long groupId, Integer status) {
47 35 return selectList(new LambdaQueryWrapperX<ImGroupMemberDO>()
48 36 .eq(ImGroupMemberDO::getGroupId, groupId)
49   - .eq(ImGroupMemberDO::getStatus, CommonStatusEnum.ENABLE.getStatus()));
  37 + .eq(ImGroupMemberDO::getStatus, status));
50 38 }
51 39  
52   - // TODO @AI:selectListByXXX;enabled 传入进来
53   - default List<ImGroupMemberDO> selectEnabledListByUserId(Long userId) {
  40 + default List<ImGroupMemberDO> selectListByUserIdAndStatus(Long userId, Integer status) {
54 41 return selectList(new LambdaQueryWrapperX<ImGroupMemberDO>()
55 42 .eq(ImGroupMemberDO::getUserId, userId)
56   - .eq(ImGroupMemberDO::getStatus, CommonStatusEnum.ENABLE.getStatus()));
  43 + .eq(ImGroupMemberDO::getStatus, status));
57 44 }
58 45  
59 46 /**
... ...
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/redis/RedisKeyConstants.java
... ... @@ -23,4 +23,12 @@ public interface RedisKeyConstants {
23 23 */
24 24 String FRIEND = "friend";
25 25  
  26 + /**
  27 + * 群信息缓存
  28 + * <p>
  29 + * KEY 格式:group:{groupId}
  30 + * VALUE 数据类型:ImGroupDO
  31 + */
  32 + String GROUP = "group";
  33 +
26 34 }
... ...
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImpl.java
... ... @@ -14,6 +14,7 @@ import jakarta.annotation.Resource;
14 14 import lombok.extern.slf4j.Slf4j;
15 15 import org.springframework.cache.annotation.CacheEvict;
16 16 import org.springframework.cache.annotation.Cacheable;
  17 +import org.springframework.context.annotation.Lazy;
17 18 import org.springframework.dao.DuplicateKeyException;
18 19 import org.springframework.stereotype.Service;
19 20 import org.springframework.transaction.annotation.Transactional;
... ... @@ -49,6 +50,7 @@ public class ImFriendServiceImpl implements ImFriendService {
49 50 @Resource
50 51 private ImWebSocketService imWebSocketService;
51 52 @Resource
  53 + @Lazy
52 54 private ImPrivateMessageService privateMessageService;
53 55  
54 56 @Override
... ...
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberService.java
1 1 package cn.iocoder.yudao.module.im.service.group;
2 2  
3   -import cn.iocoder.yudao.framework.common.pojo.PageResult;
4   -import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberPageReqVO;
5   -import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberSaveReqVO;
  3 +import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberInviteReqVO;
  4 +import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberUpdateReqVO;
6 5 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
7 6 import jakarta.validation.Valid;
8 7  
... ... @@ -17,26 +16,26 @@ import java.util.List;
17 16 public interface ImGroupMemberService {
18 17  
19 18 /**
20   - * 创建群成员
  19 + * 邀请用户加入群
21 20 *
22   - * @param createReqVO 创建信息
  21 + * @param inviteReqVO 邀请信息
23 22 * @return 编号
24 23 */
25   - Long createGroupMember(@Valid ImGroupMemberSaveReqVO createReqVO);
  24 + Long inviteGroupMember(@Valid ImGroupMemberInviteReqVO inviteReqVO);
26 25  
27 26 /**
28   - * 更新群成员
  27 + * 更新群成员信息
29 28 *
30 29 * @param updateReqVO 更新信息
31 30 */
32   - void updateGroupMember(@Valid ImGroupMemberSaveReqVO updateReqVO);
  31 + void updateGroupMember(@Valid ImGroupMemberUpdateReqVO updateReqVO);
33 32  
34 33 /**
35 34 * 删除群成员
36 35 *
37 36 * @param id 编号
38 37 */
39   - void deleteGroupMember(Long id);
  38 + void removeGroupMember(Long id);
40 39  
41 40 /**
42 41 * 获得群成员
... ... @@ -46,13 +45,6 @@ public interface ImGroupMemberService {
46 45 */
47 46 ImGroupMemberDO getGroupMember(Long id);
48 47  
49   - /**
50   - * 获得群成员分页
51   - *
52   - * @param pageReqVO 分页查询
53   - * @return 群成员分页
54   - */
55   - PageResult<ImGroupMemberDO> getGroupMemberPage(ImGroupMemberPageReqVO pageReqVO);
56 48  
57 49 /**
58 50 * 根据群组 id 查询群成员(包含所有状态)
... ... @@ -105,6 +97,18 @@ public interface ImGroupMemberService {
105 97 List<ImGroupMemberDO> getQuitGroupMemberListByUserId(Long userId, LocalDateTime minQuitTime);
106 98  
107 99 /**
  100 + * 添加群成员(入群)
  101 + * <p>
  102 + * 插入一条 ENABLE 状态的群成员记录,并设置 joinTime。
  103 + * 可用于创建群时添加群主、邀请入群等场景。
  104 + *
  105 + * @param groupId 群编号
  106 + * @param userId 用户编号
  107 + * @return 群成员记录
  108 + */
  109 + ImGroupMemberDO addGroupMember(Long groupId, Long userId);
  110 +
  111 + /**
108 112 * 校验用户是否为群的有效成员
109 113 *
110 114 * @param groupId 群编号
... ... @@ -112,4 +116,18 @@ public interface ImGroupMemberService {
112 116 * @return 群成员记录
113 117 */
114 118 ImGroupMemberDO validateMemberInGroup(Long groupId, Long userId);
  119 +
  120 + /**
  121 + * 更新群成员的展示信息(群内昵称、群名备注)
  122 + * <p>
  123 + * 内部会校验用户是否为群的有效成员
  124 + *
  125 + * @param groupId 群编号
  126 + * @param userId 用户编号
  127 + * @param displayUserName 群内昵称(null 则不更新)
  128 + * @param displayGroupName 群名备注(null 则不更新)
  129 + */
  130 + void updateGroupMember(Long groupId, Long userId,
  131 + String displayUserName, String displayGroupName);
  132 +
115 133 }
... ...
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberServiceImpl.java
1 1 package cn.iocoder.yudao.module.im.service.group;
2 2  
3 3 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
4   -import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberPageReqVO;
5   -import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberSaveReqVO;
  4 +import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberInviteReqVO;
  5 +import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberUpdateReqVO;
6 6 import org.springframework.stereotype.Service;
7 7 import jakarta.annotation.Resource;
8 8 import org.springframework.validation.annotation.Validated;
9 9  
10 10 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
11   -import cn.iocoder.yudao.framework.common.pojo.PageResult;
12 11 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
13 12  
14 13 import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMemberMapper;
... ... @@ -32,16 +31,19 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService {
32 31 private ImGroupMemberMapper imGroupMemberMapper;
33 32  
34 33 @Override
35   - public Long createGroupMember(ImGroupMemberSaveReqVO createReqVO) {
  34 + public Long inviteGroupMember(ImGroupMemberInviteReqVO inviteReqVO) {
  35 + // TODO @AI:需要群主校验下;
36 36 // 插入
37   - ImGroupMemberDO groupMember = BeanUtils.toBean(createReqVO, ImGroupMemberDO.class);
  37 + ImGroupMemberDO groupMember = BeanUtils.toBean(inviteReqVO, ImGroupMemberDO.class);
  38 + // TODO @AI:默认字段的设置;
38 39 imGroupMemberMapper.insert(groupMember);
  40 + // TODO @AI:或者调用内部的 createGroupMember 方法,设置默认字段的值;
39 41 // 返回
40 42 return groupMember.getId();
41 43 }
42 44  
43 45 @Override
44   - public void updateGroupMember(ImGroupMemberSaveReqVO updateReqVO) {
  46 + public void updateGroupMember(ImGroupMemberUpdateReqVO updateReqVO) {
45 47 // 校验存在
46 48 validateGroupMemberExists(updateReqVO.getId());
47 49 // 更新
... ... @@ -49,10 +51,13 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService {
49 51 imGroupMemberMapper.updateById(updateObj);
50 52 }
51 53  
  54 + // TODO @AI:是不是不存在删除一说?
52 55 @Override
53   - public void deleteGroupMember(Long id) {
  56 + public void removeGroupMember(Long id) {
  57 + // TODO @AI:需要群主校验下;
54 58 // 校验存在
55 59 validateGroupMemberExists(id);
  60 +
56 61 // 删除
57 62 imGroupMemberMapper.deleteById(id);
58 63 }
... ... @@ -68,10 +73,6 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService {
68 73 return imGroupMemberMapper.selectById(id);
69 74 }
70 75  
71   - @Override
72   - public PageResult<ImGroupMemberDO> getGroupMemberPage(ImGroupMemberPageReqVO pageReqVO) {
73   - return imGroupMemberMapper.selectPage(pageReqVO);
74   - }
75 76  
76 77 @Override
77 78 public List<ImGroupMemberDO> getGroupMemberListByGroupId(Long groupId) {
... ... @@ -90,12 +91,12 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService {
90 91  
91 92 @Override
92 93 public List<ImGroupMemberDO> getActiveGroupMemberListByGroupId(Long groupId) {
93   - return imGroupMemberMapper.selectEnabledListByGroupId(groupId);
  94 + return imGroupMemberMapper.selectListByGroupIdAndStatus(groupId, CommonStatusEnum.ENABLE.getStatus());
94 95 }
95 96  
96 97 @Override
97 98 public List<ImGroupMemberDO> getActiveGroupMemberListByUserId(Long userId) {
98   - return imGroupMemberMapper.selectEnabledListByUserId(userId);
  99 + return imGroupMemberMapper.selectListByUserIdAndStatus(userId, CommonStatusEnum.ENABLE.getStatus());
99 100 }
100 101  
101 102 @Override
... ... @@ -104,6 +105,16 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService {
104 105 }
105 106  
106 107 @Override
  108 + public ImGroupMemberDO addGroupMember(Long groupId, Long userId) {
  109 + ImGroupMemberDO member = new ImGroupMemberDO()
  110 + .setGroupId(groupId).setUserId(userId)
  111 + .setStatus(CommonStatusEnum.ENABLE.getStatus()).setJoinTime(LocalDateTime.now());
  112 + // TODO @AI:要不要有个 initXXX 方法,设置默认字段的值;
  113 + imGroupMemberMapper.insert(member);
  114 + return member;
  115 + }
  116 +
  117 + @Override
107 118 public ImGroupMemberDO validateMemberInGroup(Long groupId, Long userId) {
108 119 ImGroupMemberDO member = imGroupMemberMapper.selectByGroupIdAndUserId(groupId, userId);
109 120 if (member == null || CommonStatusEnum.DISABLE.getStatus().equals(member.getStatus())) {
... ... @@ -112,4 +123,16 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService {
112 123 return member;
113 124 }
114 125  
  126 + @Override
  127 + public void updateGroupMember(Long groupId, Long userId, String displayUserName, String displayGroupName) {
  128 + // 1. 校验是群的有效成员
  129 + ImGroupMemberDO member = validateMemberInGroup(groupId, userId);
  130 +
  131 + // 2. 更新展示信息
  132 + imGroupMemberMapper.updateById(new ImGroupMemberDO().setId(member.getId())
  133 + .setDisplayUserName(displayUserName).setDisplayGroupName(displayGroupName));
  134 +
  135 + // TODO @AI:需要分析下,是否需要
  136 + }
  137 +
115 138 }
... ...
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupService.java
1 1 package cn.iocoder.yudao.module.im.service.group;
2 2  
3   -import jakarta.validation.*;
4   -import cn.iocoder.yudao.module.im.controller.admin.group.vo.*;
  3 +import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupCreateReqVO;
  4 +import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupUpdateReqVO;
5 5 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
6   -import cn.iocoder.yudao.framework.common.pojo.PageResult;
  6 +import jakarta.validation.Valid;
7 7  
8 8 import java.util.List;
9 9  
... ... @@ -16,25 +16,33 @@ public interface ImGroupService {
16 16  
17 17 /**
18 18 * 创建群
  19 + * <p>
  20 + * 同时将当前登录用户设置为群主,并插入群主的群成员记录
19 21 *
20 22 * @param createReqVO 创建信息
21   - * @return 编号
  23 + * @param userId 当前登录用户编号(群主)
  24 + * @return 创建后的群信息
22 25 */
23   - Long createGroup(@Valid ImGroupSaveReqVO createReqVO);
  26 + ImGroupDO createGroup(@Valid ImGroupCreateReqVO createReqVO, Long userId);
24 27  
25 28 /**
26   - * 更新群
  29 + * 更新群信息
27 30 *
28 31 * @param updateReqVO 更新信息
  32 + * @param userId 当前登录用户编号
  33 + * @return 更新后的群信息
29 34 */
30   - void updateGroup(@Valid ImGroupSaveReqVO updateReqVO);
  35 + ImGroupDO updateGroup(@Valid ImGroupUpdateReqVO updateReqVO, Long userId);
31 36  
32 37 /**
33   - * 删除群
  38 + * 解散群
  39 + * <p>
  40 + * 仅群主可执行
34 41 *
35   - * @param id 编号
  42 + * @param id 群编号
  43 + * @param userId 当前登录用户编号
36 44 */
37   - void deleteGroup(Long id);
  45 + void dissolveGroup(Long id, Long userId);
38 46  
39 47 /**
40 48 * 获得群
... ... @@ -45,14 +53,6 @@ public interface ImGroupService {
45 53 ImGroupDO getGroup(Long id);
46 54  
47 55 /**
48   - * 获得群分页
49   - *
50   - * @param pageReqVO 分页查询
51   - * @return 群分页
52   - */
53   - PageResult<ImGroupDO> getGroupPage(ImGroupPageReqVO pageReqVO);
54   -
55   - /**
56 56 * 校验群存在且未封禁、未解散
57 57 *
58 58 * @param groupId 群编号
... ... @@ -68,6 +68,6 @@ public interface ImGroupService {
68 68 * @param userId 用户编号
69 69 * @return 群列表
70 70 */
71   - List<ImGroupDO> getMyGroupList(Long userId);
  71 + List<ImGroupDO> getActiveGroupList(Long userId);
72 72  
73 73 }
... ...
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImpl.java
1 1 package cn.iocoder.yudao.module.im.service.group;
2 2  
  3 +import cn.hutool.core.bean.BeanUtil;
  4 +import cn.hutool.core.bean.copier.CopyOptions;
3 5 import cn.hutool.core.collection.CollUtil;
4   -import org.springframework.stereotype.Service;
5   -import jakarta.annotation.Resource;
6   -import org.springframework.validation.annotation.Validated;
7   -
8   -
9   -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
10   -import cn.iocoder.yudao.module.im.controller.admin.group.vo.*;
  6 +import cn.hutool.core.util.ObjUtil;
  7 +import cn.hutool.extra.spring.SpringUtil;
  8 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  9 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  10 +import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupCreateReqVO;
  11 +import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupUpdateReqVO;
11 12 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
12 13 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
13   -import cn.iocoder.yudao.framework.common.pojo.PageResult;
14   -import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
15   -
16 14 import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMapper;
17   -import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMemberMapper;
  15 +import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService;
  16 +import cn.iocoder.yudao.module.im.service.websocket.dto.ImGroupMessageDTO;
  17 +import jakarta.annotation.Resource;
  18 +import org.springframework.cache.annotation.CacheEvict;
  19 +import org.springframework.cache.annotation.Cacheable;
  20 +import org.springframework.context.annotation.Lazy;
  21 +import org.springframework.stereotype.Service;
  22 +import org.springframework.transaction.annotation.Transactional;
  23 +import org.springframework.validation.annotation.Validated;
18 24  
  25 +import java.time.LocalDateTime;
19 26 import java.util.Collections;
20 27 import java.util.List;
21 28 import java.util.Set;
22 29  
23 30 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  31 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
  32 +import static cn.iocoder.yudao.module.im.dal.redis.RedisKeyConstants.GROUP;
24 33 import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*;
25 34  
26 35 // TODO @芋艿:群主的调整,暂时不考虑。后续在弄;
  36 +// DONE @AI:groupdo 增加一个缓存;spring cache 实现;
27 37 /**
28 38 * 群 Service 实现类
29 39 *
... ... @@ -34,103 +44,132 @@ import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*;
34 44 public class ImGroupServiceImpl implements ImGroupService {
35 45  
36 46 @Resource
37   - private ImGroupMapper imGroupMapper;
  47 + private ImGroupMapper groupMapper;
38 48  
39 49 @Resource
40   - private ImGroupMemberMapper imGroupMemberMapper;
  50 + @Lazy // 避免循环依赖
  51 + private ImGroupMemberService groupMemberService;
  52 + @Resource
  53 + private ImWebSocketService webSocketService;
41 54  
42   - // TODO @AI:返回对象;
43   - // TODO @AI:要不要拆分 VO;ImGroupCreateReqVO(name)
44 55 @Override
45   - public Long createGroup(ImGroupSaveReqVO createReqVO) {
46   - // 插入
47   - ImGroupDO group = BeanUtils.toBean(createReqVO, ImGroupDO.class);
48   - imGroupMapper.insert(group);
49   -
50   - // TODO @AI:群主 GroupMember;
51   -
52   - // TODO 多终端推送消息(group message)
53   - return group.getId();
  56 + @Transactional(rollbackFor = Exception.class)
  57 + public ImGroupDO createGroup(ImGroupCreateReqVO createReqVO, Long userId) {
  58 + // 1. 插入群记录
  59 + ImGroupDO group = BeanUtils.toBean(createReqVO, ImGroupDO.class)
  60 + .setOwnerUserId(userId).setStatus(CommonStatusEnum.ENABLE.getStatus());
  61 + groupMapper.insert(group);
  62 +
  63 + // 2. 将群主插入为群成员
  64 + groupMemberService.addGroupMember(group.getId(), userId);
  65 +
  66 + // 3. 推送群创建事件给群主(多端同步)
  67 + webSocketService.sendGroupMessageAsync(userId,
  68 + ImGroupMessageDTO.ofGroupCreate(userId, group.getId()));
  69 + return group;
54 70 }
55 71  
56   - // TODO @AI:要不要拆分 VO;ImGroupUpdateReqVO(name、avatar、notice);我在本群的昵称/群名备注;
57   - // TODO 1. 需要校验群主:name、avatar、notice;如果修改群信息,是不是要群发消息?(群信息变更消息);
58   - // TODO 2. 需要校验群成员:我在本群的昵称/群名备注
59   - // TODO @AI:返回 返回对象;
60 72 @Override
61   - public void updateGroup(ImGroupSaveReqVO updateReqVO) {
62   - // 校验存在
63   - validateGroupExists(updateReqVO.getId());
  73 + @CacheEvict(cacheNames = GROUP, key = "#updateReqVO.id")
  74 + @Transactional(rollbackFor = Exception.class)
  75 + public ImGroupDO updateGroup(ImGroupUpdateReqVO updateReqVO, Long userId) {
  76 + // 1. 校验群存在
  77 + ImGroupDO group = validateGroupExists(updateReqVO.getId());
  78 +
  79 + // 2. 更新群信息(name、avatar、notice → im_group 表)
  80 + boolean hasGroupInfoChange = !ObjUtil.isAllEmpty(
  81 + updateReqVO.getName(), updateReqVO.getAvatar(), updateReqVO.getNotice());
  82 + if (hasGroupInfoChange) {
  83 + if (ObjUtil.notEqual(group.getOwnerUserId(), userId)) {
  84 + throw exception(GROUP_NOT_OWNER);
  85 + }
  86 + // 2.1 更新数据库
  87 + ImGroupDO updateObj = BeanUtils.toBean(updateReqVO, ImGroupDO.class);
  88 + groupMapper.updateById(updateObj);
  89 + // 同步到内存中的 group 对象,避免再次查询
  90 + BeanUtil.copyProperties(updateReqVO, group, CopyOptions.create().ignoreNullValue());
  91 +
  92 + // 2.2 群信息的变更,推送给所有群成员
  93 + List<ImGroupMemberDO> members = groupMemberService.getActiveGroupMemberListByGroupId(group.getId());
  94 + Set<Long> memberUserIds = convertSet(members, ImGroupMemberDO::getUserId);
  95 + webSocketService.sendGroupMessageAsync(memberUserIds,
  96 + ImGroupMessageDTO.ofGroupUpdate(userId, group.getId()));
  97 + }
64 98  
65   - // 更新
66   - ImGroupDO updateObj = BeanUtils.toBean(updateReqVO, ImGroupDO.class);
67   - imGroupMapper.updateById(updateObj);
  99 + // 3. 更新当前用户的群成员信息(displayUserName、displayGroupName → im_group_member 表)
  100 + // TODO @AI:ObjUtil.isAllEmpty(?
  101 + if (updateReqVO.getDisplayUserName() != null || updateReqVO.getDisplayGroupName() != null) {
  102 + groupMemberService.updateGroupMember(updateReqVO.getId(), userId,
  103 + updateReqVO.getDisplayUserName(), updateReqVO.getDisplayGroupName());
  104 + }
  105 + return group;
68 106 }
69 107  
70   - // TODO @AI:dissolve 解散群;
71 108 @Override
72   - public void deleteGroup(Long id) {
73   - // 校验存在
74   - validateGroupExists(id);
75   - // TODO @AI:群主才可以;
  109 + @CacheEvict(cacheNames = GROUP, key = "#id")
  110 + @Transactional(rollbackFor = Exception.class)
  111 + public void dissolveGroup(Long id, Long userId) {
  112 + // 1.1 校验群存在
  113 + ImGroupDO group = validateGroupExists(id);
  114 + // 1.2 校验当前用户是群主
  115 + if (ObjUtil.notEqual(group.getOwnerUserId(), userId)) {
  116 + throw exception(GROUP_NOT_OWNER);
  117 + }
  118 + // TODO @AI:增加一个是否为群主的 public 方法;因为别的模块也需要使用;
76 119  
77   - // TODO @AI:清理已读缓存;
  120 + // 3. 更新群状态为已解散
  121 + // TODO @AI:链式调用;
  122 + ImGroupDO updateObj = new ImGroupDO();
  123 + updateObj.setId(id);
  124 + updateObj.setStatus(CommonStatusEnum.DISABLE.getStatus());
  125 + updateObj.setDissolvedTime(LocalDateTime.now());
  126 + groupMapper.updateById(updateObj);
78 127  
  128 + // TODO @AI:清理已读缓存;
79 129 // TODO @AI:推送群解散的提示;
80   -
81 130 // TODO @AI:推送群信息变化;
82   -
83   - // 删除
84   - imGroupMapper.deleteById(id);
85 131 }
86 132  
87   - // TODO @AI:quit 退群;
88   -
  133 + // TODO @AI:缓存还是加在这个方法上;不加在 getGroup 上;
89 134 @Override
90 135 public ImGroupDO validateGroupExists(Long id) {
91   - ImGroupDO group = imGroupMapper.selectById(id);
  136 + ImGroupDO group = getSelf().getGroup(id);
92 137 if (group == null) {
93 138 throw exception(GROUP_NOT_EXISTS);
94 139 }
95 140 if (Boolean.TRUE.equals(group.getBanned())) {
96 141 throw exception(GROUP_BANNED);
97 142 }
98   - if (Boolean.TRUE.equals(group.getDissolved())) {
  143 + if (CommonStatusEnum.DISABLE.getStatus().equals(group.getStatus())) {
99 144 throw exception(GROUP_DISSOLVED);
100 145 }
101 146 return group;
102 147 }
103 148  
104   - // TODO @AI:add 加群;
105   -
106   - // TODO @AI:remove 踢人;
107   -
108 149 @Override
  150 + @Cacheable(cacheNames = GROUP, key = "#id", unless = "#result == null")
109 151 public ImGroupDO getGroup(Long id) {
110   - return imGroupMapper.selectById(id);
111   - }
112   -
113   - @Override
114   - public PageResult<ImGroupDO> getGroupPage(ImGroupPageReqVO pageReqVO) {
115   - return imGroupMapper.selectPage(pageReqVO);
  152 + return groupMapper.selectById(id);
116 153 }
117 154  
118 155 @Override
119   - public List<ImGroupDO> getMyGroupList(Long userId) {
  156 + public List<ImGroupDO> getActiveGroupList(Long userId) {
120 157 // 1. 查用户所在的、仍有效的群成员记录(仅 ENABLE 状态)
121   - List<ImGroupMemberDO> members = imGroupMemberMapper.selectEnabledListByUserId(userId);
122   - if (CollUtil.isNotEmpty(members)) {
  158 + List<ImGroupMemberDO> members = groupMemberService.getActiveGroupMemberListByUserId(userId);
  159 + if (CollUtil.isEmpty(members)) {
123 160 return Collections.emptyList();
124 161 }
125   - Set<Long> groupIds = CollectionUtils.convertSet(members, ImGroupMemberDO::getGroupId);
  162 + Set<Long> groupIds = convertSet(members, ImGroupMemberDO::getGroupId);
126 163 if (CollUtil.isEmpty(groupIds)) {
127 164 return Collections.emptyList();
128 165 }
129   - // 2. 查群并过滤掉已封禁/解散的
130   - // TODO @AI:查询的时候,就过滤掉!不要内存过滤;
131   - List<ImGroupDO> groups = imGroupMapper.selectByIds(groupIds);
132   - groups.removeIf(g -> Boolean.TRUE.equals(g.getBanned()) || Boolean.TRUE.equals(g.getDissolved()));
133   - return groups;
  166 +
  167 + // 2. 批量查询群信息(仅 ENABLE 状态)
  168 + return groupMapper.selectListByIds(groupIds, CommonStatusEnum.ENABLE.getStatus(), null);
  169 + }
  170 +
  171 + private ImGroupServiceImpl getSelf() {
  172 + return SpringUtil.getBean(getClass());
134 173 }
135 174  
136 175 }
137 176 \ No newline at end of file
... ...
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/ImGroupMessageDTO.java
... ... @@ -116,4 +116,34 @@ public class ImGroupMessageDTO {
116 116 .setGroupId(groupId).setReadCount(readCount).setReceiptStatus(receiptStatus);
117 117 }
118 118  
  119 + // ==================== 群变更相关 ====================
  120 +
  121 + /**
  122 + * 构建群创建推送 DTO(通知群成员"有一个新群")
  123 + *
  124 + * @param senderId 创建者(群主)用户编号
  125 + * @param groupId 群编号
  126 + * @return 群聊 DTO
  127 + */
  128 + public static ImGroupMessageDTO ofGroupCreate(Long senderId, Long groupId) {
  129 + return new ImGroupMessageDTO()
  130 + .setType(ImMessageTypeEnum.GROUP_CREATE.getType())
  131 + .setSenderId(senderId).setGroupId(groupId)
  132 + .setSendTime(LocalDateTime.now());
  133 + }
  134 +
  135 + /**
  136 + * 构建群信息变更推送 DTO(通知所有群成员"群信息变了")
  137 + *
  138 + * @param senderId 操作者用户编号
  139 + * @param groupId 群编号
  140 + * @return 群聊 DTO
  141 + */
  142 + public static ImGroupMessageDTO ofGroupUpdate(Long senderId, Long groupId) {
  143 + return new ImGroupMessageDTO()
  144 + .setType(ImMessageTypeEnum.GROUP_UPDATE.getType())
  145 + .setSenderId(senderId).setGroupId(groupId)
  146 + .setSendTime(LocalDateTime.now());
  147 + }
  148 +
119 149 }
... ...
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/ImPrivateMessageDTO.java
... ... @@ -102,6 +102,8 @@ public class ImPrivateMessageDTO {
102 102 .setSenderId(senderId).setReceiverId(receiverId);
103 103 }
104 104  
  105 + // ==================== 好友变更相关 ====================
  106 +
105 107 /**
106 108 * 构建好友添加推送 DTO(通知对方"你有一位新好友")
107 109 *
... ... @@ -129,6 +131,7 @@ public class ImPrivateMessageDTO {
129 131 .setSenderId(userId).setReceiverId(friendUserId)
130 132 .setSendTime(LocalDateTime.now());
131 133 }
  134 +
132 135 /**
133 136 * 构建好友更新推送 DTO(通知自己多端同步好友属性变更)
134 137 *
... ...