diff --git a/yudao-module-im/yudao-module-im-api/src/main/java/cn/iocoder/yudao/module/im/enums/ErrorCodeConstants.java b/yudao-module-im/yudao-module-im-api/src/main/java/cn/iocoder/yudao/module/im/enums/ErrorCodeConstants.java index e6faccd..8e3df76 100644 --- a/yudao-module-im/yudao-module-im-api/src/main/java/cn/iocoder/yudao/module/im/enums/ErrorCodeConstants.java +++ b/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 { ErrorCode GROUP_NOT_EXISTS = new ErrorCode(1_040_400_000, "群不存在"); ErrorCode GROUP_BANNED = new ErrorCode(1_040_400_001, "群已被封禁"); ErrorCode GROUP_DISSOLVED = new ErrorCode(1_040_400_002, "群已解散"); + ErrorCode GROUP_NOT_OWNER = new ErrorCode(1_040_400_003, "仅群主可执行该操作"); // ========== 群成员 (1-040-500-000) ========== ErrorCode GROUP_MEMBER_NOT_EXISTS = new ErrorCode(1_040_500_000, "群成员不存在"); diff --git a/yudao-module-im/yudao-module-im-api/src/main/java/cn/iocoder/yudao/module/im/enums/ImCommonConstants.java b/yudao-module-im/yudao-module-im-api/src/main/java/cn/iocoder/yudao/module/im/enums/ImCommonConstants.java index 65e50ee..be9efa3 100644 --- a/yudao-module-im/yudao-module-im-api/src/main/java/cn/iocoder/yudao/module/im/enums/ImCommonConstants.java +++ b/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 { */ int MESSAGE_GROUP_PULL_MAX_DAYS = 30; + // ==================== 好友相关提示消息 ==================== + + /** + * 好友添加系统提示 + */ + String FRIEND_ADD_TIP_MESSAGE = "你们已成为好友,现在可以开始聊天了"; + /** + * 好友删除系统提示 + */ + String FRIEND_DELETE_TIP_MESSAGE = "你们的好友关系已被解除"; + } diff --git a/yudao-module-im/yudao-module-im-api/src/main/java/cn/iocoder/yudao/module/im/enums/message/ImMessageTypeEnum.java b/yudao-module-im/yudao-module-im-api/src/main/java/cn/iocoder/yudao/module/im/enums/message/ImMessageTypeEnum.java index c9dcb1d..59b435d 100644 --- a/yudao-module-im/yudao-module-im-api/src/main/java/cn/iocoder/yudao/module/im/enums/message/ImMessageTypeEnum.java +++ b/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; @AllArgsConstructor public enum ImMessageTypeEnum implements ArrayValuable { - TEXT(0, "文本"), - IMAGE(1, "图片"), - FILE(2, "文件"), - VOICE(3, "语音"), - VIDEO(4, "视频"), + TEXT(0, "文本"), // 对应 TextMessage 类 + IMAGE(1, "图片"), // 对应 ImageMessage 类 + FILE(2, "文件"), // 对应 FileMessage 类 + VOICE(3, "语音"), // 对应 AudioMessage 类 + VIDEO(4, "视频"), // 对应 VideoMessage 类 + + RECALL(10, "撤回"), // 对应 RecallMessage 类 + READ(11, "已读"), // 暂无 + RECEIPT(12, "回执"), // 暂无 + TIP_TEXT(21, "提示文本"), // 对应 TextMessage 类 RECALL(10, "撤回"), READ(11, "已读"), diff --git a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupController.java b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupController.java index f05335d..050cfe0 100644 --- a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupController.java +++ b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupController.java @@ -1,12 +1,10 @@ package cn.iocoder.yudao.module.im.controller.admin.group; import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; -import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupCreateReqVO; import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupRespVO; -import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupSaveReqVO; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupUpdateReqVO; import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; import cn.iocoder.yudao.module.im.service.group.ImGroupService; import io.swagger.v3.oas.annotations.Operation; @@ -21,6 +19,7 @@ import org.springframework.web.bind.annotation.*; import java.util.List; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId; @Tag(name = "管理后台 - 群") @RestController @@ -34,24 +33,25 @@ public class ImGroupController { @PostMapping("/create") @Operation(summary = "创建群") @PreAuthorize("@ss.hasPermission('im:group:create')") - public CommonResult createGroup(@Valid @RequestBody ImGroupSaveReqVO createReqVO) { - return success(imGroupService.createGroup(createReqVO)); + public CommonResult createGroup(@Valid @RequestBody ImGroupCreateReqVO createReqVO) { + ImGroupDO group = imGroupService.createGroup(createReqVO, getLoginUserId()); + return success(BeanUtils.toBean(group, ImGroupRespVO.class)); } @PutMapping("/update") @Operation(summary = "更新群") @PreAuthorize("@ss.hasPermission('im:group:update')") - public CommonResult updateGroup(@Valid @RequestBody ImGroupSaveReqVO updateReqVO) { - imGroupService.updateGroup(updateReqVO); - return success(true); + public CommonResult updateGroup(@Valid @RequestBody ImGroupUpdateReqVO updateReqVO) { + ImGroupDO group = imGroupService.updateGroup(updateReqVO, getLoginUserId()); + return success(BeanUtils.toBean(group, ImGroupRespVO.class)); } - @DeleteMapping("/delete") - @Operation(summary = "删除群") - @Parameter(name = "id", description = "编号", required = true) + @DeleteMapping("/dissolve") + @Operation(summary = "解散群") + @Parameter(name = "id", description = "群编号", required = true) @PreAuthorize("@ss.hasPermission('im:group:delete')") - public CommonResult deleteGroup(@RequestParam("id") Long id) { - imGroupService.deleteGroup(id); + public CommonResult dissolveGroup(@RequestParam("id") Long id) { + imGroupService.dissolveGroup(id, getLoginUserId()); return success(true); } @@ -64,35 +64,11 @@ public class ImGroupController { return success(BeanUtils.toBean(group, ImGroupRespVO.class)); } - // TODO @AI:去掉这个接口; - @GetMapping("/page") - @Operation(summary = "获得群分页") - @PreAuthorize("@ss.hasPermission('im:group:query')") - public CommonResult> getGroupPage(@Valid ImGroupPageReqVO pageReqVO) { - PageResult pageResult = imGroupService.getGroupPage(pageReqVO); - return success(BeanUtils.toBean(pageResult, ImGroupRespVO.class)); - } - - // ========== 用户侧接口(登录用户自己的群,无需后台权限) ========== - - // TODO @AI:直接 list; - @GetMapping("/list-my") + @GetMapping("/list") @Operation(summary = "获得当前登录用户的群列表") public CommonResult> getMyGroupList() { - Long userId = SecurityFrameworkUtils.getLoginUserId(); - List groups = imGroupService.getMyGroupList(userId); + List groups = imGroupService.getActiveGroupList(getLoginUserId()); return success(BeanUtils.toBean(groups, ImGroupRespVO.class)); } - // TODO @芋艿:【对齐】/group/create、/group/modify、/group/invite、/group/quit、/group/delete/{id} - // 这些用户侧接口第一期暂未实现。需要: - // 1. createGroup(vo):需同时插入 im_group + 批量插入 im_group_member;还要给群成员推送 FRIEND_INVITE 类消息 - // 2. modifyGroup(vo):允许修改 name / notice / avatar / remarkGroupName / remarkNickName - // (其中 remarkGroupName / remarkNickName 是 im_group_member 的字段,不是 im_group 的) - // 3. invite(groupId, userIds):批量插入 im_group_member 为 ENABLE;给被邀请人推送入群通知 - // 4. quitGroup(groupId):校验非群主才能退,然后把自己的 im_group_member 标为 DISABLE + quitTime - // 5. removeGroupMembers(groupId, userIds):群主权限校验 + 把成员标为 DISABLE - // 以上都需要 WebSocket 推送对应事件(FRIEND_INVITE / MEMBER_QUIT / MEMBER_REMOVE 等), - // 涉及 `ImWsEventType` 扩展 + `ImWebSocketPusher` 新增事件推送方法,待统一设计后补上。 - } diff --git a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupMemberController.java b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupMemberController.java index 0a5a2d4..d96c9de 100644 --- a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupMemberController.java +++ b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/ImGroupMemberController.java @@ -1,12 +1,11 @@ package cn.iocoder.yudao.module.im.controller.admin.group; import cn.iocoder.yudao.framework.common.pojo.CommonResult; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; -import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberInviteReqVO; import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberRespVO; -import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberSaveReqVO; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberUpdateReqVO; import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; import cn.iocoder.yudao.module.im.service.group.ImGroupMemberService; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; @@ -20,11 +19,11 @@ import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import java.util.Collections; import java.util.List; import java.util.Map; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; @Tag(name = "管理后台 - 群成员") @RestController @@ -38,32 +37,27 @@ public class ImGroupMemberController { @Resource private AdminUserApi adminUserApi; - // TODO @AI:add 加人; - @PostMapping("/create") - @Operation(summary = "创建群成员") + @PostMapping("/invite") + @Operation(summary = "邀请用户加入群") @PreAuthorize("@ss.hasPermission('im:group-member:create')") - public CommonResult createGroupMember(@Valid @RequestBody ImGroupMemberSaveReqVO createReqVO) { - return success(imGroupMemberService.createGroupMember(createReqVO)); + public CommonResult inviteGroupMember(@Valid @RequestBody ImGroupMemberInviteReqVO inviteReqVO) { + return success(imGroupMemberService.inviteGroupMember(inviteReqVO)); } - // TODO @AI:踢人; - - // TODO @AI:退群;放在这里;避免 group 里面逻辑太多; - @PutMapping("/update") @Operation(summary = "更新群成员") @PreAuthorize("@ss.hasPermission('im:group-member:update')") - public CommonResult updateGroupMember(@Valid @RequestBody ImGroupMemberSaveReqVO updateReqVO) { + public CommonResult updateGroupMember(@Valid @RequestBody ImGroupMemberUpdateReqVO updateReqVO) { imGroupMemberService.updateGroupMember(updateReqVO); return success(true); } - @DeleteMapping("/delete") - @Operation(summary = "删除群成员") - @Parameter(name = "id", description = "编号", required = true) + @DeleteMapping("/remove") + @Operation(summary = "移除群成员(踢人)") + @Parameter(name = "id", description = "群成员编号", required = true) @PreAuthorize("@ss.hasPermission('im:group-member:delete')") - public CommonResult deleteGroupMember(@RequestParam("id") Long id) { - imGroupMemberService.deleteGroupMember(id); + public CommonResult removeGroupMember(@RequestParam("id") Long id) { + imGroupMemberService.removeGroupMember(id); return success(true); } @@ -76,45 +70,20 @@ public class ImGroupMemberController { return success(BeanUtils.toBean(groupMember, ImGroupMemberRespVO.class)); } - @GetMapping("/page") - @Operation(summary = "获得群成员分页") - @PreAuthorize("@ss.hasPermission('im:group-member:query')") - public CommonResult> getGroupMemberPage(@Valid ImGroupMemberPageReqVO pageReqVO) { - PageResult pageResult = imGroupMemberService.getGroupMemberPage(pageReqVO); - return success(BeanUtils.toBean(pageResult, ImGroupMemberRespVO.class)); - } - - // ========== 用户侧接口(登录用户自己看群成员,无需后台权限) ========== - @GetMapping("/list") @Operation(summary = "获得指定群的成员列表") @Parameter(name = "groupId", description = "群编号", required = true, example = "1024") public CommonResult> getGroupMemberList(@RequestParam("groupId") Long groupId) { List members = imGroupMemberService.getGroupMemberListByGroupId(groupId); - if (members.isEmpty()) { - return success(Collections.emptyList()); - } - // 聚合 AdminUser 的昵称 + 头像 - List userIds = CollectionUtils.convertList(members, ImGroupMemberDO::getUserId); - Map userMap = adminUserApi.getUserMap(userIds); - return success(CollectionUtils.convertList(members, m -> { + // 批量聚合 AdminUser 信息(昵称 / 头像) + Map userMap = adminUserApi.getUserMap( + convertList(members, ImGroupMemberDO::getUserId)); + return success(convertList(members, m -> { ImGroupMemberRespVO vo = BeanUtils.toBean(m, ImGroupMemberRespVO.class); - AdminUserRespDTO user = userMap.get(m.getUserId()); - if (user != null) { - vo.setNickname(user.getNickname()); - vo.setAvatar(user.getAvatar()); - } - // showNickname 回落:优先群内备注 displayUserName,否则用用户昵称 - if (m.getDisplayUserName() != null && !m.getDisplayUserName().isEmpty()) { - vo.setShowNickname(m.getDisplayUserName()); - } else if (user != null) { - vo.setShowNickname(user.getNickname()); - } + MapUtils.findAndThen(userMap, m.getUserId(), user -> + vo.setNickname(user.getNickname()).setAvatar(user.getAvatar())); return vo; })); } - // TODO @芋艿:【对齐】/group/members/remove 批量踢人接口第一期未实现。 - // 需要:校验群主权限、批量更新 im_group_member 为 DISABLE + 设置 quitTime、通过 WebSocket 推送给被踢成员。 - } diff --git a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupCreateReqVO.java b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupCreateReqVO.java new file mode 100644 index 0000000..37fa337 --- /dev/null +++ b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupCreateReqVO.java @@ -0,0 +1,15 @@ +package cn.iocoder.yudao.module.im.controller.admin.group.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +@Schema(description = "管理后台 - 群创建 Request VO") +@Data +public class ImGroupCreateReqVO { + + @Schema(description = "群名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道技术交流群") + @NotBlank(message = "群名称不能为空") + private String name; + +} diff --git a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupRespVO.java b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupRespVO.java index 8eebe81..037daf0 100644 --- a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupRespVO.java +++ b/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 { @Schema(description = "封禁时间") private LocalDateTime bannedTime; - @Schema(description = "是否解散") - private Boolean dissolved; + @Schema(description = "群状态", requiredMode = Schema.RequiredMode.REQUIRED) + private Integer status; @Schema(description = "解散时间") private LocalDateTime dissolvedTime; diff --git a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupUpdateReqVO.java b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupUpdateReqVO.java new file mode 100644 index 0000000..6e57289 --- /dev/null +++ b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/ImGroupUpdateReqVO.java @@ -0,0 +1,32 @@ +package cn.iocoder.yudao.module.im.controller.admin.group.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "管理后台 - 群更新 Request VO") +@Data +public class ImGroupUpdateReqVO { + + @Schema(description = "群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1003") + @NotNull(message = "群编号不能为空") + private Long id; + + @Schema(description = "群名称", example = "芋道技术交流群") + private String name; + + @Schema(description = "群头像") + private String avatar; + + @Schema(description = "群公告") + private String notice; + + // ========== 以下字段来自 im_group_member 表(当前用户在该群的个人设置) ========== + + @Schema(description = "我在本群的昵称") + private String displayUserName; + + @Schema(description = "群名备注") + private String displayGroupName; + +} diff --git a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/member/ImGroupMemberInviteReqVO.java b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/member/ImGroupMemberInviteReqVO.java new file mode 100644 index 0000000..656b42f --- /dev/null +++ b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/member/ImGroupMemberInviteReqVO.java @@ -0,0 +1,19 @@ +package cn.iocoder.yudao.module.im.controller.admin.group.vo.member; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "管理后台 - 群成员邀请 Request VO") +@Data +public class ImGroupMemberInviteReqVO { + + @Schema(description = "群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "13279") + @NotNull(message = "群编号不能为空") + private Long groupId; + + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "21730") + @NotNull(message = "用户编号不能为空") + private Long userId; + +} diff --git a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/member/ImGroupMemberRespVO.java b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/member/ImGroupMemberRespVO.java index 884c620..1827e29 100644 --- a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/member/ImGroupMemberRespVO.java +++ b/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 { @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) private LocalDateTime createTime; - // ========== 聚合字段:用户侧接口按需填充(admin CRUD 查询不填) ========== + // ========== 关联 AdminUser 的字段 ========== - @Schema(description = "用户昵称(聚合自 AdminUser,用户侧接口返回)", example = "芋道") + @Schema(description = "用户昵称", example = "芋道") private String nickname; - @Schema(description = "用户头像(聚合自 AdminUser,用户侧接口返回)") + @Schema(description = "用户头像") private String avatar; - @Schema(description = "展示昵称:优先 displayUserName,回落 nickname", example = "芋道") - private String showNickname; - } \ No newline at end of file diff --git a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/member/ImGroupMemberUpdateReqVO.java b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/member/ImGroupMemberUpdateReqVO.java new file mode 100644 index 0000000..02a33a2 --- /dev/null +++ b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/member/ImGroupMemberUpdateReqVO.java @@ -0,0 +1,18 @@ +package cn.iocoder.yudao.module.im.controller.admin.group.vo.member; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "管理后台 - 群成员更新 Request VO") +@Data +public class ImGroupMemberUpdateReqVO { + + @Schema(description = "群成员编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17071") + @NotNull(message = "群成员编号不能为空") + private Long id; + + @Schema(description = "是否免打扰") + private Boolean muted; + +} diff --git a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/group/ImGroupDO.java b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/group/ImGroupDO.java index f564285..c304938 100644 --- a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/group/ImGroupDO.java +++ b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/dataobject/group/ImGroupDO.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.im.dal.dataobject.group; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.TableId; @@ -58,11 +59,13 @@ public class ImGroupDO extends BaseDO { * 封禁时间 */ private LocalDateTime bannedTime; - // TODO @AI:status 字段,枚举 CommonStatusEnum,表示正常 / 解散;如果是解散状态,则不允许任何操作了。 /** - * 是否解散 + * 群状态 + *

+ * 枚举 {@link CommonStatusEnum} + * ENABLE = 正常,DISABLE = 已解散 */ - private Boolean dissolved; + private Integer status; /** * 解散时间 */ diff --git a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupMapper.java b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupMapper.java index 46432ae..94c831e 100644 --- a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupMapper.java +++ b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupMapper.java @@ -1,11 +1,12 @@ package cn.iocoder.yudao.module.im.dal.mysql.group; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; import org.apache.ibatis.annotations.Mapper; -import cn.iocoder.yudao.module.im.controller.admin.group.vo.*; + +import java.util.Collection; +import java.util.List; /** * IM 群 Mapper @@ -15,13 +16,19 @@ import cn.iocoder.yudao.module.im.controller.admin.group.vo.*; @Mapper public interface ImGroupMapper extends BaseMapperX { - default PageResult selectPage(ImGroupPageReqVO reqVO) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .likeIfPresent(ImGroupDO::getName, reqVO.getName()) - .eqIfPresent(ImGroupDO::getOwnerUserId, reqVO.getOwnerUserId()) - .eqIfPresent(ImGroupDO::getNotice, reqVO.getNotice()) - .betweenIfPresent(ImGroupDO::getCreateTime, reqVO.getCreateTime()) - .orderByDesc(ImGroupDO::getId)); + /** + * 根据群编号批量查询群,支持按状态和封禁过滤 + * + * @param ids 群编号集合 + * @param status 群状态(可空,空则不过滤) + * @param banned 是否封禁(可空,空则不过滤) + * @return 群列表 + */ + default List selectListByIds(Collection ids, Integer status, Boolean banned) { + return selectList(new LambdaQueryWrapperX() + .in(ImGroupDO::getId, ids) + .eqIfPresent(ImGroupDO::getStatus, status) + .eqIfPresent(ImGroupDO::getBanned, banned)); } } \ No newline at end of file diff --git a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupMemberMapper.java b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupMemberMapper.java index afa1fdb..ff773e6 100644 --- a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupMemberMapper.java +++ b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupMemberMapper.java @@ -1,10 +1,8 @@ package cn.iocoder.yudao.module.im.dal.mysql.group; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; -import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; -import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberPageReqVO; import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; import org.apache.ibatis.annotations.Mapper; @@ -19,15 +17,6 @@ import java.util.List; @Mapper public interface ImGroupMemberMapper extends BaseMapperX { - default PageResult selectPage(ImGroupMemberPageReqVO reqVO) { - return selectPage(reqVO, new LambdaQueryWrapperX() - .eqIfPresent(ImGroupMemberDO::getGroupId, reqVO.getGroupId()) - .eqIfPresent(ImGroupMemberDO::getUserId, reqVO.getUserId()) - .eqIfPresent(ImGroupMemberDO::getStatus, reqVO.getStatus()) - .betweenIfPresent(ImGroupMemberDO::getCreateTime, reqVO.getCreateTime()) - .orderByDesc(ImGroupMemberDO::getId)); - } - default List selectListByGroupId(Long groupId) { return selectList(new LambdaQueryWrapperX().eq(ImGroupMemberDO::getGroupId, groupId)); } @@ -42,18 +31,16 @@ public interface ImGroupMemberMapper extends BaseMapperX { return selectList(new LambdaQueryWrapperX().eq(ImGroupMemberDO::getUserId, userId)); } - // TODO @AI:selectListByXXX;enabled 传入进来 - default List selectEnabledListByGroupId(Long groupId) { + default List selectListByGroupIdAndStatus(Long groupId, Integer status) { return selectList(new LambdaQueryWrapperX() .eq(ImGroupMemberDO::getGroupId, groupId) - .eq(ImGroupMemberDO::getStatus, CommonStatusEnum.ENABLE.getStatus())); + .eq(ImGroupMemberDO::getStatus, status)); } - // TODO @AI:selectListByXXX;enabled 传入进来 - default List selectEnabledListByUserId(Long userId) { + default List selectListByUserIdAndStatus(Long userId, Integer status) { return selectList(new LambdaQueryWrapperX() .eq(ImGroupMemberDO::getUserId, userId) - .eq(ImGroupMemberDO::getStatus, CommonStatusEnum.ENABLE.getStatus())); + .eq(ImGroupMemberDO::getStatus, status)); } /** diff --git a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/redis/RedisKeyConstants.java b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/redis/RedisKeyConstants.java index 209e46d..445d7fc 100644 --- a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/dal/redis/RedisKeyConstants.java +++ b/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 { */ String FRIEND = "friend"; + /** + * 群信息缓存 + *

+ * KEY 格式:group:{groupId} + * VALUE 数据类型:ImGroupDO + */ + String GROUP = "group"; + } diff --git a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImpl.java b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImpl.java index 726c8b9..2fca13a 100644 --- a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImpl.java +++ b/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; import lombok.extern.slf4j.Slf4j; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; +import org.springframework.context.annotation.Lazy; import org.springframework.dao.DuplicateKeyException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -49,6 +50,7 @@ public class ImFriendServiceImpl implements ImFriendService { @Resource private ImWebSocketService imWebSocketService; @Resource + @Lazy private ImPrivateMessageService privateMessageService; @Override diff --git a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberService.java b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberService.java index 123d9cb..9b8a705 100644 --- a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberService.java +++ b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberService.java @@ -1,8 +1,7 @@ package cn.iocoder.yudao.module.im.service.group; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberPageReqVO; -import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberSaveReqVO; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberInviteReqVO; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberUpdateReqVO; import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; import jakarta.validation.Valid; @@ -17,26 +16,26 @@ import java.util.List; public interface ImGroupMemberService { /** - * 创建群成员 + * 邀请用户加入群 * - * @param createReqVO 创建信息 + * @param inviteReqVO 邀请信息 * @return 编号 */ - Long createGroupMember(@Valid ImGroupMemberSaveReqVO createReqVO); + Long inviteGroupMember(@Valid ImGroupMemberInviteReqVO inviteReqVO); /** - * 更新群成员 + * 更新群成员信息 * * @param updateReqVO 更新信息 */ - void updateGroupMember(@Valid ImGroupMemberSaveReqVO updateReqVO); + void updateGroupMember(@Valid ImGroupMemberUpdateReqVO updateReqVO); /** * 删除群成员 * * @param id 编号 */ - void deleteGroupMember(Long id); + void removeGroupMember(Long id); /** * 获得群成员 @@ -46,13 +45,6 @@ public interface ImGroupMemberService { */ ImGroupMemberDO getGroupMember(Long id); - /** - * 获得群成员分页 - * - * @param pageReqVO 分页查询 - * @return 群成员分页 - */ - PageResult getGroupMemberPage(ImGroupMemberPageReqVO pageReqVO); /** * 根据群组 id 查询群成员(包含所有状态) @@ -105,6 +97,18 @@ public interface ImGroupMemberService { List getQuitGroupMemberListByUserId(Long userId, LocalDateTime minQuitTime); /** + * 添加群成员(入群) + *

+ * 插入一条 ENABLE 状态的群成员记录,并设置 joinTime。 + * 可用于创建群时添加群主、邀请入群等场景。 + * + * @param groupId 群编号 + * @param userId 用户编号 + * @return 群成员记录 + */ + ImGroupMemberDO addGroupMember(Long groupId, Long userId); + + /** * 校验用户是否为群的有效成员 * * @param groupId 群编号 @@ -112,4 +116,18 @@ public interface ImGroupMemberService { * @return 群成员记录 */ ImGroupMemberDO validateMemberInGroup(Long groupId, Long userId); + + /** + * 更新群成员的展示信息(群内昵称、群名备注) + *

+ * 内部会校验用户是否为群的有效成员 + * + * @param groupId 群编号 + * @param userId 用户编号 + * @param displayUserName 群内昵称(null 则不更新) + * @param displayGroupName 群名备注(null 则不更新) + */ + void updateGroupMember(Long groupId, Long userId, + String displayUserName, String displayGroupName); + } diff --git a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberServiceImpl.java b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberServiceImpl.java index 111c0e4..f38e546 100644 --- a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberServiceImpl.java +++ b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberServiceImpl.java @@ -1,14 +1,13 @@ package cn.iocoder.yudao.module.im.service.group; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; -import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberPageReqVO; -import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberSaveReqVO; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberInviteReqVO; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberUpdateReqVO; import org.springframework.stereotype.Service; import jakarta.annotation.Resource; import org.springframework.validation.annotation.Validated; import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; -import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMemberMapper; @@ -32,16 +31,19 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService { private ImGroupMemberMapper imGroupMemberMapper; @Override - public Long createGroupMember(ImGroupMemberSaveReqVO createReqVO) { + public Long inviteGroupMember(ImGroupMemberInviteReqVO inviteReqVO) { + // TODO @AI:需要群主校验下; // 插入 - ImGroupMemberDO groupMember = BeanUtils.toBean(createReqVO, ImGroupMemberDO.class); + ImGroupMemberDO groupMember = BeanUtils.toBean(inviteReqVO, ImGroupMemberDO.class); + // TODO @AI:默认字段的设置; imGroupMemberMapper.insert(groupMember); + // TODO @AI:或者调用内部的 createGroupMember 方法,设置默认字段的值; // 返回 return groupMember.getId(); } @Override - public void updateGroupMember(ImGroupMemberSaveReqVO updateReqVO) { + public void updateGroupMember(ImGroupMemberUpdateReqVO updateReqVO) { // 校验存在 validateGroupMemberExists(updateReqVO.getId()); // 更新 @@ -49,10 +51,13 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService { imGroupMemberMapper.updateById(updateObj); } + // TODO @AI:是不是不存在删除一说? @Override - public void deleteGroupMember(Long id) { + public void removeGroupMember(Long id) { + // TODO @AI:需要群主校验下; // 校验存在 validateGroupMemberExists(id); + // 删除 imGroupMemberMapper.deleteById(id); } @@ -68,10 +73,6 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService { return imGroupMemberMapper.selectById(id); } - @Override - public PageResult getGroupMemberPage(ImGroupMemberPageReqVO pageReqVO) { - return imGroupMemberMapper.selectPage(pageReqVO); - } @Override public List getGroupMemberListByGroupId(Long groupId) { @@ -90,12 +91,12 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService { @Override public List getActiveGroupMemberListByGroupId(Long groupId) { - return imGroupMemberMapper.selectEnabledListByGroupId(groupId); + return imGroupMemberMapper.selectListByGroupIdAndStatus(groupId, CommonStatusEnum.ENABLE.getStatus()); } @Override public List getActiveGroupMemberListByUserId(Long userId) { - return imGroupMemberMapper.selectEnabledListByUserId(userId); + return imGroupMemberMapper.selectListByUserIdAndStatus(userId, CommonStatusEnum.ENABLE.getStatus()); } @Override @@ -104,6 +105,16 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService { } @Override + public ImGroupMemberDO addGroupMember(Long groupId, Long userId) { + ImGroupMemberDO member = new ImGroupMemberDO() + .setGroupId(groupId).setUserId(userId) + .setStatus(CommonStatusEnum.ENABLE.getStatus()).setJoinTime(LocalDateTime.now()); + // TODO @AI:要不要有个 initXXX 方法,设置默认字段的值; + imGroupMemberMapper.insert(member); + return member; + } + + @Override public ImGroupMemberDO validateMemberInGroup(Long groupId, Long userId) { ImGroupMemberDO member = imGroupMemberMapper.selectByGroupIdAndUserId(groupId, userId); if (member == null || CommonStatusEnum.DISABLE.getStatus().equals(member.getStatus())) { @@ -112,4 +123,16 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService { return member; } + @Override + public void updateGroupMember(Long groupId, Long userId, String displayUserName, String displayGroupName) { + // 1. 校验是群的有效成员 + ImGroupMemberDO member = validateMemberInGroup(groupId, userId); + + // 2. 更新展示信息 + imGroupMemberMapper.updateById(new ImGroupMemberDO().setId(member.getId()) + .setDisplayUserName(displayUserName).setDisplayGroupName(displayGroupName)); + + // TODO @AI:需要分析下,是否需要 + } + } diff --git a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupService.java b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupService.java index 8fddb7b..99aa13a 100644 --- a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupService.java +++ b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupService.java @@ -1,9 +1,9 @@ package cn.iocoder.yudao.module.im.service.group; -import jakarta.validation.*; -import cn.iocoder.yudao.module.im.controller.admin.group.vo.*; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupCreateReqVO; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupUpdateReqVO; import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; -import cn.iocoder.yudao.framework.common.pojo.PageResult; +import jakarta.validation.Valid; import java.util.List; @@ -16,25 +16,33 @@ public interface ImGroupService { /** * 创建群 + *

+ * 同时将当前登录用户设置为群主,并插入群主的群成员记录 * * @param createReqVO 创建信息 - * @return 编号 + * @param userId 当前登录用户编号(群主) + * @return 创建后的群信息 */ - Long createGroup(@Valid ImGroupSaveReqVO createReqVO); + ImGroupDO createGroup(@Valid ImGroupCreateReqVO createReqVO, Long userId); /** - * 更新群 + * 更新群信息 * * @param updateReqVO 更新信息 + * @param userId 当前登录用户编号 + * @return 更新后的群信息 */ - void updateGroup(@Valid ImGroupSaveReqVO updateReqVO); + ImGroupDO updateGroup(@Valid ImGroupUpdateReqVO updateReqVO, Long userId); /** - * 删除群 + * 解散群 + *

+ * 仅群主可执行 * - * @param id 编号 + * @param id 群编号 + * @param userId 当前登录用户编号 */ - void deleteGroup(Long id); + void dissolveGroup(Long id, Long userId); /** * 获得群 @@ -45,14 +53,6 @@ public interface ImGroupService { ImGroupDO getGroup(Long id); /** - * 获得群分页 - * - * @param pageReqVO 分页查询 - * @return 群分页 - */ - PageResult getGroupPage(ImGroupPageReqVO pageReqVO); - - /** * 校验群存在且未封禁、未解散 * * @param groupId 群编号 @@ -68,6 +68,6 @@ public interface ImGroupService { * @param userId 用户编号 * @return 群列表 */ - List getMyGroupList(Long userId); + List getActiveGroupList(Long userId); } diff --git a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImpl.java b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImpl.java index 6bcc40e..359907d 100644 --- a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImpl.java +++ b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImpl.java @@ -1,29 +1,39 @@ package cn.iocoder.yudao.module.im.service.group; +import cn.hutool.core.bean.BeanUtil; +import cn.hutool.core.bean.copier.CopyOptions; import cn.hutool.core.collection.CollUtil; -import org.springframework.stereotype.Service; -import jakarta.annotation.Resource; -import org.springframework.validation.annotation.Validated; - - -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; -import cn.iocoder.yudao.module.im.controller.admin.group.vo.*; +import cn.hutool.core.util.ObjUtil; +import cn.hutool.extra.spring.SpringUtil; +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupCreateReqVO; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupUpdateReqVO; import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; -import cn.iocoder.yudao.framework.common.pojo.PageResult; -import cn.iocoder.yudao.framework.common.util.object.BeanUtils; - import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMapper; -import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMemberMapper; +import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; +import cn.iocoder.yudao.module.im.service.websocket.dto.ImGroupMessageDTO; +import jakarta.annotation.Resource; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.context.annotation.Lazy; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.validation.annotation.Validated; +import java.time.LocalDateTime; import java.util.Collections; import java.util.List; import java.util.Set; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; +import static cn.iocoder.yudao.module.im.dal.redis.RedisKeyConstants.GROUP; import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*; // TODO @芋艿:群主的调整,暂时不考虑。后续在弄; +// DONE @AI:groupdo 增加一个缓存;spring cache 实现; /** * 群 Service 实现类 * @@ -34,103 +44,132 @@ import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*; public class ImGroupServiceImpl implements ImGroupService { @Resource - private ImGroupMapper imGroupMapper; + private ImGroupMapper groupMapper; @Resource - private ImGroupMemberMapper imGroupMemberMapper; + @Lazy // 避免循环依赖 + private ImGroupMemberService groupMemberService; + @Resource + private ImWebSocketService webSocketService; - // TODO @AI:返回对象; - // TODO @AI:要不要拆分 VO;ImGroupCreateReqVO(name) @Override - public Long createGroup(ImGroupSaveReqVO createReqVO) { - // 插入 - ImGroupDO group = BeanUtils.toBean(createReqVO, ImGroupDO.class); - imGroupMapper.insert(group); - - // TODO @AI:群主 GroupMember; - - // TODO 多终端推送消息(group message) - return group.getId(); + @Transactional(rollbackFor = Exception.class) + public ImGroupDO createGroup(ImGroupCreateReqVO createReqVO, Long userId) { + // 1. 插入群记录 + ImGroupDO group = BeanUtils.toBean(createReqVO, ImGroupDO.class) + .setOwnerUserId(userId).setStatus(CommonStatusEnum.ENABLE.getStatus()); + groupMapper.insert(group); + + // 2. 将群主插入为群成员 + groupMemberService.addGroupMember(group.getId(), userId); + + // 3. 推送群创建事件给群主(多端同步) + webSocketService.sendGroupMessageAsync(userId, + ImGroupMessageDTO.ofGroupCreate(userId, group.getId())); + return group; } - // TODO @AI:要不要拆分 VO;ImGroupUpdateReqVO(name、avatar、notice);我在本群的昵称/群名备注; - // TODO 1. 需要校验群主:name、avatar、notice;如果修改群信息,是不是要群发消息?(群信息变更消息); - // TODO 2. 需要校验群成员:我在本群的昵称/群名备注 - // TODO @AI:返回 返回对象; @Override - public void updateGroup(ImGroupSaveReqVO updateReqVO) { - // 校验存在 - validateGroupExists(updateReqVO.getId()); + @CacheEvict(cacheNames = GROUP, key = "#updateReqVO.id") + @Transactional(rollbackFor = Exception.class) + public ImGroupDO updateGroup(ImGroupUpdateReqVO updateReqVO, Long userId) { + // 1. 校验群存在 + ImGroupDO group = validateGroupExists(updateReqVO.getId()); + + // 2. 更新群信息(name、avatar、notice → im_group 表) + boolean hasGroupInfoChange = !ObjUtil.isAllEmpty( + updateReqVO.getName(), updateReqVO.getAvatar(), updateReqVO.getNotice()); + if (hasGroupInfoChange) { + if (ObjUtil.notEqual(group.getOwnerUserId(), userId)) { + throw exception(GROUP_NOT_OWNER); + } + // 2.1 更新数据库 + ImGroupDO updateObj = BeanUtils.toBean(updateReqVO, ImGroupDO.class); + groupMapper.updateById(updateObj); + // 同步到内存中的 group 对象,避免再次查询 + BeanUtil.copyProperties(updateReqVO, group, CopyOptions.create().ignoreNullValue()); + + // 2.2 群信息的变更,推送给所有群成员 + List members = groupMemberService.getActiveGroupMemberListByGroupId(group.getId()); + Set memberUserIds = convertSet(members, ImGroupMemberDO::getUserId); + webSocketService.sendGroupMessageAsync(memberUserIds, + ImGroupMessageDTO.ofGroupUpdate(userId, group.getId())); + } - // 更新 - ImGroupDO updateObj = BeanUtils.toBean(updateReqVO, ImGroupDO.class); - imGroupMapper.updateById(updateObj); + // 3. 更新当前用户的群成员信息(displayUserName、displayGroupName → im_group_member 表) + // TODO @AI:ObjUtil.isAllEmpty(? + if (updateReqVO.getDisplayUserName() != null || updateReqVO.getDisplayGroupName() != null) { + groupMemberService.updateGroupMember(updateReqVO.getId(), userId, + updateReqVO.getDisplayUserName(), updateReqVO.getDisplayGroupName()); + } + return group; } - // TODO @AI:dissolve 解散群; @Override - public void deleteGroup(Long id) { - // 校验存在 - validateGroupExists(id); - // TODO @AI:群主才可以; + @CacheEvict(cacheNames = GROUP, key = "#id") + @Transactional(rollbackFor = Exception.class) + public void dissolveGroup(Long id, Long userId) { + // 1.1 校验群存在 + ImGroupDO group = validateGroupExists(id); + // 1.2 校验当前用户是群主 + if (ObjUtil.notEqual(group.getOwnerUserId(), userId)) { + throw exception(GROUP_NOT_OWNER); + } + // TODO @AI:增加一个是否为群主的 public 方法;因为别的模块也需要使用; - // TODO @AI:清理已读缓存; + // 3. 更新群状态为已解散 + // TODO @AI:链式调用; + ImGroupDO updateObj = new ImGroupDO(); + updateObj.setId(id); + updateObj.setStatus(CommonStatusEnum.DISABLE.getStatus()); + updateObj.setDissolvedTime(LocalDateTime.now()); + groupMapper.updateById(updateObj); + // TODO @AI:清理已读缓存; // TODO @AI:推送群解散的提示; - // TODO @AI:推送群信息变化; - - // 删除 - imGroupMapper.deleteById(id); } - // TODO @AI:quit 退群; - + // TODO @AI:缓存还是加在这个方法上;不加在 getGroup 上; @Override public ImGroupDO validateGroupExists(Long id) { - ImGroupDO group = imGroupMapper.selectById(id); + ImGroupDO group = getSelf().getGroup(id); if (group == null) { throw exception(GROUP_NOT_EXISTS); } if (Boolean.TRUE.equals(group.getBanned())) { throw exception(GROUP_BANNED); } - if (Boolean.TRUE.equals(group.getDissolved())) { + if (CommonStatusEnum.DISABLE.getStatus().equals(group.getStatus())) { throw exception(GROUP_DISSOLVED); } return group; } - // TODO @AI:add 加群; - - // TODO @AI:remove 踢人; - @Override + @Cacheable(cacheNames = GROUP, key = "#id", unless = "#result == null") public ImGroupDO getGroup(Long id) { - return imGroupMapper.selectById(id); - } - - @Override - public PageResult getGroupPage(ImGroupPageReqVO pageReqVO) { - return imGroupMapper.selectPage(pageReqVO); + return groupMapper.selectById(id); } @Override - public List getMyGroupList(Long userId) { + public List getActiveGroupList(Long userId) { // 1. 查用户所在的、仍有效的群成员记录(仅 ENABLE 状态) - List members = imGroupMemberMapper.selectEnabledListByUserId(userId); - if (CollUtil.isNotEmpty(members)) { + List members = groupMemberService.getActiveGroupMemberListByUserId(userId); + if (CollUtil.isEmpty(members)) { return Collections.emptyList(); } - Set groupIds = CollectionUtils.convertSet(members, ImGroupMemberDO::getGroupId); + Set groupIds = convertSet(members, ImGroupMemberDO::getGroupId); if (CollUtil.isEmpty(groupIds)) { return Collections.emptyList(); } - // 2. 查群并过滤掉已封禁/解散的 - // TODO @AI:查询的时候,就过滤掉!不要内存过滤; - List groups = imGroupMapper.selectByIds(groupIds); - groups.removeIf(g -> Boolean.TRUE.equals(g.getBanned()) || Boolean.TRUE.equals(g.getDissolved())); - return groups; + + // 2. 批量查询群信息(仅 ENABLE 状态) + return groupMapper.selectListByIds(groupIds, CommonStatusEnum.ENABLE.getStatus(), null); + } + + private ImGroupServiceImpl getSelf() { + return SpringUtil.getBean(getClass()); } } \ No newline at end of file diff --git a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/ImGroupMessageDTO.java b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/ImGroupMessageDTO.java index 2a83c84..91ba494 100644 --- a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/ImGroupMessageDTO.java +++ b/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 { .setGroupId(groupId).setReadCount(readCount).setReceiptStatus(receiptStatus); } + // ==================== 群变更相关 ==================== + + /** + * 构建群创建推送 DTO(通知群成员"有一个新群") + * + * @param senderId 创建者(群主)用户编号 + * @param groupId 群编号 + * @return 群聊 DTO + */ + public static ImGroupMessageDTO ofGroupCreate(Long senderId, Long groupId) { + return new ImGroupMessageDTO() + .setType(ImMessageTypeEnum.GROUP_CREATE.getType()) + .setSenderId(senderId).setGroupId(groupId) + .setSendTime(LocalDateTime.now()); + } + + /** + * 构建群信息变更推送 DTO(通知所有群成员"群信息变了") + * + * @param senderId 操作者用户编号 + * @param groupId 群编号 + * @return 群聊 DTO + */ + public static ImGroupMessageDTO ofGroupUpdate(Long senderId, Long groupId) { + return new ImGroupMessageDTO() + .setType(ImMessageTypeEnum.GROUP_UPDATE.getType()) + .setSenderId(senderId).setGroupId(groupId) + .setSendTime(LocalDateTime.now()); + } + } diff --git a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/ImPrivateMessageDTO.java b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/ImPrivateMessageDTO.java index 9aa9339..44f165c 100644 --- a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/service/websocket/dto/ImPrivateMessageDTO.java +++ b/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 { .setSenderId(senderId).setReceiverId(receiverId); } + // ==================== 好友变更相关 ==================== + /** * 构建好友添加推送 DTO(通知对方"你有一位新好友") * @@ -129,6 +131,7 @@ public class ImPrivateMessageDTO { .setSenderId(userId).setReceiverId(friendUserId) .setSendTime(LocalDateTime.now()); } + /** * 构建好友更新推送 DTO(通知自己多端同步好友属性变更) *