From 37f0a8a01d727f38ca7576a33ee0a3bf5b13acab Mon Sep 17 00:00:00 2001 From: YunaiV Date: Thu, 30 Apr 2026 08:46:28 +0800 Subject: [PATCH] ✨ feat(im): 增加 friend、group、message、sensitiveword 的管理接口 --- yudao-module-im/pom.xml | 8 ++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/friend/ImFriendManagerController.java | 37 +++++++++++++++++++++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/friend/vo/ImFriendManagerPageReqVO.java | 34 ++++++++++++++++++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/friend/vo/ImFriendManagerRespVO.java | 45 +++++++++++++++++++++++++++++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/ImGroupManagerController.java | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/ImGroupMemberManagerController.java | 38 ++++++++++++++++++++++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/ImGroupMessageManagerController.java | 47 +++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/ImPrivateMessageManagerController.java | 47 +++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/group/ImGroupMessageManagerPageReqVO.java | 35 +++++++++++++++++++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/group/ImGroupMessageManagerRespVO.java | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/privates/ImPrivateMessageManagerPageReqVO.java | 34 ++++++++++++++++++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/privates/ImPrivateMessageManagerRespVO.java | 45 +++++++++++++++++++++++++++++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/sensitiveword/ImSensitiveWordManagerController.java | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/sensitiveword/vo/ImSensitiveWordPageReqVO.java | 28 ++++++++++++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/sensitiveword/vo/ImSensitiveWordRespVO.java | 27 +++++++++++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/sensitiveword/vo/ImSensitiveWordSaveReqVO.java | 26 ++++++++++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/friend/ImFriendMapper.java | 12 ++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupMapper.java | 14 ++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupMemberMapper.java | 26 ++++++++++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImGroupMessageMapper.java | 12 ++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImPrivateMessageMapper.java | 12 ++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/sensitiveword/ImSensitiveWordMapper.java | 17 +++++++++++++++-- yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ErrorCodeConstants.java | 4 ++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/sensitiveword/ImSensitiveWordConfiguration.java | 32 ++++++++++++++++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/sensitiveword/ImSensitiveWordDeny.java | 36 ++++++++++++++++++++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendService.java | 9 +++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImpl.java | 36 +++++++++++++++++++++++++++++++++--- yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberService.java | 29 +++++++++++++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberServiceImpl.java | 46 ++++++++++++++++++++++++++++++++++++++++------ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupService.java | 40 ++++++++++++++++++++++++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImpl.java | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageService.java | 15 +++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java | 41 ++++++++++++++++++++++++++++++++++++++++- yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageService.java | 15 +++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java | 43 +++++++++++++++++++++++++++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/sensitiveword/ImSensitiveWordService.java | 40 ++++++++++++++++++++++++++++++++++++++++ yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/sensitiveword/ImSensitiveWordServiceImpl.java | 146 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------------- 37 files changed, 1292 insertions(+), 45 deletions(-) create mode 100644 yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/friend/ImFriendManagerController.java create mode 100644 yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/friend/vo/ImFriendManagerPageReqVO.java create mode 100644 yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/friend/vo/ImFriendManagerRespVO.java create mode 100644 yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/ImGroupManagerController.java create mode 100644 yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/ImGroupMemberManagerController.java create mode 100644 yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/ImGroupMessageManagerController.java create mode 100644 yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/ImPrivateMessageManagerController.java create mode 100644 yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/group/ImGroupMessageManagerPageReqVO.java create mode 100644 yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/group/ImGroupMessageManagerRespVO.java create mode 100644 yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/privates/ImPrivateMessageManagerPageReqVO.java create mode 100644 yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/privates/ImPrivateMessageManagerRespVO.java create mode 100644 yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/sensitiveword/ImSensitiveWordManagerController.java create mode 100644 yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/sensitiveword/vo/ImSensitiveWordPageReqVO.java create mode 100644 yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/sensitiveword/vo/ImSensitiveWordRespVO.java create mode 100644 yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/sensitiveword/vo/ImSensitiveWordSaveReqVO.java create mode 100644 yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/sensitiveword/ImSensitiveWordConfiguration.java create mode 100644 yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/sensitiveword/ImSensitiveWordDeny.java diff --git a/yudao-module-im/pom.xml b/yudao-module-im/pom.xml index cbb3b6d..8f2f096 100644 --- a/yudao-module-im/pom.xml +++ b/yudao-module-im/pom.xml @@ -62,6 +62,14 @@ yudao-spring-boot-starter-excel + + + + com.github.houbb + sensitive-word + 0.29.5 + + cn.iocoder.boot diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/friend/ImFriendManagerController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/friend/ImFriendManagerController.java new file mode 100644 index 0000000..d5f07ea --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/friend/ImFriendManagerController.java @@ -0,0 +1,37 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.friend; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo.ImFriendManagerPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo.ImFriendManagerRespVO; +import cn.iocoder.yudao.module.im.service.friend.ImFriendService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - IM 好友管理") +@RestController +@RequestMapping("/im/manager/friend") +@Validated +public class ImFriendManagerController { + + @Resource + private ImFriendService friendService; + + @GetMapping("/page") + @Operation(summary = "获得好友关系分页") + @PreAuthorize("@ss.hasPermission('im:manager:friend:query')") + public CommonResult> getFriendPage( + @Valid ImFriendManagerPageReqVO pageReqVO) { + return success(friendService.getFriendPage(pageReqVO)); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/friend/vo/ImFriendManagerPageReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/friend/vo/ImFriendManagerPageReqVO.java new file mode 100644 index 0000000..d31517e --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/friend/vo/ImFriendManagerPageReqVO.java @@ -0,0 +1,34 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +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 = "管理后台 - IM 好友关系分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +public class ImFriendManagerPageReqVO extends PageParam { + + @Schema(description = "用户编号", example = "1024") + private Long userId; + + @Schema(description = "好友用户编号", example = "2048") + private Long friendUserId; + + @Schema(description = "好友状态", example = "0") + private Integer status; // 参见 CommonStatusEnum 枚举类 + + @Schema(description = "是否免打扰", example = "false") + private Boolean muted; + + @Schema(description = "添加好友时间", example = "[2026-04-01 00:00:00, 2026-04-30 23:59:59]") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] addTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/friend/vo/ImFriendManagerRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/friend/vo/ImFriendManagerRespVO.java new file mode 100644 index 0000000..270b326 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/friend/vo/ImFriendManagerRespVO.java @@ -0,0 +1,45 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - IM 好友关系 Response VO") +@Data +public class ImFriendManagerRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long userId; + + @Schema(description = "用户昵称", example = "张三") + private String userNickname; + + @Schema(description = "好友用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") + private Long friendUserId; + + @Schema(description = "好友昵称", example = "李四") + private String friendNickname; + + @Schema(description = "好友展示备注") + private String displayName; + + @Schema(description = "是否免打扰", requiredMode = Schema.RequiredMode.REQUIRED, example = "false") + private Boolean muted; + + @Schema(description = "好友状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + private Integer status; // 参见 CommonStatusEnum 枚举类 + + @Schema(description = "添加好友时间") + private LocalDateTime addTime; + + @Schema(description = "删除好友时间") + private LocalDateTime deleteTime; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/ImGroupManagerController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/ImGroupManagerController.java new file mode 100644 index 0000000..2779d5b --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/ImGroupManagerController.java @@ -0,0 +1,64 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.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.module.im.controller.admin.manager.group.vo.ImGroupManagerBanReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerRespVO; +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; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - IM 群聊管理") +@RestController +@RequestMapping("/im/manager/group") +@Validated +public class ImGroupManagerController { + + @Resource + private ImGroupService groupService; + + @GetMapping("/page") + @Operation(summary = "获得群分页") + @PreAuthorize("@ss.hasPermission('im:manager:group:query')") + public CommonResult> getGroupPage(@Valid ImGroupManagerPageReqVO pageReqVO) { + return success(groupService.getGroupPage(pageReqVO)); + } + + @GetMapping("/get") + @Operation(summary = "获得群详情") + @Parameter(name = "id", description = "群编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('im:manager:group:query')") + public CommonResult getGroup(@RequestParam("id") Long id) { + ImGroupDO group = groupService.getGroup(id); + return success(BeanUtils.toBean(group, ImGroupManagerRespVO.class)); + } + + @PutMapping("/ban") + @Operation(summary = "封禁群") + @PreAuthorize("@ss.hasPermission('im:manager:group:ban')") + public CommonResult banGroup(@Valid @RequestBody ImGroupManagerBanReqVO reqVO) { + groupService.banGroup(reqVO); + return success(true); + } + + @PutMapping("/unban") + @Operation(summary = "解封群") + @Parameter(name = "id", description = "群编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('im:manager:group:ban')") + public CommonResult unbanGroup(@RequestParam("id") Long id) { + groupService.unbanGroup(id); + return success(true); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/ImGroupMemberManagerController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/ImGroupMemberManagerController.java new file mode 100644 index 0000000..88192b6 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/ImGroupMemberManagerController.java @@ -0,0 +1,38 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.group; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.member.ImGroupMemberManagerRespVO; +import cn.iocoder.yudao.module.im.service.group.ImGroupMemberService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - IM 群成员管理") +@RestController +@RequestMapping("/im/manager/group/member") +@Validated +public class ImGroupMemberManagerController { + + @Resource + private ImGroupMemberService groupMemberService; + + @GetMapping("/list") + @Operation(summary = "获得群成员列表") + @Parameter(name = "groupId", description = "群编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('im:manager:group:query')") + public CommonResult> getGroupMemberList(@RequestParam("groupId") Long groupId) { + return success(groupMemberService.getGroupMemberList(groupId)); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/ImGroupMessageManagerController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/ImGroupMessageManagerController.java new file mode 100644 index 0000000..6e1f9c2 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/ImGroupMessageManagerController.java @@ -0,0 +1,47 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.message; + +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.module.im.controller.admin.manager.message.vo.group.ImGroupMessageManagerPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group.ImGroupMessageManagerRespVO; +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; +import cn.iocoder.yudao.module.im.service.message.ImGroupMessageService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - IM 群聊消息") +@RestController +@RequestMapping("/im/manager/message/group") +@Validated +public class ImGroupMessageManagerController { + + @Resource + private ImGroupMessageService groupMessageService; + + @GetMapping("/page") + @Operation(summary = "获得群聊消息分页") + @PreAuthorize("@ss.hasPermission('im:manager:message:query')") + public CommonResult> getGroupMessagePage( + @Valid ImGroupMessageManagerPageReqVO pageReqVO) { + return success(groupMessageService.getGroupMessagePage(pageReqVO)); + } + + @GetMapping("/get") + @Operation(summary = "获得群聊消息详情") + @Parameter(name = "id", description = "消息编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('im:manager:message:query')") + public CommonResult getGroupMessage(@RequestParam("id") Long id) { + ImGroupMessageDO message = groupMessageService.getGroupMessage(id); + return success(BeanUtils.toBean(message, ImGroupMessageManagerRespVO.class)); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/ImPrivateMessageManagerController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/ImPrivateMessageManagerController.java new file mode 100644 index 0000000..67fb34c --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/ImPrivateMessageManagerController.java @@ -0,0 +1,47 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.message; + +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.module.im.controller.admin.manager.message.vo.privates.ImPrivateMessageManagerPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.privates.ImPrivateMessageManagerRespVO; +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; +import cn.iocoder.yudao.module.im.service.message.ImPrivateMessageService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - IM 私聊消息") +@RestController +@RequestMapping("/im/manager/message/private") +@Validated +public class ImPrivateMessageManagerController { + + @Resource + private ImPrivateMessageService privateMessageService; + + @GetMapping("/page") + @Operation(summary = "获得私聊消息分页") + @PreAuthorize("@ss.hasPermission('im:manager:message:query')") + public CommonResult> getPrivateMessagePage( + @Valid ImPrivateMessageManagerPageReqVO pageReqVO) { + return success(privateMessageService.getPrivateMessagePage(pageReqVO)); + } + + @GetMapping("/get") + @Operation(summary = "获得私聊消息详情") + @Parameter(name = "id", description = "消息编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('im:manager:message:query')") + public CommonResult getPrivateMessage(@RequestParam("id") Long id) { + ImPrivateMessageDO message = privateMessageService.getPrivateMessage(id); + return success(BeanUtils.toBean(message, ImPrivateMessageManagerRespVO.class)); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/group/ImGroupMessageManagerPageReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/group/ImGroupMessageManagerPageReqVO.java new file mode 100644 index 0000000..1ce4a7c --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/group/ImGroupMessageManagerPageReqVO.java @@ -0,0 +1,35 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +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 = "管理后台 - IM 群聊消息分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +public class ImGroupMessageManagerPageReqVO extends PageParam { + + @Schema(description = "群编号", example = "1024") + private Long groupId; + + @Schema(description = "发送人编号", example = "1024") + private Long senderId; + + // TODO @AI:参见 ImMessageTypeEnum 枚举类,都放到 // 注释后面。注意加到我的 memory 里。其它类似也处理下。im 的。 + @Schema(description = "消息类型,参见 ImMessageTypeEnum 枚举类", example = "1") + private Integer type; + + @Schema(description = "消息状态,参见 ImMessageStatusEnum 枚举类", example = "0") + private Integer status; + + @Schema(description = "发送时间", example = "[2026-04-01 00:00:00, 2026-04-30 23:59:59]") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] sendTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/group/ImGroupMessageManagerRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/group/ImGroupMessageManagerRespVO.java new file mode 100644 index 0000000..10610da --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/group/ImGroupMessageManagerRespVO.java @@ -0,0 +1,52 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +@Schema(description = "管理后台 - IM 群聊消息 Response VO") +@Data +public class ImGroupMessageManagerRespVO { + + @Schema(description = "消息编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "客户端消息编号", example = "c-uuid-xxx") + private String clientMessageId; + + @Schema(description = "群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long groupId; + + @Schema(description = "群名称", example = "技术交流群") + private String groupName; + + @Schema(description = "发送人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long senderId; + + @Schema(description = "发送人昵称", example = "张三") + private String senderNickname; + + @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer type; // 参见 ImMessageTypeEnum 枚举类 + + @Schema(description = "消息内容(JSON 格式)", requiredMode = Schema.RequiredMode.REQUIRED) + private String content; + + @Schema(description = "消息状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + private Integer status; // 参见 ImMessageStatusEnum 枚举类 + + @Schema(description = "@ 目标用户编号列表") + private List atUserIds; + + @Schema(description = "回执状态", example = "0") + private Integer receiptStatus; // 参见 ImGroupMessageReceiptStatusEnum 枚举类 + + @Schema(description = "发送时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime sendTime; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/privates/ImPrivateMessageManagerPageReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/privates/ImPrivateMessageManagerPageReqVO.java new file mode 100644 index 0000000..95b19d2 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/privates/ImPrivateMessageManagerPageReqVO.java @@ -0,0 +1,34 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.privates; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +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 = "管理后台 - IM 私聊消息分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +public class ImPrivateMessageManagerPageReqVO extends PageParam { + + @Schema(description = "发送人编号", example = "1024") + private Long senderId; + + @Schema(description = "接收人编号", example = "2048") + private Long receiverId; + + @Schema(description = "消息类型,参见 ImMessageTypeEnum 枚举类", example = "1") + private Integer type; + + @Schema(description = "消息状态,参见 ImMessageStatusEnum 枚举类", example = "0") + private Integer status; + + @Schema(description = "发送时间", example = "[2026-04-01 00:00:00, 2026-04-30 23:59:59]") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] sendTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/privates/ImPrivateMessageManagerRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/privates/ImPrivateMessageManagerRespVO.java new file mode 100644 index 0000000..c467a0a --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/privates/ImPrivateMessageManagerRespVO.java @@ -0,0 +1,45 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.privates; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - IM 私聊消息 Response VO") +@Data +public class ImPrivateMessageManagerRespVO { + + @Schema(description = "消息编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "客户端消息编号", example = "c-uuid-xxx") + private String clientMessageId; + + @Schema(description = "发送人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long senderId; + + @Schema(description = "发送人昵称", example = "张三") + private String senderNickname; + + @Schema(description = "接收人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") + private Long receiverId; + + @Schema(description = "接收人昵称", example = "李四") + private String receiverNickname; + + @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer type; // 参见 ImMessageTypeEnum 枚举类 + + @Schema(description = "消息内容(JSON 格式)", requiredMode = Schema.RequiredMode.REQUIRED) + private String content; + + @Schema(description = "消息状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + private Integer status; // 参见 ImMessageStatusEnum 枚举类 + + @Schema(description = "发送时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime sendTime; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/sensitiveword/ImSensitiveWordManagerController.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/sensitiveword/ImSensitiveWordManagerController.java new file mode 100644 index 0000000..febc9cb --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/sensitiveword/ImSensitiveWordManagerController.java @@ -0,0 +1,84 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword; + +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.module.im.controller.admin.manager.sensitiveword.vo.ImSensitiveWordPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo.ImSensitiveWordRespVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo.ImSensitiveWordSaveReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.sensitiveword.ImSensitiveWordDO; +import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - IM 敏感词") +@RestController +@RequestMapping("/im/manager/sensitive-word") +@Validated +public class ImSensitiveWordManagerController { + + @Resource + private ImSensitiveWordService sensitiveWordService; + + @PostMapping("/create") + @Operation(summary = "新增敏感词") + @PreAuthorize("@ss.hasPermission('im:manager:sensitive-word:create')") + public CommonResult createSensitiveWord(@Valid @RequestBody ImSensitiveWordSaveReqVO reqVO) { + return success(sensitiveWordService.createSensitiveWord(reqVO)); + } + + @PutMapping("/update") + @Operation(summary = "修改敏感词") + @PreAuthorize("@ss.hasPermission('im:manager:sensitive-word:update')") + public CommonResult updateSensitiveWord(@Valid @RequestBody ImSensitiveWordSaveReqVO reqVO) { + sensitiveWordService.updateSensitiveWord(reqVO); + return success(true); + } + + @DeleteMapping("/delete") + @Operation(summary = "删除敏感词") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('im:manager:sensitive-word:delete')") + public CommonResult deleteSensitiveWord(@RequestParam("id") Long id) { + sensitiveWordService.deleteSensitiveWord(id); + return success(true); + } + + @DeleteMapping("/delete-list") + @Operation(summary = "批量删除敏感词") + @Parameter(name = "ids", description = "编号列表", required = true) + @PreAuthorize("@ss.hasPermission('im:manager:sensitive-word:delete')") + public CommonResult deleteSensitiveWordList(@RequestParam("ids") List ids) { + sensitiveWordService.deleteSensitiveWordList(ids); + return success(true); + } + + @GetMapping("/page") + @Operation(summary = "获得敏感词分页") + @PreAuthorize("@ss.hasPermission('im:manager:sensitive-word:query')") + public CommonResult> getSensitiveWordPage( + @Valid ImSensitiveWordPageReqVO pageReqVO) { + PageResult pageResult = sensitiveWordService.getSensitiveWordPage(pageReqVO); + return success(BeanUtils.toBean(pageResult, ImSensitiveWordRespVO.class)); + } + + @GetMapping("/get") + @Operation(summary = "获得敏感词详情") + @Parameter(name = "id", description = "编号", required = true, example = "1024") + @PreAuthorize("@ss.hasPermission('im:manager:sensitive-word:query')") + public CommonResult getSensitiveWord(@RequestParam("id") Long id) { + ImSensitiveWordDO word = sensitiveWordService.getSensitiveWord(id); + return success(BeanUtils.toBean(word, ImSensitiveWordRespVO.class)); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/sensitiveword/vo/ImSensitiveWordPageReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/sensitiveword/vo/ImSensitiveWordPageReqVO.java new file mode 100644 index 0000000..36c25e9 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/sensitiveword/vo/ImSensitiveWordPageReqVO.java @@ -0,0 +1,28 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo; + +import cn.iocoder.yudao.framework.common.pojo.PageParam; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; +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 = "管理后台 - IM 敏感词分页 Request VO") +@Data +@EqualsAndHashCode(callSuper = true) +public class ImSensitiveWordPageReqVO extends PageParam { + + @Schema(description = "敏感词,模糊匹配", example = "敏感") + private String word; + + @Schema(description = "状态,参见 CommonStatusEnum 枚举类(0 启用 / 1 禁用)", example = "0") + private Integer status; + + @Schema(description = "创建时间", example = "[2026-04-01 00:00:00, 2026-04-30 23:59:59]") + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) + private LocalDateTime[] createTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/sensitiveword/vo/ImSensitiveWordRespVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/sensitiveword/vo/ImSensitiveWordRespVO.java new file mode 100644 index 0000000..e30fa79 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/sensitiveword/vo/ImSensitiveWordRespVO.java @@ -0,0 +1,27 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +import java.time.LocalDateTime; + +@Schema(description = "管理后台 - IM 敏感词 Response VO") +@Data +public class ImSensitiveWordRespVO { + + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024") + private Long id; + + @Schema(description = "敏感词", requiredMode = Schema.RequiredMode.REQUIRED, example = "敏感词内容") + private String word; + + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + private Integer status; // 参见 CommonStatusEnum 枚举类 + + @Schema(description = "创建人") + private String creator; + + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) + private LocalDateTime createTime; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/sensitiveword/vo/ImSensitiveWordSaveReqVO.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/sensitiveword/vo/ImSensitiveWordSaveReqVO.java new file mode 100644 index 0000000..c7c57a9 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/sensitiveword/vo/ImSensitiveWordSaveReqVO.java @@ -0,0 +1,26 @@ +package cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Data; + +@Schema(description = "管理后台 - IM 敏感词新增 / 修改 Request VO") +@Data +public class ImSensitiveWordSaveReqVO { + + @Schema(description = "编号(修改时必填)", example = "1024") + private Long id; + + @Schema(description = "敏感词", requiredMode = Schema.RequiredMode.REQUIRED, example = "敏感词内容") + @NotBlank(message = "敏感词不能为空") + @Size(max = 64, message = "敏感词长度不能超过 64") + private String word; + + @Schema(description = "状态,参见 CommonStatusEnum 枚举类(0 启用 / 1 禁用)", + requiredMode = Schema.RequiredMode.REQUIRED, example = "0") + @NotNull(message = "状态不能为空") + private Integer status; + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/friend/ImFriendMapper.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/friend/ImFriendMapper.java index dc716f0..4714cc1 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/friend/ImFriendMapper.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/friend/ImFriendMapper.java @@ -1,7 +1,9 @@ package cn.iocoder.yudao.module.im.dal.mysql.friend; +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.manager.friend.vo.ImFriendManagerPageReqVO; import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; import org.apache.ibatis.annotations.Mapper; @@ -43,4 +45,14 @@ public interface ImFriendMapper extends BaseMapperX { .eq(ImFriendDO::getStatus, status)); } + default PageResult selectPage(ImFriendManagerPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(ImFriendDO::getUserId, reqVO.getUserId()) + .eqIfPresent(ImFriendDO::getFriendUserId, reqVO.getFriendUserId()) + .eqIfPresent(ImFriendDO::getStatus, reqVO.getStatus()) + .eqIfPresent(ImFriendDO::getMuted, reqVO.getMuted()) + .betweenIfPresent(ImFriendDO::getAddTime, reqVO.getAddTime()) + .orderByDesc(ImFriendDO::getId)); + } + } diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupMapper.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupMapper.java index 8861658..26a3619 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupMapper.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupMapper.java @@ -1,6 +1,9 @@ package cn.iocoder.yudao.module.im.dal.mysql.group; +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.manager.group.vo.ImGroupManagerPageReqVO; import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; import org.apache.ibatis.annotations.Mapper; @@ -11,4 +14,15 @@ import org.apache.ibatis.annotations.Mapper; */ @Mapper public interface ImGroupMapper extends BaseMapperX { + + default PageResult selectPage(ImGroupManagerPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(ImGroupDO::getName, reqVO.getName()) + .eqIfPresent(ImGroupDO::getOwnerUserId, reqVO.getOwnerUserId()) + .eqIfPresent(ImGroupDO::getStatus, reqVO.getStatus()) + .eqIfPresent(ImGroupDO::getBanned, reqVO.getBanned()) + .betweenIfPresent(ImGroupDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(ImGroupDO::getId)); + } + } \ No newline at end of file diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupMemberMapper.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupMemberMapper.java index 0f23180..b69a09b 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupMemberMapper.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupMemberMapper.java @@ -1,14 +1,19 @@ package cn.iocoder.yudao.module.im.dal.mysql.group; +import cn.hutool.core.collection.CollUtil; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 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.ImGroupMemberDO; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; import org.apache.ibatis.annotations.Mapper; import java.time.LocalDateTime; import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * IM 群成员 Mapper @@ -78,4 +83,25 @@ public interface ImGroupMemberMapper extends BaseMapperX { .eq(ImGroupMemberDO::getStatus, oldStatus)); } + /** + * 批量按 group_id 统计指定状态的成员数 + */ + default Map selectCountMapByGroupIdsAndStatus(Collection groupIds, Integer status) { + if (CollUtil.isEmpty(groupIds)) { + return Collections.emptyMap(); + } + // TODO @AI:使用 mybatis plus join,可以简化这个操作么? + List> rows = selectMaps(Wrappers.query() + .select("group_id AS groupId", "COUNT(*) AS cnt") + .in("group_id", groupIds) + .eq("status", status) + .groupBy("group_id")); + // 转换成 Map + Map result = new HashMap<>(rows.size()); + rows.forEach(row -> result.put( + ((Number) row.get("groupId")).longValue(), + ((Number) row.get("cnt")).longValue())); + return result; + } + } \ No newline at end of file diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImGroupMessageMapper.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImGroupMessageMapper.java index f7fa201..94a55f2 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImGroupMessageMapper.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImGroupMessageMapper.java @@ -1,8 +1,10 @@ package cn.iocoder.yudao.module.im.dal.mysql.message; +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.framework.mybatis.core.query.QueryWrapperX; +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group.ImGroupMessageManagerPageReqVO; import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; import cn.iocoder.yudao.module.im.enums.message.ImGroupMessageReceiptStatusEnum; import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; @@ -113,4 +115,14 @@ public interface ImGroupMessageMapper extends BaseMapperX { .ne(ImGroupMessageDO::getStatus, ImMessageStatusEnum.RECALL.getStatus())); } + default PageResult selectPage(ImGroupMessageManagerPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(ImGroupMessageDO::getGroupId, reqVO.getGroupId()) + .eqIfPresent(ImGroupMessageDO::getSenderId, reqVO.getSenderId()) + .eqIfPresent(ImGroupMessageDO::getType, reqVO.getType()) + .eqIfPresent(ImGroupMessageDO::getStatus, reqVO.getStatus()) + .betweenIfPresent(ImGroupMessageDO::getSendTime, reqVO.getSendTime()) + .orderByDesc(ImGroupMessageDO::getId)); + } + } diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImPrivateMessageMapper.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImPrivateMessageMapper.java index f8a5693..32c0516 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImPrivateMessageMapper.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImPrivateMessageMapper.java @@ -1,8 +1,10 @@ package cn.iocoder.yudao.module.im.dal.mysql.message; +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.framework.mybatis.core.query.QueryWrapperX; +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.privates.ImPrivateMessageManagerPageReqVO; import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum; import org.apache.ibatis.annotations.Mapper; @@ -86,4 +88,14 @@ public interface ImPrivateMessageMapper extends BaseMapperX .eq(ImPrivateMessageDO::getStatus, whereStatus)); } + default PageResult selectPage(ImPrivateMessageManagerPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .eqIfPresent(ImPrivateMessageDO::getSenderId, reqVO.getSenderId()) + .eqIfPresent(ImPrivateMessageDO::getReceiverId, reqVO.getReceiverId()) + .eqIfPresent(ImPrivateMessageDO::getType, reqVO.getType()) + .eqIfPresent(ImPrivateMessageDO::getStatus, reqVO.getStatus()) + .betweenIfPresent(ImPrivateMessageDO::getSendTime, reqVO.getSendTime()) + .orderByDesc(ImPrivateMessageDO::getId)); + } + } diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/sensitiveword/ImSensitiveWordMapper.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/sensitiveword/ImSensitiveWordMapper.java index 49b916d..2b33b78 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/sensitiveword/ImSensitiveWordMapper.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/sensitiveword/ImSensitiveWordMapper.java @@ -1,8 +1,9 @@ package cn.iocoder.yudao.module.im.dal.mysql.sensitiveword; -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.manager.sensitiveword.vo.ImSensitiveWordPageReqVO; import cn.iocoder.yudao.module.im.dal.dataobject.sensitiveword.ImSensitiveWordDO; import org.apache.ibatis.annotations.Mapper; @@ -16,10 +17,22 @@ import java.util.List; @Mapper public interface ImSensitiveWordMapper extends BaseMapperX { - // DONE @AI:已改名为 selectListByStatus,status 作为参数 default List selectListByStatus(Integer status) { return selectList(new LambdaQueryWrapperX() .eq(ImSensitiveWordDO::getStatus, status)); } + default ImSensitiveWordDO selectByWord(String word) { + return selectOne(new LambdaQueryWrapperX() + .eq(ImSensitiveWordDO::getWord, word)); + } + + default PageResult selectPage(ImSensitiveWordPageReqVO reqVO) { + return selectPage(reqVO, new LambdaQueryWrapperX() + .likeIfPresent(ImSensitiveWordDO::getWord, reqVO.getWord()) + .eqIfPresent(ImSensitiveWordDO::getStatus, reqVO.getStatus()) + .betweenIfPresent(ImSensitiveWordDO::getCreateTime, reqVO.getCreateTime()) + .orderByDesc(ImSensitiveWordDO::getId)); + } + } diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ErrorCodeConstants.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ErrorCodeConstants.java index db6d4ef..56c4289 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ErrorCodeConstants.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ErrorCodeConstants.java @@ -34,4 +34,8 @@ public interface ErrorCodeConstants { ErrorCode FRIEND_NOT_FRIEND = new ErrorCode(1_040_600_001, "对方不是您的好友"); ErrorCode FRIEND_ADD_SELF = new ErrorCode(1_040_600_002, "不允许添加自己为好友"); + // ========== 敏感词 (1-040-700-000) ========== + ErrorCode SENSITIVE_WORD_NOT_EXISTS = new ErrorCode(1_040_700_000, "敏感词不存在"); + ErrorCode SENSITIVE_WORD_DUPLICATED = new ErrorCode(1_040_700_001, "敏感词 '{}' 已存在"); + } diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/sensitiveword/ImSensitiveWordConfiguration.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/sensitiveword/ImSensitiveWordConfiguration.java new file mode 100644 index 0000000..44c51c7 --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/sensitiveword/ImSensitiveWordConfiguration.java @@ -0,0 +1,32 @@ +package cn.iocoder.yudao.module.im.framework.sensitiveword; + +import com.github.houbb.sensitive.word.bs.SensitiveWordBs; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * IM 敏感词检测配置 + * + * @author 芋道源码 + */ +@Configuration +public class ImSensitiveWordConfiguration { + + // TODO @AI:可能需要按需创建;因为多租户。 + /** + * 装配 {@link SensitiveWordBs}: + * 词库来源 = {@link ImSensitiveWordDeny}(DB 里启用的敏感词),并开启常用规范化能力。 + */ + @Bean + public SensitiveWordBs sensitiveWordBs(ImSensitiveWordDeny imSensitiveWordDeny) { + return SensitiveWordBs.newInstance() + .wordDeny(imSensitiveWordDeny) + .ignoreCase(true) + .ignoreWidth(true) // 忽略全/半角 + .ignoreNumStyle(true) // 忽略数字风格(中文/阿拉伯) + .ignoreChineseStyle(true) // 忽略繁简体 + .enableWordCheck(true) + .init(); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/sensitiveword/ImSensitiveWordDeny.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/sensitiveword/ImSensitiveWordDeny.java new file mode 100644 index 0000000..34e8b7c --- /dev/null +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/sensitiveword/ImSensitiveWordDeny.java @@ -0,0 +1,36 @@ +package cn.iocoder.yudao.module.im.framework.sensitiveword; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.module.im.dal.dataobject.sensitiveword.ImSensitiveWordDO; +import cn.iocoder.yudao.module.im.dal.mysql.sensitiveword.ImSensitiveWordMapper; +import com.github.houbb.sensitive.word.api.IWordDeny; +import jakarta.annotation.Resource; +import org.springframework.stereotype.Component; + +import java.util.List; + +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; + +// TODO @AI:是不是可以,直接塞入一个数组。而不是这里查询??? +/** + * 自定义敏感词数据来源:从 im_sensitive_word 表加载启用状态的敏感词 + *

+ * 由 {@link com.github.houbb.sensitive.word.bs.SensitiveWordBs} 在 init() 时回调, + * 因此 CRUD 完成后调用 sensitiveWordBs.init() 即可实现热更新。 + * + * @author 芋道源码 + */ +@Component +public class ImSensitiveWordDeny implements IWordDeny { + + @Resource + private ImSensitiveWordMapper sensitiveWordMapper; + + @Override + public List deny() { + return convertList( + sensitiveWordMapper.selectListByStatus(CommonStatusEnum.ENABLE.getStatus()), + ImSensitiveWordDO::getWord); + } + +} diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendService.java index 9dce3bd..a14fe0d 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendService.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendService.java @@ -1,6 +1,9 @@ package cn.iocoder.yudao.module.im.service.friend; +import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.im.controller.admin.friend.vo.ImFriendUpdateReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo.ImFriendManagerPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo.ImFriendManagerRespVO; import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; import java.util.Collection; @@ -84,5 +87,11 @@ public interface ImFriendService { */ void updateFriend(Long userId, ImFriendUpdateReqVO reqVO); + // ==================== 管理后台 ==================== + + /** + * 【管理后台】分页查询好友关系(已填充用户 / 好友昵称) + */ + PageResult getFriendPage(ImFriendManagerPageReqVO reqVO); } diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImpl.java index f4fe4f4..14e2091 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImpl.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImpl.java @@ -1,14 +1,21 @@ package cn.iocoder.yudao.module.im.service.friend; +import cn.hutool.core.collection.CollUtil; import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +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.friend.vo.ImFriendUpdateReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo.ImFriendManagerPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo.ImFriendManagerRespVO; import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; import cn.iocoder.yudao.module.im.dal.mysql.friend.ImFriendMapper; import cn.iocoder.yudao.module.im.service.message.ImPrivateMessageService; import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.cache.annotation.CacheEvict; @@ -20,11 +27,10 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.time.LocalDateTime; -import java.util.Collection; -import java.util.List; -import java.util.Objects; +import java.util.*; 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.FRIEND; import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FRIEND_ADD_SELF; import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FRIEND_NOT_FRIEND; @@ -181,4 +187,28 @@ public class ImFriendServiceImpl implements ImFriendService { return SpringUtil.getBean(getClass()); } + // ==================== 管理后台 ==================== + + // TODO @AI:拿到 controller 拼接数据,简化 service; + @Override + public PageResult getFriendPage(ImFriendManagerPageReqVO reqVO) { + // 1. 分页查询 + PageResult pageResult = imFriendMapper.selectPage(reqVO); + if (CollUtil.isEmpty(pageResult.getList())) { + return PageResult.empty(pageResult.getTotal()); + } + + // 2. 一次性批量查询用户 + 好友的昵称 + Set userIds = new HashSet<>(); + userIds.addAll(convertSet(pageResult.getList(), ImFriendDO::getUserId)); + userIds.addAll(convertSet(pageResult.getList(), ImFriendDO::getFriendUserId)); + Map userMap = adminUserApi.getUserMap(userIds); + + // 3. 转换为 VO,填充昵称 + return BeanUtils.toBean(pageResult, ImFriendManagerRespVO.class, vo -> { + MapUtils.findAndThen(userMap, vo.getUserId(), user -> vo.setUserNickname(user.getNickname())); + MapUtils.findAndThen(userMap, vo.getFriendUserId(), user -> vo.setFriendNickname(user.getNickname())); + }); + } + } diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberService.java index fbd4bb7..21d46ed 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberService.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberService.java @@ -1,12 +1,14 @@ package cn.iocoder.yudao.module.im.service.group; import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberUpdateReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.member.ImGroupMemberManagerRespVO; import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; import jakarta.validation.Valid; import java.time.LocalDateTime; import java.util.Collection; import java.util.List; +import java.util.Map; /** * 群成员 Service 接口 @@ -24,6 +26,15 @@ public interface ImGroupMemberService { ImGroupMemberDO getGroupMember(Long id); /** + * 获得群成员 + * + * @param groupId 群编号 + * @param userId 用户编号 + * @return 群成员 + */ + ImGroupMemberDO getGroupMember(Long groupId, Long userId); + + /** * 根据群组 id 查询群成员(包含所有状态) * * @param groupId 群组id @@ -137,4 +148,22 @@ public interface ImGroupMemberService { */ void removeGroupMembersByGroupId(Long groupId); + /** + * 批量按 group 统计活跃成员数:(group_id → count) + * + * @param groupIds 群编号集合 + * @return 群成员数 Map + */ + Map getActiveMemberCountMap(Collection groupIds); + + // ==================== 管理后台 ==================== + + /** + * 【管理后台】获取群成员列表 + * + * @param groupId 群编号 + * @return 群成员列表 + */ + List getGroupMemberList(Long groupId); + } diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberServiceImpl.java index 0b92f0d..b172ebb 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberServiceImpl.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberServiceImpl.java @@ -2,12 +2,16 @@ package cn.iocoder.yudao.module.im.service.group; import cn.hutool.core.collection.CollUtil; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +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.ImGroupMemberUpdateReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.member.ImGroupMemberManagerRespVO; import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; 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 cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.cache.annotation.CacheEvict; @@ -17,14 +21,10 @@ import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; +import java.util.*; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; import static cn.iocoder.yudao.module.im.dal.redis.RedisKeyConstants.GROUP_MEMBER_IDS; import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.GROUP_MEMBER_NOT_IN_GROUP; @@ -44,12 +44,20 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService { @Resource private ImWebSocketService webSocketService; + @Resource + private AdminUserApi adminUserApi; + @Override public ImGroupMemberDO getGroupMember(Long id) { return groupMemberMapper.selectById(id); } @Override + public ImGroupMemberDO getGroupMember(Long groupId, Long userId) { + return groupMemberMapper.selectByGroupIdAndUserId(groupId, userId); + } + + @Override public List getGroupMemberListByGroupId(Long groupId) { return groupMemberMapper.selectListByGroupId(groupId); } @@ -209,4 +217,30 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService { .setQuitTime(LocalDateTime.now())); } + @Override + public Map getActiveMemberCountMap(Collection groupIds) { + return groupMemberMapper.selectCountMapByGroupIdsAndStatus(groupIds, CommonStatusEnum.ENABLE.getStatus()); + } + + // ==================== 管理后台 ==================== + + // TODO @AI:这个拼接的逻辑,拿到 controller,这里只返回 list do; + @Override + public List getGroupMemberList(Long groupId) { + // 1. 查询有效群成员 + List members = getActiveGroupMemberListByGroupId(groupId); + if (CollUtil.isEmpty(members)) { + return Collections.emptyList(); + } + + // 2. 批量查询用户信息 + Set userIds = convertSet(members, ImGroupMemberDO::getUserId); + Map userMap = adminUserApi.getUserMap(userIds); + + // 3. 转换为 VO,填充昵称、头像 + return BeanUtils.toBean(members, ImGroupMemberManagerRespVO.class, vo -> + MapUtils.findAndThen(userMap, vo.getUserId(), user -> + vo.setNickname(user.getNickname()).setAvatar(user.getAvatar()))); + } + } diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupService.java index b38991e..b40768b 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupService.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupService.java @@ -1,13 +1,19 @@ 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.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.ImGroupMemberRemoveReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerBanReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerRespVO; import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; import jakarta.validation.Valid; +import java.util.Collection; import java.util.List; +import java.util.Map; /** * 群 Service 接口 @@ -59,6 +65,14 @@ public interface ImGroupService { ImGroupDO getGroup(Long id); /** + * 批量获得群 Map + * + * @param ids 群编号集合 + * @return 群 Map(key = 群编号) + */ + Map getGroupMap(Collection ids); + + /** * 校验群存在且未封禁、未解散 * * @param groupId 群编号 @@ -122,4 +136,30 @@ public interface ImGroupService { */ void removeGroupMember(Long userId, @Valid ImGroupMemberRemoveReqVO removeReqVO); + // ==================== 管理后台 ==================== + + // TODO DONE @AI:是不是 controller 拆分哈;(getGroupPage 留在 ImGroupService;getGroupMemberList 已拆到 ImGroupMemberService + ImGroupMemberManagerController) + // TODO @AI:不对;应该 VO 放在 controller 拼接 + /** + * 【管理后台】分页查询群列表 + * + * @param pageReqVO 分页查询条件 + * @return 群分页列表 + */ + PageResult getGroupPage(ImGroupManagerPageReqVO pageReqVO); + + /** + * 【管理后台】封禁群 + * + * @param banReqVO 封禁信息(含群编号、封禁原因) + */ + void banGroup(@Valid ImGroupManagerBanReqVO banReqVO); + + /** + * 【管理后台】解封群 + * + * @param id 群编号 + */ + void unbanGroup(Long id); + } diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImpl.java index 3801f4b..57c9496 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImpl.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImpl.java @@ -7,11 +7,16 @@ import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.extra.spring.SpringUtil; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +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.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.ImGroupMemberRemoveReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerBanReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerRespVO; import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; @@ -35,6 +40,7 @@ import java.util.*; import java.util.stream.Collectors; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap; 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.*; @@ -256,6 +262,14 @@ public class ImGroupServiceImpl implements ImGroupService { } @Override + public Map getGroupMap(Collection ids) { + if (CollUtil.isEmpty(ids)) { + return Collections.emptyMap(); + } + return convertMap(groupMapper.selectByIds(ids), ImGroupDO::getId); + } + + @Override public List getMyGroupList(Long userId) { // 1.1 查用户所在的、仍有效的群成员记录(仅 ENABLE 状态) List members = groupMemberService.getActiveGroupMemberListByUserId(userId); @@ -271,6 +285,53 @@ public class ImGroupServiceImpl implements ImGroupService { return groupMapper.selectByIds(groupIds); } + // ==================== 管理后台 ==================== + + @Override + public PageResult getGroupPage(ImGroupManagerPageReqVO pageReqVO) { + // 1. 分页查询群 + PageResult pageResult = groupMapper.selectPage(pageReqVO); + if (CollUtil.isEmpty(pageResult.getList())) { + return PageResult.empty(pageResult.getTotal()); + } + + // 2.1 批量查询群主昵称 + Set ownerUserIds = convertSet(pageResult.getList(), ImGroupDO::getOwnerUserId); + Map userMap = adminUserApi.getUserMap(ownerUserIds); + // 2.2 一次 GROUP BY 查出本页所有群的活跃成员数,避免按行 N+1 + Set groupIds = convertSet(pageResult.getList(), ImGroupDO::getId); + Map memberCountMap = groupMemberService.getActiveMemberCountMap(groupIds); + // 2.3 转换为 VO,填充群主昵称、群成员数量 + return BeanUtils.toBean(pageResult, ImGroupManagerRespVO.class, vo -> { + MapUtils.findAndThen(userMap, vo.getOwnerUserId(), user -> vo.setOwnerNickname(user.getNickname())); + vo.setMemberCount(memberCountMap.getOrDefault(vo.getId(), 0L).intValue()); + }); + } + + @Override + @CacheEvict(cacheNames = GROUP, key = "#banReqVO.id") + public void banGroup(ImGroupManagerBanReqVO banReqVO) { + // 1. 校验群存在 + if (getSelf().getGroup(banReqVO.getId()) == null) { + throw exception(GROUP_NOT_EXISTS); + } + // 2. 更新封禁状态 + groupMapper.updateById(new ImGroupDO().setId(banReqVO.getId()) + .setBanned(true).setBannedReason(banReqVO.getReason()).setBannedTime(LocalDateTime.now())); + } + + @Override + @CacheEvict(cacheNames = GROUP, key = "#id") + public void unbanGroup(Long id) { + // 1. 校验群存在 + if (getSelf().getGroup(id) == null) { + throw exception(GROUP_NOT_EXISTS); + } + // 2. 解封(保留 bannedReason / bannedTime 作为历史记录) + // TODO DONE @AI:不要使用 mybatis plus 的方法,直接操作。另外,感觉封禁时间、封禁原因,没必要置空,留着问题不大。 + groupMapper.updateById(new ImGroupDO().setId(id).setBanned(false)); + } + private ImGroupServiceImpl getSelf() { return SpringUtil.getBean(getClass()); } diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageService.java index bfd279b..ae89515 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageService.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageService.java @@ -1,5 +1,8 @@ package cn.iocoder.yudao.module.im.service.message; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group.ImGroupMessageManagerPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group.ImGroupMessageManagerRespVO; import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageListReqVO; import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageSendReqVO; import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; @@ -112,5 +115,17 @@ public interface ImGroupMessageService { */ void sendTipGroupMessage(Long senderId, Long groupId, Set memberUserIds, String content); + // ==================== 管理后台 ==================== + + /** + * 【管理后台】分页查询群聊消息(已填充群名称、发送人昵称) + */ + PageResult getGroupMessagePage(ImGroupMessageManagerPageReqVO reqVO); + + /** + * 【管理后台】获取群聊消息详情 + */ + ImGroupMessageDO getGroupMessage(Long id); + } diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java index 79f0f2d..145f018 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java @@ -6,13 +6,17 @@ import cn.hutool.core.util.IdUtil; 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.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group.ImGroupMessageManagerPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group.ImGroupMessageManagerRespVO; import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageListReqVO; import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageSendReqVO; +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.dataobject.message.ImGroupMessageDO; -import cn.iocoder.yudao.module.im.service.websocket.dto.message.RecallMessage; import cn.iocoder.yudao.module.im.dal.mysql.message.ImGroupMessageMapper; import cn.iocoder.yudao.module.im.dal.redis.message.GroupMessageReadRedisDAO; import cn.iocoder.yudao.module.im.enums.message.ImGroupMessageReceiptStatusEnum; @@ -23,6 +27,9 @@ import cn.iocoder.yudao.module.im.service.group.ImGroupService; import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService; import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; import cn.iocoder.yudao.module.im.service.websocket.dto.ImGroupMessageDTO; +import cn.iocoder.yudao.module.im.service.websocket.dto.message.RecallMessage; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Async; @@ -70,6 +77,9 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { @Resource private ImWebSocketService imWebSocketService; + @Resource + private AdminUserApi adminUserApi; + @Override public ImGroupMessageDO sendGroupMessage(Long senderId, ImGroupMessageSendReqVO reqVO) { // 1.1 幂等校验:根据 senderId + clientMessageId 查重 @@ -542,6 +552,35 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { ImGroupMessageDTO.ofSend(tipMessage)); } + // ==================== 管理后台 ==================== + + // TODO @AI:这些挪到 controller 拼接 VO + @Override + public PageResult getGroupMessagePage(ImGroupMessageManagerPageReqVO reqVO) { + // 1. 分页查询 + PageResult pageResult = groupMessageMapper.selectPage(reqVO); + if (CollUtil.isEmpty(pageResult.getList())) { + return PageResult.empty(pageResult.getTotal()); + } + + // 2. 批量查询群名称、发送人昵称 + Set groupIds = convertSet(pageResult.getList(), ImGroupMessageDO::getGroupId); + Set senderIds = convertSet(pageResult.getList(), ImGroupMessageDO::getSenderId); + Map groupMap = groupService.getGroupMap(groupIds); + Map userMap = adminUserApi.getUserMap(senderIds); + + // 3. 转换为 VO,填充群名 / 发送人昵称 + return BeanUtils.toBean(pageResult, ImGroupMessageManagerRespVO.class, vo -> { + MapUtils.findAndThen(groupMap, vo.getGroupId(), group -> vo.setGroupName(group.getName())); + MapUtils.findAndThen(userMap, vo.getSenderId(), user -> vo.setSenderNickname(user.getNickname())); + }); + } + + @Override + public ImGroupMessageDO getGroupMessage(Long id) { + return groupMessageMapper.selectById(id); + } + private ImGroupMessageServiceImpl getSelf() { return SpringUtil.getBean(getClass()); } diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageService.java index fa1c7bd..407eda0 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageService.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageService.java @@ -1,5 +1,8 @@ package cn.iocoder.yudao.module.im.service.message; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.privates.ImPrivateMessageManagerPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.privates.ImPrivateMessageManagerRespVO; import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageListReqVO; import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageSendReqVO; import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; @@ -85,4 +88,16 @@ public interface ImPrivateMessageService { */ void sendTipPrivateMessage(Long senderId, Long receiverId, String content); + // ==================== 管理后台 ==================== + + /** + * 【管理后台】分页查询私聊消息(已填充发送人 / 接收人昵称) + */ + PageResult getPrivateMessagePage(ImPrivateMessageManagerPageReqVO reqVO); + + /** + * 【管理后台】获取私聊消息详情 + */ + ImPrivateMessageDO getPrivateMessage(Long id); + } diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java index 9412de5..49ac482 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java @@ -1,10 +1,15 @@ package cn.iocoder.yudao.module.im.service.message; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.ObjUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.privates.ImPrivateMessageManagerPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.privates.ImPrivateMessageManagerRespVO; import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageListReqVO; import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageSendReqVO; import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; @@ -16,6 +21,8 @@ import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService; import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO; import cn.iocoder.yudao.module.im.service.websocket.dto.message.RecallMessage; +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -23,9 +30,13 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.time.LocalDateTime; +import java.util.HashSet; import java.util.List; +import java.util.Map; +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.enums.ErrorCodeConstants.*; import static cn.iocoder.yudao.module.im.enums.ImCommonConstants.*; @@ -50,6 +61,9 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService { @Resource private ImWebSocketService imWebSocketService; + @Resource + private AdminUserApi adminUserApi; + @Override public ImPrivateMessageDO sendPrivateMessage(Long senderId, ImPrivateMessageSendReqVO reqVO) { // 1.1 幂等校验:根据 senderId + clientMessageId 查重 @@ -167,6 +181,35 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService { return privateMessageMapper.selectHistoryList(userId, reqVO.getReceiverId(), reqVO.getMaxId(), reqVO.getLimit()); } + // ==================== 管理后台 ==================== + + // TODO @AI:这些挪到 controller 拼接 VO + @Override + public PageResult getPrivateMessagePage(ImPrivateMessageManagerPageReqVO reqVO) { + // 1. 分页查询 + PageResult pageResult = privateMessageMapper.selectPage(reqVO); + if (CollUtil.isEmpty(pageResult.getList())) { + return PageResult.empty(pageResult.getTotal()); + } + + // 2. 一次性批量查询发送人 + 接收人昵称 + Set userIds = new HashSet<>(); + userIds.addAll(convertSet(pageResult.getList(), ImPrivateMessageDO::getSenderId)); + userIds.addAll(convertSet(pageResult.getList(), ImPrivateMessageDO::getReceiverId)); + Map userMap = adminUserApi.getUserMap(userIds); + + // 3. 转换为 VO,填充昵称 + return BeanUtils.toBean(pageResult, ImPrivateMessageManagerRespVO.class, vo -> { + MapUtils.findAndThen(userMap, vo.getSenderId(), user -> vo.setSenderNickname(user.getNickname())); + MapUtils.findAndThen(userMap, vo.getReceiverId(), user -> vo.setReceiverNickname(user.getNickname())); + }); + } + + @Override + public ImPrivateMessageDO getPrivateMessage(Long id) { + return privateMessageMapper.selectById(id); + } + @Override public void sendTipPrivateMessage(Long senderId, Long receiverId, String content) { // 1. 插入 TIP_TEXT 消息 diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/sensitiveword/ImSensitiveWordService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/sensitiveword/ImSensitiveWordService.java index 25bd621..1a5c3aa 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/sensitiveword/ImSensitiveWordService.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/sensitiveword/ImSensitiveWordService.java @@ -1,5 +1,13 @@ package cn.iocoder.yudao.module.im.service.sensitiveword; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo.ImSensitiveWordPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo.ImSensitiveWordSaveReqVO; +import cn.iocoder.yudao.module.im.dal.dataobject.sensitiveword.ImSensitiveWordDO; +import jakarta.validation.Valid; + +import java.util.List; + /** * IM 敏感词 Service 接口 * @@ -14,4 +22,36 @@ public interface ImSensitiveWordService { */ void validateText(String text); + // ==================== 管理后台 ==================== + + /** + * 【管理后台】分页查询敏感词 + */ + PageResult getSensitiveWordPage(ImSensitiveWordPageReqVO reqVO); + + /** + * 【管理后台】获取敏感词详情 + */ + ImSensitiveWordDO getSensitiveWord(Long id); + + /** + * 【管理后台】新增敏感词,返回新增 id + */ + Long createSensitiveWord(@Valid ImSensitiveWordSaveReqVO reqVO); + + /** + * 【管理后台】修改敏感词 + */ + void updateSensitiveWord(@Valid ImSensitiveWordSaveReqVO reqVO); + + /** + * 【管理后台】删除敏感词 + */ + void deleteSensitiveWord(Long id); + + /** + * 【管理后台】批量删除敏感词 + */ + void deleteSensitiveWordList(List ids); + } diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/sensitiveword/ImSensitiveWordServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/sensitiveword/ImSensitiveWordServiceImpl.java index cacb7a6..1620b9e 100644 --- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/sensitiveword/ImSensitiveWordServiceImpl.java +++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/sensitiveword/ImSensitiveWordServiceImpl.java @@ -1,25 +1,31 @@ package cn.iocoder.yudao.module.im.service.sensitiveword; -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.util.ObjUtil; +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.pojo.PageResult; +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; +import cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo.ImSensitiveWordPageReqVO; +import cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo.ImSensitiveWordSaveReqVO; import cn.iocoder.yudao.module.im.dal.dataobject.sensitiveword.ImSensitiveWordDO; import cn.iocoder.yudao.module.im.dal.mysql.sensitiveword.ImSensitiveWordMapper; -import jakarta.annotation.PostConstruct; +import com.github.houbb.sensitive.word.bs.SensitiveWordBs; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; -import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; -import java.util.Collections; import java.util.List; -import java.util.stream.Collectors; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.MESSAGE_SENSITIVE_WORD_BLOCKED; +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.SENSITIVE_WORD_DUPLICATED; +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.SENSITIVE_WORD_NOT_EXISTS; -// DONE @AI:已实现内存化,启动时加载 + 定时刷新 /** * IM 敏感词 Service 实现类 + *

+ * 词库匹配交给 houbb sensitive-word 库(trie 树 + 全/半角 / 大小写 / 繁简体 / 数字风格规范化) * * @author 芋道源码 */ @@ -29,48 +35,122 @@ import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.MESSAGE_SENSIT public class ImSensitiveWordServiceImpl implements ImSensitiveWordService { @Resource - private ImSensitiveWordMapper imSensitiveWordMapper; + private ImSensitiveWordMapper sensitiveWordMapper; + // TODO @AI:当前节点,立即刷新。其他节点,通过 job 扫描最后更新时间(只要有不同于目前最后一条记录的最大更新时间,就重新构建对应的树)。 /** - * 内存缓存的敏感词列表 + * houbb 敏感词检测器,词库由 ImSensitiveWordDeny 在 init 时回调加载 */ - private volatile List sensitiveWords = Collections.emptyList(); + @Resource + private SensitiveWordBs sensitiveWordBs; + // TODO @AI:你看看现有仓库里,是否有类似这种的。按照最后更新判断,是否需要刷新本地缓存。 /** - * 初始化:启动时加载敏感词到内存 + * 触发 houbb 词库重新加载(重建 trie) + *

+ * 加 synchronized 是因为 SensitiveWordBs.init() 内部对 wordData 做就地写入, + * 若与并发的 contains() 重叠会读到半构建的 trie。互斥后短暂阻塞 contains 即可保证可见性。 + *

+ * init() 本身幂等,单实例可以直接调;多实例部署时需配合 Redis pub/sub 广播让各实例 init。 */ - @PostConstruct - public void init() { - loadSensitiveWords(); + private synchronized void reloadSensitiveWords() { + sensitiveWordBs.init(); + log.info("[reloadSensitiveWords][敏感词库已 reload]"); } - /** - * 定时刷新:每 5 分钟重新加载一次 - */ - @Scheduled(fixedDelay = 5 * 60 * 1000, initialDelay = 5 * 60 * 1000) - public void refresh() { - loadSensitiveWords(); + // TODO @AI:需要按照租户来。 + @Override + public void validateText(String text) { + if (StrUtil.isBlank(text)) { + return; + } + if (sensitiveWordBs.contains(text)) { + throw exception(MESSAGE_SENSITIVE_WORD_BLOCKED); + } } - private void loadSensitiveWords() { - List enabledWords = imSensitiveWordMapper.selectListByStatus( - CommonStatusEnum.ENABLE.getStatus()); - sensitiveWords = enabledWords.stream() - .map(ImSensitiveWordDO::getWord) - .collect(Collectors.toList()); - log.info("[loadSensitiveWords][加载 IM 敏感词 {} 个]", sensitiveWords.size()); + // ==================== 管理后台 ==================== + + @Override + public PageResult getSensitiveWordPage(ImSensitiveWordPageReqVO reqVO) { + return sensitiveWordMapper.selectPage(reqVO); } @Override - public void validateText(String text) { - if (text == null || text.isEmpty()) { + public ImSensitiveWordDO getSensitiveWord(Long id) { + return sensitiveWordMapper.selectById(id); + } + + @Override + public Long createSensitiveWord(ImSensitiveWordSaveReqVO reqVO) { + // 1. 校验唯一 + validateWordUnique(null, reqVO.getWord()); + + // 2.1 入库 + ImSensitiveWordDO word = BeanUtils.toBean(reqVO, ImSensitiveWordDO.class); + sensitiveWordMapper.insert(word); + // TODO @AI:houbb 这个注释有点奇怪! + // 2.2 触发 houbb 词库 reload + reloadSensitiveWords(); + return word.getId(); + } + + @Override + public void updateSensitiveWord(ImSensitiveWordSaveReqVO reqVO) { + // 1.1 校验存在 + validateSensitiveWordExists(reqVO.getId()); + // 1.2 校验唯一(排除自身) + validateWordUnique(reqVO.getId(), reqVO.getWord()); + + // 2.1 更新 + ImSensitiveWordDO updateObj = BeanUtils.toBean(reqVO, ImSensitiveWordDO.class); + sensitiveWordMapper.updateById(updateObj); + // 2.2 TODO @AI:houbb 这个注释有点奇怪! + // 2.2 触发 houbb 词库 reload + reloadSensitiveWords(); + } + + @Override + public void deleteSensitiveWord(Long id) { + // 1. 校验存在 + validateSensitiveWordExists(id); + + // 2.1 删除 + sensitiveWordMapper.deleteById(id); + // 2.2 触发 houbb 词库 reload + reloadSensitiveWords(); + } + + + @Override + public void deleteSensitiveWordList(List ids) { + if (CollUtil.isEmpty(ids)) { + return; + } + + // 1. 删除 + sensitiveWordMapper.deleteByIds(ids); + // 触发 houbb 词库 reload + reloadSensitiveWords(); + } + + private void validateSensitiveWordExists(Long id) { + if (sensitiveWordMapper.selectById(id) == null) { + throw exception(SENSITIVE_WORD_NOT_EXISTS); + } + } + + /** + * 校验敏感词唯一(修改时排除自身) + */ + private void validateWordUnique(Long id, String word) { + ImSensitiveWordDO exist = sensitiveWordMapper.selectByWord(word); + if (exist == null) { return; } - // 内存匹配 - for (String word : sensitiveWords) { - if (text.contains(word)) { - throw exception(MESSAGE_SENSITIVE_WORD_BLOCKED); - } + // TODO @AI:是不是 id 不用判断 + if (id == null || ObjUtil.notEqual(exist.getId(), id)) { + throw exception(SENSITIVE_WORD_DUPLICATED, word); } } -- libgit2 0.21.4