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 8e3df76..4426873 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 @@ -35,6 +35,8 @@ public interface ErrorCodeConstants { ErrorCode GROUP_MEMBER_NOT_EXISTS = new ErrorCode(1_040_500_000, "群成员不存在"); ErrorCode GROUP_MEMBER_NOT_IN_GROUP = new ErrorCode(1_040_500_001, "您已不在该群中"); ErrorCode GROUP_MEMBER_ALREADY_QUIT = new ErrorCode(1_040_500_002, "您已退出该群"); + ErrorCode GROUP_OWNER_CANNOT_QUIT = new ErrorCode(1_040_500_003, "您是群主,不可退出群聊"); + ErrorCode GROUP_CANNOT_REMOVE_SELF = new ErrorCode(1_040_500_004, "不能移除自己"); // ========== 好友 (1-040-600-000) ========== ErrorCode FRIEND_NOT_EXISTS = new ErrorCode(1_040_600_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 db5ff60..52d4bca 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 @@ -51,5 +51,13 @@ public interface ImCommonConstants { * 群解散系统提示 */ String GROUP_DISSOLVE_TIP_MESSAGE = "'{}'解散了群聊"; + /** + * 退群系统提示 + */ + String GROUP_QUIT_TIP_MESSAGE = "您已退出群聊"; + /** + * 踢出群系统提示 + */ + String GROUP_REMOVE_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 95bbcba..74d84e7 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 @@ -34,7 +34,7 @@ public enum ImMessageTypeEnum implements ArrayValuable { GROUP_CREATE(200, "群创建"), // 暂无 GROUP_UPDATE(201, "群信息变更"), // 暂无 - GROUP_DISSOLVE(202, "群解散"); // 暂无 + GROUP_DELETE(202, "群删除"); // 暂无(解散/退群/踢出均用此类型) public static final Integer[] ARRAYS = Arrays.stream(values()).map(ImMessageTypeEnum::getType).toArray(Integer[]::new); 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 050cfe0..0520785 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 @@ -5,6 +5,7 @@ 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.ImGroupRespVO; import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupUpdateReqVO; +import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberInviteReqVO; 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; @@ -30,6 +31,8 @@ public class ImGroupController { @Resource private ImGroupService imGroupService; + // ==================== 群的写操作 ==================== + @PostMapping("/create") @Operation(summary = "创建群") @PreAuthorize("@ss.hasPermission('im:group:create')") @@ -55,6 +58,8 @@ public class ImGroupController { return success(true); } + // ==================== 群的读操作 ==================== + @GetMapping("/get") @Operation(summary = "获得群") @Parameter(name = "id", description = "编号", required = true, example = "1024") @@ -71,4 +76,31 @@ public class ImGroupController { return success(BeanUtils.toBean(groups, ImGroupRespVO.class)); } + // ==================== 群成员的写操作 ==================== + + @PostMapping("/invite") + @Operation(summary = "邀请用户加入群") + @PreAuthorize("@ss.hasPermission('im:group:update')") + public CommonResult inviteGroupMember(@Valid @RequestBody ImGroupMemberInviteReqVO inviteReqVO) { + return success(imGroupService.inviteGroupMember(getLoginUserId(), inviteReqVO)); + } + + @DeleteMapping("/quit") + @Operation(summary = "退出群") + @Parameter(name = "groupId", description = "群编号", required = true) + @PreAuthorize("@ss.hasPermission('im:group:update')") + public CommonResult quitGroup(@RequestParam("groupId") Long groupId) { + imGroupService.quitGroup(groupId, getLoginUserId()); + return success(true); + } + + @DeleteMapping("/kicking") + @Operation(summary = "移除群成员") + @PreAuthorize("@ss.hasPermission('im:group:update')") + public CommonResult removeGroupMember(@RequestParam("groupId") Long groupId, + @RequestParam("memberUserId") Long memberUserId) { + imGroupService.removeGroupMember(groupId, memberUserId, getLoginUserId()); + return success(true); + } + } 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 d96c9de..6c66369 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 @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.im.controller.admin.group; import cn.iocoder.yudao.framework.common.pojo.CommonResult; 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.ImGroupMemberInviteReqVO; import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberRespVO; import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberUpdateReqVO; import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; @@ -24,6 +23,7 @@ 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; +import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.getLoginUserId; @Tag(name = "管理后台 - 群成员") @RestController @@ -32,32 +32,16 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils. public class ImGroupMemberController { @Resource - private ImGroupMemberService imGroupMemberService; + private ImGroupMemberService groupMemberService; @Resource private AdminUserApi adminUserApi; - @PostMapping("/invite") - @Operation(summary = "邀请用户加入群") - @PreAuthorize("@ss.hasPermission('im:group-member:create')") - public CommonResult inviteGroupMember(@Valid @RequestBody ImGroupMemberInviteReqVO inviteReqVO) { - return success(imGroupMemberService.inviteGroupMember(inviteReqVO)); - } - @PutMapping("/update") @Operation(summary = "更新群成员") @PreAuthorize("@ss.hasPermission('im:group-member:update')") public CommonResult updateGroupMember(@Valid @RequestBody ImGroupMemberUpdateReqVO updateReqVO) { - imGroupMemberService.updateGroupMember(updateReqVO); - return success(true); - } - - @DeleteMapping("/remove") - @Operation(summary = "移除群成员(踢人)") - @Parameter(name = "id", description = "群成员编号", required = true) - @PreAuthorize("@ss.hasPermission('im:group-member:delete')") - public CommonResult removeGroupMember(@RequestParam("id") Long id) { - imGroupMemberService.removeGroupMember(id); + groupMemberService.updateGroupMember(getLoginUserId(), updateReqVO); return success(true); } @@ -66,7 +50,7 @@ public class ImGroupMemberController { @Parameter(name = "id", description = "编号", required = true, example = "1024") @PreAuthorize("@ss.hasPermission('im:group-member:query')") public CommonResult getGroupMember(@RequestParam("id") Long id) { - ImGroupMemberDO groupMember = imGroupMemberService.getGroupMember(id); + ImGroupMemberDO groupMember = groupMemberService.getGroupMember(id); return success(BeanUtils.toBean(groupMember, ImGroupMemberRespVO.class)); } @@ -74,7 +58,7 @@ public class ImGroupMemberController { @Operation(summary = "获得指定群的成员列表") @Parameter(name = "groupId", description = "群编号", required = true, example = "1024") public CommonResult> getGroupMemberList(@RequestParam("groupId") Long groupId) { - List members = imGroupMemberService.getGroupMemberListByGroupId(groupId); + List members = groupMemberService.getGroupMemberListByGroupId(groupId); // 批量聚合 AdminUser 信息(昵称 / 头像) Map userMap = adminUserApi.getUserMap( convertList(members, ImGroupMemberDO::getUserId)); diff --git a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/member/ImGroupMemberPageReqVO.java b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/member/ImGroupMemberPageReqVO.java deleted file mode 100644 index 7161c24..0000000 --- a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/member/ImGroupMemberPageReqVO.java +++ /dev/null @@ -1,33 +0,0 @@ -package cn.iocoder.yudao.module.im.controller.admin.group.vo.member; - -import cn.iocoder.yudao.framework.common.pojo.PageParam; -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; -import org.springframework.format.annotation.DateTimeFormat; - -import java.time.LocalDateTime; - -import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND; - -@Schema(description = "管理后台 - 群成员分页 Request VO") -@Data -@EqualsAndHashCode(callSuper = true) -@ToString(callSuper = true) -public class ImGroupMemberPageReqVO extends PageParam { - - @Schema(description = "群编号", example = "13279") - private Long groupId; - - @Schema(description = "用户编号", example = "21730") - private Long userId; - - @Schema(description = "成员状态", example = "0") - private Integer status; - - @Schema(description = "创建时间") - @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) - private LocalDateTime[] createTime; - -} \ 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/ImGroupMemberSaveReqVO.java b/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/member/ImGroupMemberSaveReqVO.java deleted file mode 100644 index c2f0669..0000000 --- a/yudao-module-im/yudao-module-im-biz/src/main/java/cn/iocoder/yudao/module/im/controller/admin/group/vo/member/ImGroupMemberSaveReqVO.java +++ /dev/null @@ -1,24 +0,0 @@ -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 ImGroupMemberSaveReqVO { - - @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17071") - private Long id; - - @Schema(description = "群编号", example = "13279") - private Long groupId; - - @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "21730") - @NotNull(message = "用户编号不能为空") - private Long userId; - - @Schema(description = "是否免打扰") - private Boolean muted; - -} \ 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 index 02a33a2..3e31cb9 100644 --- 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 @@ -3,14 +3,22 @@ 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; +import lombok.experimental.Accessors; @Schema(description = "管理后台 - 群成员更新 Request VO") @Data +@Accessors(chain = true) public class ImGroupMemberUpdateReqVO { - @Schema(description = "群成员编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "17071") - @NotNull(message = "群成员编号不能为空") - private Long id; + @Schema(description = "群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + @NotNull(message = "群编号不能为空") + private Long groupId; + + @Schema(description = "群内昵称", example = "芋头") + private String displayUserName; + + @Schema(description = "群名备注", example = "公司群") + private String displayGroupName; @Schema(description = "是否免打扰") private Boolean muted; 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 7b1871a..02206a7 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,6 +1,5 @@ package cn.iocoder.yudao.module.im.service.group; -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; @@ -16,28 +15,6 @@ import java.util.List; public interface ImGroupMemberService { /** - * 邀请用户加入群 - * - * @param inviteReqVO 邀请信息 - * @return 编号 - */ - Long inviteGroupMember(@Valid ImGroupMemberInviteReqVO inviteReqVO); - - /** - * 更新群成员信息 - * - * @param updateReqVO 更新信息 - */ - void updateGroupMember(@Valid ImGroupMemberUpdateReqVO updateReqVO); - - /** - * 删除群成员 - * - * @param id 编号 - */ - void removeGroupMember(Long id); - - /** * 获得群成员 * * @param id 编号 @@ -101,17 +78,24 @@ public interface ImGroupMemberService { ImGroupMemberDO validateMemberInGroup(Long groupId, Long userId); /** - * 更新群成员的展示信息(群内昵称、群名备注) + * 更新群成员信息(群内昵称、群名备注、免打扰等) *

* 内部会校验用户是否为群的有效成员 * - * @param groupId 群编号 - * @param userId 用户编号 - * @param displayUserName 群内昵称(null 则不更新) - * @param displayGroupName 群名备注(null 则不更新) + * @param userId 当前登录用户编号 + * @param updateReqVO 更新信息 + */ + void updateGroupMember(Long userId, @Valid ImGroupMemberUpdateReqVO updateReqVO); + + /** + * 移除指定群成员(设置为 DISABLE 状态) + *

+ * 用于退群、踢出场景 + * + * @param groupId 群编号 + * @param userId 用户编号 */ - void updateGroupMember(Long groupId, Long userId, - String displayUserName, String displayGroupName); + void removeGroupMember(Long groupId, Long userId); /** * 移除群的全部成员(设置为 DISABLE 状态) 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 fd1b7a7..d6eb01e 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 @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.im.service.group; 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.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 cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMemberMapper; @@ -14,7 +13,6 @@ import java.time.LocalDateTime; import java.util.List; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.GROUP_MEMBER_NOT_EXISTS; import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.GROUP_MEMBER_NOT_IN_GROUP; /** @@ -30,43 +28,6 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService { private ImGroupMemberMapper imGroupMemberMapper; @Override - public Long inviteGroupMember(ImGroupMemberInviteReqVO inviteReqVO) { - // TODO @AI:需要群主校验下; - // 插入 - ImGroupMemberDO groupMember = BeanUtils.toBean(inviteReqVO, ImGroupMemberDO.class); - // TODO @AI:默认字段的设置; - imGroupMemberMapper.insert(groupMember); - // TODO @AI:或者调用内部的 createGroupMember 方法,设置默认字段的值; - // 返回 - return groupMember.getId(); - } - - @Override - public void updateGroupMember(ImGroupMemberUpdateReqVO updateReqVO) { - // 校验存在 - validateGroupMemberExists(updateReqVO.getId()); - // 更新 - ImGroupMemberDO updateObj = BeanUtils.toBean(updateReqVO, ImGroupMemberDO.class); - imGroupMemberMapper.updateById(updateObj); - } - - @Override - public void removeGroupMember(Long id) { - // TODO @AI:需要群主校验下; - // 校验存在 - validateGroupMemberExists(id); - - // 删除 - imGroupMemberMapper.deleteById(id); - } - - private void validateGroupMemberExists(Long id) { - if (imGroupMemberMapper.selectById(id) == null) { - throw exception(GROUP_MEMBER_NOT_EXISTS); - } - } - - @Override public ImGroupMemberDO getGroupMember(Long id) { return imGroupMemberMapper.selectById(id); } @@ -96,7 +57,6 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService { ImGroupMemberDO member = new ImGroupMemberDO() .setGroupId(groupId).setUserId(userId) .setStatus(CommonStatusEnum.ENABLE.getStatus()).setJoinTime(LocalDateTime.now()); - // TODO @AI:要不要有个 initXXX 方法,设置默认字段的值; imGroupMemberMapper.insert(member); return member; } @@ -111,15 +71,23 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService { } @Override - public void updateGroupMember(Long groupId, Long userId, String displayUserName, String displayGroupName) { + public void updateGroupMember(Long userId, ImGroupMemberUpdateReqVO updateReqVO) { // 1. 校验是群的有效成员 - ImGroupMemberDO member = validateMemberInGroup(groupId, userId); + ImGroupMemberDO member = validateMemberInGroup(updateReqVO.getGroupId(), userId); + // 2. 更新群成员信息 + ImGroupMemberDO updateObj = BeanUtils.toBean(updateReqVO, ImGroupMemberDO.class) + .setId(member.getId()); + imGroupMemberMapper.updateById(updateObj); + } - // 2. 更新展示信息 + @Override + public void removeGroupMember(Long groupId, Long userId) { + // 1. 校验是群的有效成员 + ImGroupMemberDO member = validateMemberInGroup(groupId, userId); + // 2. 更新为退群状态 imGroupMemberMapper.updateById(new ImGroupMemberDO().setId(member.getId()) - .setDisplayUserName(displayUserName).setDisplayGroupName(displayGroupName)); - - // TODO @AI:需要分析下,是否需要 + .setStatus(CommonStatusEnum.DISABLE.getStatus()) + .setQuitTime(LocalDateTime.now())); } @Override 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 7c36059..01a033c 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 @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.im.service.group; 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.controller.admin.group.vo.member.ImGroupMemberInviteReqVO; import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; import jakarta.validation.Valid; @@ -14,6 +15,8 @@ import java.util.List; */ public interface ImGroupService { + // ==================== 群的写操作 ==================== + /** * 创建群 *

@@ -44,6 +47,8 @@ public interface ImGroupService { */ void dissolveGroup(Long id, Long userId); + // ==================== 群的读操作 ==================== + /** * 获得群 * @@ -80,4 +85,40 @@ public interface ImGroupService { */ List getActiveGroupList(Long userId); + // ==================== 群成员的写操作 ==================== + // 说明:群成员的写操作统一放在 ImGroupService,而非 ImGroupMemberService, + // 保持 ImGroupMemberService 无 WebSocket 推送等外部依赖,职责更单一。 + + /** + * 邀请用户加入群 + *

+ * 仅群主可执行 + * + * @param userId 当前登录用户编号(群主) + * @param inviteReqVO 邀请信息 + * @return 群成员编号 + */ + Long inviteGroupMember(Long userId, @Valid ImGroupMemberInviteReqVO inviteReqVO); + + /** + * 退群 + *

+ * 群主不可退群(只能解散) + * + * @param groupId 群编号 + * @param userId 当前登录用户编号 + */ + void quitGroup(Long groupId, Long userId); + + /** + * 移除群成员(踢人) + *

+ * 仅群主可执行,且不能移除自己 + * + * @param groupId 群编号 + * @param memberUserId 被移除的用户编号 + * @param userId 当前登录用户编号(群主) + */ + void removeGroupMember(Long groupId, Long memberUserId, 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 6f0a446..c7c8bed 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 @@ -10,6 +10,8 @@ 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.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.ImGroupDO; import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMapper; @@ -35,7 +37,7 @@ import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionU 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.*; -import static cn.iocoder.yudao.module.im.enums.ImCommonConstants.GROUP_DISSOLVE_TIP_MESSAGE; +import static cn.iocoder.yudao.module.im.enums.ImCommonConstants.*; // TODO @芋艿:群主的调整,暂时不考虑。后续在弄; /** @@ -62,6 +64,8 @@ public class ImGroupServiceImpl implements ImGroupService { @Resource private AdminUserApi adminUserApi; + // ==================== 群的写操作 ==================== + @Override @Transactional(rollbackFor = Exception.class) public ImGroupDO createGroup(ImGroupCreateReqVO createReqVO, Long userId) { @@ -107,10 +111,10 @@ public class ImGroupServiceImpl implements ImGroupService { } // 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()); + if (!ObjUtil.isAllEmpty(updateReqVO.getDisplayUserName(), updateReqVO.getDisplayGroupName())) { + groupMemberService.updateGroupMember(userId, new ImGroupMemberUpdateReqVO() + .setGroupId(updateReqVO.getId()).setDisplayUserName(updateReqVO.getDisplayUserName()) + .setDisplayGroupName(updateReqVO.getDisplayGroupName())); } return group; } @@ -137,12 +141,68 @@ public class ImGroupServiceImpl implements ImGroupService { AdminUserRespDTO user = adminUserApi.getUser(userId); String tipContent = StrUtil.format(GROUP_DISSOLVE_TIP_MESSAGE, user.getNickname()); groupMessageService.sendTipGroupMessage(userId, id, memberUserIds, tipContent); - // 3.2 推送群解散事件给所有原群成员 + // 3.2 推送群删除事件给所有原群成员 webSocketService.sendGroupMessageAsync(memberUserIds, - ImGroupMessageDTO.ofGroupDissolve(userId, id)); + ImGroupMessageDTO.ofGroupDelete(userId, id)); + } + + // ==================== 群成员的写操作 ==================== + + @Override + public Long inviteGroupMember(Long userId, ImGroupMemberInviteReqVO inviteReqVO) { + // 1. 校验群存在 + 当前用户是群主 + validateGroupOwner(inviteReqVO.getGroupId(), userId); + + // 2. 添加群成员 + ImGroupMemberDO member = groupMemberService.addGroupMember(inviteReqVO.getGroupId(), inviteReqVO.getUserId()); + return member.getId(); } @Override + public void quitGroup(Long groupId, Long userId) { + // 1. 校验群存在 + ImGroupDO group = validateGroupExists(groupId); + // 2. 群主不可退群 + if (ObjUtil.equal(group.getOwnerUserId(), userId)) { + throw exception(GROUP_OWNER_CANNOT_QUIT); + } + + // 3.1 移除群成员 + groupMemberService.removeGroupMember(groupId, userId); + // 3.2 清理已读缓存 + groupMessageService.deleteReadMaxMessageId(groupId, userId); + + // 4.1 发送退群提示消息(TIP_TEXT) + groupMessageService.sendTipGroupMessage(userId, groupId, Set.of(userId), GROUP_QUIT_TIP_MESSAGE); + // 4.2 推送群删除事件 + webSocketService.sendGroupMessageAsync(Set.of(userId), + ImGroupMessageDTO.ofGroupDelete(userId, groupId)); + } + + @Override + public void removeGroupMember(Long groupId, Long memberUserId, Long userId) { + // 1. 校验群存在 + 当前用户是群主 + validateGroupOwner(groupId, userId); + // 2. 不能移除自己 + if (ObjUtil.equal(memberUserId, userId)) { + throw exception(GROUP_CANNOT_REMOVE_SELF); + } + + // 3.1 移除群成员 + groupMemberService.removeGroupMember(groupId, memberUserId); + // 3.2 清理已读缓存 + groupMessageService.deleteReadMaxMessageId(groupId, memberUserId); + + // 4.1 发送踢出提示消息(TIP_TEXT) + groupMessageService.sendTipGroupMessage(userId, groupId, Set.of(memberUserId), GROUP_REMOVE_TIP_MESSAGE); + // 4.2 推送群删除事件 + webSocketService.sendGroupMessageAsync(Set.of(memberUserId), + ImGroupMessageDTO.ofGroupDelete(userId, groupId)); + } + + // ==================== 群的读操作 ==================== + + @Override public ImGroupDO validateGroupExists(Long id) { ImGroupDO group = getSelf().getGroup(id); if (group == null) { @@ -185,7 +245,7 @@ public class ImGroupServiceImpl implements ImGroupService { } // 2. 批量查询群信息(仅 ENABLE 状态) - return groupMapper.selectListByIds(groupIds, CommonStatusEnum.ENABLE.getStatus(), null); + return groupMapper.selectListByIds(groupIds, CommonStatusEnum.ENABLE.getStatus(), false); } private ImGroupServiceImpl getSelf() { 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 8aa52c3..6304bf8 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 @@ -147,15 +147,17 @@ public class ImGroupMessageDTO { } /** - * 构建群解散推送 DTO(通知所有群成员"群已解散") + * 构建群删除推送 DTO(通知群成员"群已移除") + *

+ * 适用于群解散、退群、踢出等场景 * - * @param senderId 操作者(群主)用户编号 + * @param senderId 操作者用户编号 * @param groupId 群编号 * @return 群聊 DTO */ - public static ImGroupMessageDTO ofGroupDissolve(Long senderId, Long groupId) { + public static ImGroupMessageDTO ofGroupDelete(Long senderId, Long groupId) { return new ImGroupMessageDTO() - .setType(ImMessageTypeEnum.GROUP_DISSOLVE.getType()) + .setType(ImMessageTypeEnum.GROUP_DELETE.getType()) .setSenderId(senderId).setGroupId(groupId) .setSendTime(LocalDateTime.now()); }