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,6 +29,7 @@ public interface ErrorCodeConstants {
29 ErrorCode GROUP_NOT_EXISTS = new ErrorCode(1_040_400_000, "群不存在"); 29 ErrorCode GROUP_NOT_EXISTS = new ErrorCode(1_040_400_000, "群不存在");
30 ErrorCode GROUP_BANNED = new ErrorCode(1_040_400_001, "群已被封禁"); 30 ErrorCode GROUP_BANNED = new ErrorCode(1_040_400_001, "群已被封禁");
31 ErrorCode GROUP_DISSOLVED = new ErrorCode(1_040_400_002, "群已解散"); 31 ErrorCode GROUP_DISSOLVED = new ErrorCode(1_040_400_002, "群已解散");
  32 + ErrorCode GROUP_NOT_OWNER = new ErrorCode(1_040_400_003, "仅群主可执行该操作");
32 33
33 // ========== 群成员 (1-040-500-000) ========== 34 // ========== 群成员 (1-040-500-000) ==========
34 ErrorCode GROUP_MEMBER_NOT_EXISTS = new ErrorCode(1_040_500_000, "群成员不存在"); 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,4 +34,15 @@ public interface ImCommonConstants {
34 */ 34 */
35 int MESSAGE_GROUP_PULL_MAX_DAYS = 30; 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,11 +15,16 @@ import java.util.Arrays;
15 @AllArgsConstructor 15 @AllArgsConstructor
16 public enum ImMessageTypeEnum implements ArrayValuable<Integer> { 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 RECALL(10, "撤回"), 29 RECALL(10, "撤回"),
25 READ(11, "已读"), 30 READ(11, "已读"),
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 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;  
5 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 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 import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupRespVO; 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 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; 8 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
11 import cn.iocoder.yudao.module.im.service.group.ImGroupService; 9 import cn.iocoder.yudao.module.im.service.group.ImGroupService;
12 import io.swagger.v3.oas.annotations.Operation; 10 import io.swagger.v3.oas.annotations.Operation;
@@ -21,6 +19,7 @@ import org.springframework.web.bind.annotation.*; @@ -21,6 +19,7 @@ import org.springframework.web.bind.annotation.*;
21 import java.util.List; 19 import java.util.List;
22 20
23 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 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 @Tag(name = "管理后台 - 群") 24 @Tag(name = "管理后台 - 群")
26 @RestController 25 @RestController
@@ -34,24 +33,25 @@ public class ImGroupController { @@ -34,24 +33,25 @@ public class ImGroupController {
34 @PostMapping("/create") 33 @PostMapping("/create")
35 @Operation(summary = "创建群") 34 @Operation(summary = "创建群")
36 @PreAuthorize("@ss.hasPermission('im:group:create')") 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 @PutMapping("/update") 41 @PutMapping("/update")
42 @Operation(summary = "更新群") 42 @Operation(summary = "更新群")
43 @PreAuthorize("@ss.hasPermission('im:group:update')") 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 @PreAuthorize("@ss.hasPermission('im:group:delete')") 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 return success(true); 55 return success(true);
56 } 56 }
57 57
@@ -64,35 +64,11 @@ public class ImGroupController { @@ -64,35 +64,11 @@ public class ImGroupController {
64 return success(BeanUtils.toBean(group, ImGroupRespVO.class)); 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 @Operation(summary = "获得当前登录用户的群列表") 68 @Operation(summary = "获得当前登录用户的群列表")
81 public CommonResult<List<ImGroupRespVO>> getMyGroupList() { 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 return success(BeanUtils.toBean(groups, ImGroupRespVO.class)); 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 package cn.iocoder.yudao.module.im.controller.admin.group; 1 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;  
5 -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; 4 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
6 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 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 import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberRespVO; 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 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; 9 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
11 import cn.iocoder.yudao.module.im.service.group.ImGroupMemberService; 10 import cn.iocoder.yudao.module.im.service.group.ImGroupMemberService;
12 import cn.iocoder.yudao.module.system.api.user.AdminUserApi; 11 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
@@ -20,11 +19,11 @@ import org.springframework.security.access.prepost.PreAuthorize; @@ -20,11 +19,11 @@ import org.springframework.security.access.prepost.PreAuthorize;
20 import org.springframework.validation.annotation.Validated; 19 import org.springframework.validation.annotation.Validated;
21 import org.springframework.web.bind.annotation.*; 20 import org.springframework.web.bind.annotation.*;
22 21
23 -import java.util.Collections;  
24 import java.util.List; 22 import java.util.List;
25 import java.util.Map; 23 import java.util.Map;
26 24
27 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; 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 @Tag(name = "管理后台 - 群成员") 28 @Tag(name = "管理后台 - 群成员")
30 @RestController 29 @RestController
@@ -38,32 +37,27 @@ public class ImGroupMemberController { @@ -38,32 +37,27 @@ public class ImGroupMemberController {
38 @Resource 37 @Resource
39 private AdminUserApi adminUserApi; 38 private AdminUserApi adminUserApi;
40 39
41 - // TODO @AI:add 加人;  
42 - @PostMapping("/create")  
43 - @Operation(summary = "创建群成员") 40 + @PostMapping("/invite")
  41 + @Operation(summary = "邀请用户加入群")
44 @PreAuthorize("@ss.hasPermission('im:group-member:create')") 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 @PutMapping("/update") 47 @PutMapping("/update")
54 @Operation(summary = "更新群成员") 48 @Operation(summary = "更新群成员")
55 @PreAuthorize("@ss.hasPermission('im:group-member:update')") 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 imGroupMemberService.updateGroupMember(updateReqVO); 51 imGroupMemberService.updateGroupMember(updateReqVO);
58 return success(true); 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 @PreAuthorize("@ss.hasPermission('im:group-member:delete')") 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 return success(true); 61 return success(true);
68 } 62 }
69 63
@@ -76,45 +70,20 @@ public class ImGroupMemberController { @@ -76,45 +70,20 @@ public class ImGroupMemberController {
76 return success(BeanUtils.toBean(groupMember, ImGroupMemberRespVO.class)); 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 @GetMapping("/list") 73 @GetMapping("/list")
90 @Operation(summary = "获得指定群的成员列表") 74 @Operation(summary = "获得指定群的成员列表")
91 @Parameter(name = "groupId", description = "群编号", required = true, example = "1024") 75 @Parameter(name = "groupId", description = "群编号", required = true, example = "1024")
92 public CommonResult<List<ImGroupMemberRespVO>> getGroupMemberList(@RequestParam("groupId") Long groupId) { 76 public CommonResult<List<ImGroupMemberRespVO>> getGroupMemberList(@RequestParam("groupId") Long groupId) {
93 List<ImGroupMemberDO> members = imGroupMemberService.getGroupMemberListByGroupId(groupId); 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 ImGroupMemberRespVO vo = BeanUtils.toBean(m, ImGroupMemberRespVO.class); 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 return vo; 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,8 +30,8 @@ public class ImGroupRespVO {
30 @Schema(description = "封禁时间") 30 @Schema(description = "封禁时间")
31 private LocalDateTime bannedTime; 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 @Schema(description = "解散时间") 36 @Schema(description = "解散时间")
37 private LocalDateTime dissolvedTime; 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,15 +39,12 @@ 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 查询不填) ========== 42 + // ========== 关联 AdminUser 的字段 ==========
43 43
44 - @Schema(description = "用户昵称(聚合自 AdminUser,用户侧接口返回)", example = "芋道") 44 + @Schema(description = "用户昵称", example = "芋道")
45 private String nickname; 45 private String nickname;
46 46
47 - @Schema(description = "用户头像(聚合自 AdminUser,用户侧接口返回)") 47 + @Schema(description = "用户头像")
48 private String avatar; 48 private String avatar;
49 49
50 - @Schema(description = "展示昵称:优先 displayUserName,回落 nickname", example = "芋道")  
51 - private String showNickname;  
52 -  
53 } 50 }
54 \ No newline at end of file 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 package cn.iocoder.yudao.module.im.dal.dataobject.group; 1 package cn.iocoder.yudao.module.im.dal.dataobject.group;
2 2
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
3 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; 4 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
4 import com.baomidou.mybatisplus.annotation.KeySequence; 5 import com.baomidou.mybatisplus.annotation.KeySequence;
5 import com.baomidou.mybatisplus.annotation.TableId; 6 import com.baomidou.mybatisplus.annotation.TableId;
@@ -58,11 +59,13 @@ public class ImGroupDO extends BaseDO { @@ -58,11 +59,13 @@ public class ImGroupDO extends BaseDO {
58 * 封禁时间 59 * 封禁时间
59 */ 60 */
60 private LocalDateTime bannedTime; 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 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.pojo.PageResult;  
4 -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;  
5 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; 3 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  4 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
6 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; 5 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
7 import org.apache.ibatis.annotations.Mapper; 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 * IM 群 Mapper 12 * IM 群 Mapper
@@ -15,13 +16,19 @@ import cn.iocoder.yudao.module.im.controller.admin.group.vo.*; @@ -15,13 +16,19 @@ import cn.iocoder.yudao.module.im.controller.admin.group.vo.*;
15 @Mapper 16 @Mapper
16 public interface ImGroupMapper extends BaseMapperX<ImGroupDO> { 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 \ No newline at end of file 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 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.enums.CommonStatusEnum;
4 -import cn.iocoder.yudao.framework.common.pojo.PageResult;  
5 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
6 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; 5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
7 -import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberPageReqVO;  
8 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; 6 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
9 import org.apache.ibatis.annotations.Mapper; 7 import org.apache.ibatis.annotations.Mapper;
10 8
@@ -19,15 +17,6 @@ import java.util.List; @@ -19,15 +17,6 @@ import java.util.List;
19 @Mapper 17 @Mapper
20 public interface ImGroupMemberMapper extends BaseMapperX<ImGroupMemberDO> { 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 default List<ImGroupMemberDO> selectListByGroupId(Long groupId) { 20 default List<ImGroupMemberDO> selectListByGroupId(Long groupId) {
32 return selectList(new LambdaQueryWrapperX<ImGroupMemberDO>().eq(ImGroupMemberDO::getGroupId, groupId)); 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,18 +31,16 @@ public interface ImGroupMemberMapper extends BaseMapperX&lt;ImGroupMemberDO&gt; {
42 return selectList(new LambdaQueryWrapperX<ImGroupMemberDO>().eq(ImGroupMemberDO::getUserId, userId)); 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 return selectList(new LambdaQueryWrapperX<ImGroupMemberDO>() 35 return selectList(new LambdaQueryWrapperX<ImGroupMemberDO>()
48 .eq(ImGroupMemberDO::getGroupId, groupId) 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 return selectList(new LambdaQueryWrapperX<ImGroupMemberDO>() 41 return selectList(new LambdaQueryWrapperX<ImGroupMemberDO>()
55 .eq(ImGroupMemberDO::getUserId, userId) 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,4 +23,12 @@ public interface RedisKeyConstants {
23 */ 23 */
24 String FRIEND = "friend"; 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,6 +14,7 @@ import jakarta.annotation.Resource;
14 import lombok.extern.slf4j.Slf4j; 14 import lombok.extern.slf4j.Slf4j;
15 import org.springframework.cache.annotation.CacheEvict; 15 import org.springframework.cache.annotation.CacheEvict;
16 import org.springframework.cache.annotation.Cacheable; 16 import org.springframework.cache.annotation.Cacheable;
  17 +import org.springframework.context.annotation.Lazy;
17 import org.springframework.dao.DuplicateKeyException; 18 import org.springframework.dao.DuplicateKeyException;
18 import org.springframework.stereotype.Service; 19 import org.springframework.stereotype.Service;
19 import org.springframework.transaction.annotation.Transactional; 20 import org.springframework.transaction.annotation.Transactional;
@@ -49,6 +50,7 @@ public class ImFriendServiceImpl implements ImFriendService { @@ -49,6 +50,7 @@ public class ImFriendServiceImpl implements ImFriendService {
49 @Resource 50 @Resource
50 private ImWebSocketService imWebSocketService; 51 private ImWebSocketService imWebSocketService;
51 @Resource 52 @Resource
  53 + @Lazy
52 private ImPrivateMessageService privateMessageService; 54 private ImPrivateMessageService privateMessageService;
53 55
54 @Override 56 @Override
yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberService.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.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 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; 5 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
7 import jakarta.validation.Valid; 6 import jakarta.validation.Valid;
8 7
@@ -17,26 +16,26 @@ import java.util.List; @@ -17,26 +16,26 @@ import java.util.List;
17 public interface ImGroupMemberService { 16 public interface ImGroupMemberService {
18 17
19 /** 18 /**
20 - * 创建群成员 19 + * 邀请用户加入群
21 * 20 *
22 - * @param createReqVO 创建信息 21 + * @param inviteReqVO 邀请信息
23 * @return 编号 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 * @param updateReqVO 更新信息 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 * @param id 编号 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,13 +45,6 @@ public interface ImGroupMemberService {
46 */ 45 */
47 ImGroupMemberDO getGroupMember(Long id); 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 * 根据群组 id 查询群成员(包含所有状态) 50 * 根据群组 id 查询群成员(包含所有状态)
@@ -105,6 +97,18 @@ public interface ImGroupMemberService { @@ -105,6 +97,18 @@ public interface ImGroupMemberService {
105 List<ImGroupMemberDO> getQuitGroupMemberListByUserId(Long userId, LocalDateTime minQuitTime); 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 * @param groupId 群编号 114 * @param groupId 群编号
@@ -112,4 +116,18 @@ public interface ImGroupMemberService { @@ -112,4 +116,18 @@ public interface ImGroupMemberService {
112 * @return 群成员记录 116 * @return 群成员记录
113 */ 117 */
114 ImGroupMemberDO validateMemberInGroup(Long groupId, Long userId); 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 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.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 import org.springframework.stereotype.Service; 6 import org.springframework.stereotype.Service;
7 import jakarta.annotation.Resource; 7 import jakarta.annotation.Resource;
8 import org.springframework.validation.annotation.Validated; 8 import org.springframework.validation.annotation.Validated;
9 9
10 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; 10 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
11 -import cn.iocoder.yudao.framework.common.pojo.PageResult;  
12 import cn.iocoder.yudao.framework.common.util.object.BeanUtils; 11 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
13 12
14 import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMemberMapper; 13 import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMemberMapper;
@@ -32,16 +31,19 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService { @@ -32,16 +31,19 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService {
32 private ImGroupMemberMapper imGroupMemberMapper; 31 private ImGroupMemberMapper imGroupMemberMapper;
33 32
34 @Override 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 imGroupMemberMapper.insert(groupMember); 39 imGroupMemberMapper.insert(groupMember);
  40 + // TODO @AI:或者调用内部的 createGroupMember 方法,设置默认字段的值;
39 // 返回 41 // 返回
40 return groupMember.getId(); 42 return groupMember.getId();
41 } 43 }
42 44
43 @Override 45 @Override
44 - public void updateGroupMember(ImGroupMemberSaveReqVO updateReqVO) { 46 + public void updateGroupMember(ImGroupMemberUpdateReqVO updateReqVO) {
45 // 校验存在 47 // 校验存在
46 validateGroupMemberExists(updateReqVO.getId()); 48 validateGroupMemberExists(updateReqVO.getId());
47 // 更新 49 // 更新
@@ -49,10 +51,13 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService { @@ -49,10 +51,13 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService {
49 imGroupMemberMapper.updateById(updateObj); 51 imGroupMemberMapper.updateById(updateObj);
50 } 52 }
51 53
  54 + // TODO @AI:是不是不存在删除一说?
52 @Override 55 @Override
53 - public void deleteGroupMember(Long id) { 56 + public void removeGroupMember(Long id) {
  57 + // TODO @AI:需要群主校验下;
54 // 校验存在 58 // 校验存在
55 validateGroupMemberExists(id); 59 validateGroupMemberExists(id);
  60 +
56 // 删除 61 // 删除
57 imGroupMemberMapper.deleteById(id); 62 imGroupMemberMapper.deleteById(id);
58 } 63 }
@@ -68,10 +73,6 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService { @@ -68,10 +73,6 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService {
68 return imGroupMemberMapper.selectById(id); 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 @Override 77 @Override
77 public List<ImGroupMemberDO> getGroupMemberListByGroupId(Long groupId) { 78 public List<ImGroupMemberDO> getGroupMemberListByGroupId(Long groupId) {
@@ -90,12 +91,12 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService { @@ -90,12 +91,12 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService {
90 91
91 @Override 92 @Override
92 public List<ImGroupMemberDO> getActiveGroupMemberListByGroupId(Long groupId) { 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 @Override 97 @Override
97 public List<ImGroupMemberDO> getActiveGroupMemberListByUserId(Long userId) { 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 @Override 102 @Override
@@ -104,6 +105,16 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService { @@ -104,6 +105,16 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService {
104 } 105 }
105 106
106 @Override 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 public ImGroupMemberDO validateMemberInGroup(Long groupId, Long userId) { 118 public ImGroupMemberDO validateMemberInGroup(Long groupId, Long userId) {
108 ImGroupMemberDO member = imGroupMemberMapper.selectByGroupIdAndUserId(groupId, userId); 119 ImGroupMemberDO member = imGroupMemberMapper.selectByGroupIdAndUserId(groupId, userId);
109 if (member == null || CommonStatusEnum.DISABLE.getStatus().equals(member.getStatus())) { 120 if (member == null || CommonStatusEnum.DISABLE.getStatus().equals(member.getStatus())) {
@@ -112,4 +123,16 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService { @@ -112,4 +123,16 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService {
112 return member; 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 package cn.iocoder.yudao.module.im.service.group; 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 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 jakarta.validation.Valid;
7 7
8 import java.util.List; 8 import java.util.List;
9 9
@@ -16,25 +16,33 @@ public interface ImGroupService { @@ -16,25 +16,33 @@ public interface ImGroupService {
16 16
17 /** 17 /**
18 * 创建群 18 * 创建群
  19 + * <p>
  20 + * 同时将当前登录用户设置为群主,并插入群主的群成员记录
19 * 21 *
20 * @param createReqVO 创建信息 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 * @param updateReqVO 更新信息 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,14 +53,6 @@ public interface ImGroupService {
45 ImGroupDO getGroup(Long id); 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 * @param groupId 群编号 58 * @param groupId 群编号
@@ -68,6 +68,6 @@ public interface ImGroupService { @@ -68,6 +68,6 @@ public interface ImGroupService {
68 * @param userId 用户编号 68 * @param userId 用户编号
69 * @return 群列表 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 package cn.iocoder.yudao.module.im.service.group; 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 import cn.hutool.core.collection.CollUtil; 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 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; 12 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
12 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; 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 import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMapper; 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 import java.util.Collections; 26 import java.util.Collections;
20 import java.util.List; 27 import java.util.List;
21 import java.util.Set; 28 import java.util.Set;
22 29
23 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 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 import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*; 33 import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*;
25 34
26 // TODO @芋艿:群主的调整,暂时不考虑。后续在弄; 35 // TODO @芋艿:群主的调整,暂时不考虑。后续在弄;
  36 +// DONE @AI:groupdo 增加一个缓存;spring cache 实现;
27 /** 37 /**
28 * 群 Service 实现类 38 * 群 Service 实现类
29 * 39 *
@@ -34,103 +44,132 @@ import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*; @@ -34,103 +44,132 @@ import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*;
34 public class ImGroupServiceImpl implements ImGroupService { 44 public class ImGroupServiceImpl implements ImGroupService {
35 45
36 @Resource 46 @Resource
37 - private ImGroupMapper imGroupMapper; 47 + private ImGroupMapper groupMapper;
38 48
39 @Resource 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 @Override 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 @Override 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 @Override 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 // TODO @AI:推送群解散的提示; 129 // TODO @AI:推送群解散的提示;
80 -  
81 // TODO @AI:推送群信息变化; 130 // TODO @AI:推送群信息变化;
82 -  
83 - // 删除  
84 - imGroupMapper.deleteById(id);  
85 } 131 }
86 132
87 - // TODO @AI:quit 退群;  
88 - 133 + // TODO @AI:缓存还是加在这个方法上;不加在 getGroup 上;
89 @Override 134 @Override
90 public ImGroupDO validateGroupExists(Long id) { 135 public ImGroupDO validateGroupExists(Long id) {
91 - ImGroupDO group = imGroupMapper.selectById(id); 136 + ImGroupDO group = getSelf().getGroup(id);
92 if (group == null) { 137 if (group == null) {
93 throw exception(GROUP_NOT_EXISTS); 138 throw exception(GROUP_NOT_EXISTS);
94 } 139 }
95 if (Boolean.TRUE.equals(group.getBanned())) { 140 if (Boolean.TRUE.equals(group.getBanned())) {
96 throw exception(GROUP_BANNED); 141 throw exception(GROUP_BANNED);
97 } 142 }
98 - if (Boolean.TRUE.equals(group.getDissolved())) { 143 + if (CommonStatusEnum.DISABLE.getStatus().equals(group.getStatus())) {
99 throw exception(GROUP_DISSOLVED); 144 throw exception(GROUP_DISSOLVED);
100 } 145 }
101 return group; 146 return group;
102 } 147 }
103 148
104 - // TODO @AI:add 加群;  
105 -  
106 - // TODO @AI:remove 踢人;  
107 -  
108 @Override 149 @Override
  150 + @Cacheable(cacheNames = GROUP, key = "#id", unless = "#result == null")
109 public ImGroupDO getGroup(Long id) { 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 @Override 155 @Override
119 - public List<ImGroupDO> getMyGroupList(Long userId) { 156 + public List<ImGroupDO> getActiveGroupList(Long userId) {
120 // 1. 查用户所在的、仍有效的群成员记录(仅 ENABLE 状态) 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 return Collections.emptyList(); 160 return Collections.emptyList();
124 } 161 }
125 - Set<Long> groupIds = CollectionUtils.convertSet(members, ImGroupMemberDO::getGroupId); 162 + Set<Long> groupIds = convertSet(members, ImGroupMemberDO::getGroupId);
126 if (CollUtil.isEmpty(groupIds)) { 163 if (CollUtil.isEmpty(groupIds)) {
127 return Collections.emptyList(); 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 \ No newline at end of file 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,4 +116,34 @@ public class ImGroupMessageDTO {
116 .setGroupId(groupId).setReadCount(readCount).setReceiptStatus(receiptStatus); 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,6 +102,8 @@ public class ImPrivateMessageDTO {
102 .setSenderId(senderId).setReceiverId(receiverId); 102 .setSenderId(senderId).setReceiverId(receiverId);
103 } 103 }
104 104
  105 + // ==================== 好友变更相关 ====================
  106 +
105 /** 107 /**
106 * 构建好友添加推送 DTO(通知对方"你有一位新好友") 108 * 构建好友添加推送 DTO(通知对方"你有一位新好友")
107 * 109 *
@@ -129,6 +131,7 @@ public class ImPrivateMessageDTO { @@ -129,6 +131,7 @@ public class ImPrivateMessageDTO {
129 .setSenderId(userId).setReceiverId(friendUserId) 131 .setSenderId(userId).setReceiverId(friendUserId)
130 .setSendTime(LocalDateTime.now()); 132 .setSendTime(LocalDateTime.now());
131 } 133 }
  134 +
132 /** 135 /**
133 * 构建好友更新推送 DTO(通知自己多端同步好友属性变更) 136 * 构建好友更新推送 DTO(通知自己多端同步好友属性变更)
134 * 137 *