Commit 443d91d7e3a9818e88de3aa9b99489a875509ab5
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<ImGroupMemberDO> { |
| 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
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 | * | ... | ... |