Commit 37f0a8a01d727f38ca7576a33ee0a3bf5b13acab

Authored by YunaiV
1 parent d0fbab8f

✨ feat(im): 增加 friend、group、message、sensitiveword 的管理接口

Showing 37 changed files with 1292 additions and 45 deletions
yudao-module-im/pom.xml
... ... @@ -62,6 +62,14 @@
62 62 <artifactId>yudao-spring-boot-starter-excel</artifactId>
63 63 </dependency>
64 64  
  65 + <!-- 敏感词检测:houbb sensitive-word(trie 树高效匹配,支持忽略大小写 / 全半角 / 数字风格) -->
  66 + <!-- TODO @AI:放到基础 pom 里管理。 -->
  67 + <dependency>
  68 + <groupId>com.github.houbb</groupId>
  69 + <artifactId>sensitive-word</artifactId>
  70 + <version>0.29.5</version>
  71 + </dependency>
  72 +
65 73 <!-- Test 测试相关 -->
66 74 <dependency>
67 75 <groupId>cn.iocoder.boot</groupId>
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/friend/ImFriendManagerController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.friend;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo.ImFriendManagerPageReqVO;
  6 +import cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo.ImFriendManagerRespVO;
  7 +import cn.iocoder.yudao.module.im.service.friend.ImFriendService;
  8 +import io.swagger.v3.oas.annotations.Operation;
  9 +import io.swagger.v3.oas.annotations.tags.Tag;
  10 +import jakarta.annotation.Resource;
  11 +import jakarta.validation.Valid;
  12 +import org.springframework.security.access.prepost.PreAuthorize;
  13 +import org.springframework.validation.annotation.Validated;
  14 +import org.springframework.web.bind.annotation.GetMapping;
  15 +import org.springframework.web.bind.annotation.RequestMapping;
  16 +import org.springframework.web.bind.annotation.RestController;
  17 +
  18 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  19 +
  20 +@Tag(name = "管理后台 - IM 好友管理")
  21 +@RestController
  22 +@RequestMapping("/im/manager/friend")
  23 +@Validated
  24 +public class ImFriendManagerController {
  25 +
  26 + @Resource
  27 + private ImFriendService friendService;
  28 +
  29 + @GetMapping("/page")
  30 + @Operation(summary = "获得好友关系分页")
  31 + @PreAuthorize("@ss.hasPermission('im:manager:friend:query')")
  32 + public CommonResult<PageResult<ImFriendManagerRespVO>> getFriendPage(
  33 + @Valid ImFriendManagerPageReqVO pageReqVO) {
  34 + return success(friendService.getFriendPage(pageReqVO));
  35 + }
  36 +
  37 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/friend/vo/ImFriendManagerPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  7 +import org.springframework.format.annotation.DateTimeFormat;
  8 +
  9 +import java.time.LocalDateTime;
  10 +
  11 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  12 +
  13 +@Schema(description = "管理后台 - IM 好友关系分页 Request VO")
  14 +@Data
  15 +@EqualsAndHashCode(callSuper = true)
  16 +public class ImFriendManagerPageReqVO extends PageParam {
  17 +
  18 + @Schema(description = "用户编号", example = "1024")
  19 + private Long userId;
  20 +
  21 + @Schema(description = "好友用户编号", example = "2048")
  22 + private Long friendUserId;
  23 +
  24 + @Schema(description = "好友状态", example = "0")
  25 + private Integer status; // 参见 CommonStatusEnum 枚举类
  26 +
  27 + @Schema(description = "是否免打扰", example = "false")
  28 + private Boolean muted;
  29 +
  30 + @Schema(description = "添加好友时间", example = "[2026-04-01 00:00:00, 2026-04-30 23:59:59]")
  31 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  32 + private LocalDateTime[] addTime;
  33 +
  34 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/friend/vo/ImFriendManagerRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +
  8 +@Schema(description = "管理后台 - IM 好友关系 Response VO")
  9 +@Data
  10 +public class ImFriendManagerRespVO {
  11 +
  12 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  13 + private Long id;
  14 +
  15 + @Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  16 + private Long userId;
  17 +
  18 + @Schema(description = "用户昵称", example = "张三")
  19 + private String userNickname;
  20 +
  21 + @Schema(description = "好友用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
  22 + private Long friendUserId;
  23 +
  24 + @Schema(description = "好友昵称", example = "李四")
  25 + private String friendNickname;
  26 +
  27 + @Schema(description = "好友展示备注")
  28 + private String displayName;
  29 +
  30 + @Schema(description = "是否免打扰", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
  31 + private Boolean muted;
  32 +
  33 + @Schema(description = "好友状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  34 + private Integer status; // 参见 CommonStatusEnum 枚举类
  35 +
  36 + @Schema(description = "添加好友时间")
  37 + private LocalDateTime addTime;
  38 +
  39 + @Schema(description = "删除好友时间")
  40 + private LocalDateTime deleteTime;
  41 +
  42 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  43 + private LocalDateTime createTime;
  44 +
  45 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/ImGroupManagerController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.group;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  6 +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerBanReqVO;
  7 +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerPageReqVO;
  8 +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerRespVO;
  9 +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
  10 +import cn.iocoder.yudao.module.im.service.group.ImGroupService;
  11 +import io.swagger.v3.oas.annotations.Operation;
  12 +import io.swagger.v3.oas.annotations.Parameter;
  13 +import io.swagger.v3.oas.annotations.tags.Tag;
  14 +import jakarta.annotation.Resource;
  15 +import jakarta.validation.Valid;
  16 +import org.springframework.security.access.prepost.PreAuthorize;
  17 +import org.springframework.validation.annotation.Validated;
  18 +import org.springframework.web.bind.annotation.*;
  19 +
  20 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  21 +
  22 +@Tag(name = "管理后台 - IM 群聊管理")
  23 +@RestController
  24 +@RequestMapping("/im/manager/group")
  25 +@Validated
  26 +public class ImGroupManagerController {
  27 +
  28 + @Resource
  29 + private ImGroupService groupService;
  30 +
  31 + @GetMapping("/page")
  32 + @Operation(summary = "获得群分页")
  33 + @PreAuthorize("@ss.hasPermission('im:manager:group:query')")
  34 + public CommonResult<PageResult<ImGroupManagerRespVO>> getGroupPage(@Valid ImGroupManagerPageReqVO pageReqVO) {
  35 + return success(groupService.getGroupPage(pageReqVO));
  36 + }
  37 +
  38 + @GetMapping("/get")
  39 + @Operation(summary = "获得群详情")
  40 + @Parameter(name = "id", description = "群编号", required = true, example = "1024")
  41 + @PreAuthorize("@ss.hasPermission('im:manager:group:query')")
  42 + public CommonResult<ImGroupManagerRespVO> getGroup(@RequestParam("id") Long id) {
  43 + ImGroupDO group = groupService.getGroup(id);
  44 + return success(BeanUtils.toBean(group, ImGroupManagerRespVO.class));
  45 + }
  46 +
  47 + @PutMapping("/ban")
  48 + @Operation(summary = "封禁群")
  49 + @PreAuthorize("@ss.hasPermission('im:manager:group:ban')")
  50 + public CommonResult<Boolean> banGroup(@Valid @RequestBody ImGroupManagerBanReqVO reqVO) {
  51 + groupService.banGroup(reqVO);
  52 + return success(true);
  53 + }
  54 +
  55 + @PutMapping("/unban")
  56 + @Operation(summary = "解封群")
  57 + @Parameter(name = "id", description = "群编号", required = true, example = "1024")
  58 + @PreAuthorize("@ss.hasPermission('im:manager:group:ban')")
  59 + public CommonResult<Boolean> unbanGroup(@RequestParam("id") Long id) {
  60 + groupService.unbanGroup(id);
  61 + return success(true);
  62 + }
  63 +
  64 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/ImGroupMemberManagerController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.group;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.member.ImGroupMemberManagerRespVO;
  5 +import cn.iocoder.yudao.module.im.service.group.ImGroupMemberService;
  6 +import io.swagger.v3.oas.annotations.Operation;
  7 +import io.swagger.v3.oas.annotations.Parameter;
  8 +import io.swagger.v3.oas.annotations.tags.Tag;
  9 +import jakarta.annotation.Resource;
  10 +import org.springframework.security.access.prepost.PreAuthorize;
  11 +import org.springframework.validation.annotation.Validated;
  12 +import org.springframework.web.bind.annotation.GetMapping;
  13 +import org.springframework.web.bind.annotation.RequestMapping;
  14 +import org.springframework.web.bind.annotation.RequestParam;
  15 +import org.springframework.web.bind.annotation.RestController;
  16 +
  17 +import java.util.List;
  18 +
  19 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  20 +
  21 +@Tag(name = "管理后台 - IM 群成员管理")
  22 +@RestController
  23 +@RequestMapping("/im/manager/group/member")
  24 +@Validated
  25 +public class ImGroupMemberManagerController {
  26 +
  27 + @Resource
  28 + private ImGroupMemberService groupMemberService;
  29 +
  30 + @GetMapping("/list")
  31 + @Operation(summary = "获得群成员列表")
  32 + @Parameter(name = "groupId", description = "群编号", required = true, example = "1024")
  33 + @PreAuthorize("@ss.hasPermission('im:manager:group:query')")
  34 + public CommonResult<List<ImGroupMemberManagerRespVO>> getGroupMemberList(@RequestParam("groupId") Long groupId) {
  35 + return success(groupMemberService.getGroupMemberList(groupId));
  36 + }
  37 +
  38 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/ImGroupMessageManagerController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.message;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  6 +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group.ImGroupMessageManagerPageReqVO;
  7 +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group.ImGroupMessageManagerRespVO;
  8 +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO;
  9 +import cn.iocoder.yudao.module.im.service.message.ImGroupMessageService;
  10 +import io.swagger.v3.oas.annotations.Operation;
  11 +import io.swagger.v3.oas.annotations.Parameter;
  12 +import io.swagger.v3.oas.annotations.tags.Tag;
  13 +import jakarta.annotation.Resource;
  14 +import jakarta.validation.Valid;
  15 +import org.springframework.security.access.prepost.PreAuthorize;
  16 +import org.springframework.validation.annotation.Validated;
  17 +import org.springframework.web.bind.annotation.*;
  18 +
  19 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  20 +
  21 +@Tag(name = "管理后台 - IM 群聊消息")
  22 +@RestController
  23 +@RequestMapping("/im/manager/message/group")
  24 +@Validated
  25 +public class ImGroupMessageManagerController {
  26 +
  27 + @Resource
  28 + private ImGroupMessageService groupMessageService;
  29 +
  30 + @GetMapping("/page")
  31 + @Operation(summary = "获得群聊消息分页")
  32 + @PreAuthorize("@ss.hasPermission('im:manager:message:query')")
  33 + public CommonResult<PageResult<ImGroupMessageManagerRespVO>> getGroupMessagePage(
  34 + @Valid ImGroupMessageManagerPageReqVO pageReqVO) {
  35 + return success(groupMessageService.getGroupMessagePage(pageReqVO));
  36 + }
  37 +
  38 + @GetMapping("/get")
  39 + @Operation(summary = "获得群聊消息详情")
  40 + @Parameter(name = "id", description = "消息编号", required = true, example = "1024")
  41 + @PreAuthorize("@ss.hasPermission('im:manager:message:query')")
  42 + public CommonResult<ImGroupMessageManagerRespVO> getGroupMessage(@RequestParam("id") Long id) {
  43 + ImGroupMessageDO message = groupMessageService.getGroupMessage(id);
  44 + return success(BeanUtils.toBean(message, ImGroupMessageManagerRespVO.class));
  45 + }
  46 +
  47 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/ImPrivateMessageManagerController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.message;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  6 +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.privates.ImPrivateMessageManagerPageReqVO;
  7 +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.privates.ImPrivateMessageManagerRespVO;
  8 +import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO;
  9 +import cn.iocoder.yudao.module.im.service.message.ImPrivateMessageService;
  10 +import io.swagger.v3.oas.annotations.Operation;
  11 +import io.swagger.v3.oas.annotations.Parameter;
  12 +import io.swagger.v3.oas.annotations.tags.Tag;
  13 +import jakarta.annotation.Resource;
  14 +import jakarta.validation.Valid;
  15 +import org.springframework.security.access.prepost.PreAuthorize;
  16 +import org.springframework.validation.annotation.Validated;
  17 +import org.springframework.web.bind.annotation.*;
  18 +
  19 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  20 +
  21 +@Tag(name = "管理后台 - IM 私聊消息")
  22 +@RestController
  23 +@RequestMapping("/im/manager/message/private")
  24 +@Validated
  25 +public class ImPrivateMessageManagerController {
  26 +
  27 + @Resource
  28 + private ImPrivateMessageService privateMessageService;
  29 +
  30 + @GetMapping("/page")
  31 + @Operation(summary = "获得私聊消息分页")
  32 + @PreAuthorize("@ss.hasPermission('im:manager:message:query')")
  33 + public CommonResult<PageResult<ImPrivateMessageManagerRespVO>> getPrivateMessagePage(
  34 + @Valid ImPrivateMessageManagerPageReqVO pageReqVO) {
  35 + return success(privateMessageService.getPrivateMessagePage(pageReqVO));
  36 + }
  37 +
  38 + @GetMapping("/get")
  39 + @Operation(summary = "获得私聊消息详情")
  40 + @Parameter(name = "id", description = "消息编号", required = true, example = "1024")
  41 + @PreAuthorize("@ss.hasPermission('im:manager:message:query')")
  42 + public CommonResult<ImPrivateMessageManagerRespVO> getPrivateMessage(@RequestParam("id") Long id) {
  43 + ImPrivateMessageDO message = privateMessageService.getPrivateMessage(id);
  44 + return success(BeanUtils.toBean(message, ImPrivateMessageManagerRespVO.class));
  45 + }
  46 +
  47 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/group/ImGroupMessageManagerPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  7 +import org.springframework.format.annotation.DateTimeFormat;
  8 +
  9 +import java.time.LocalDateTime;
  10 +
  11 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  12 +
  13 +@Schema(description = "管理后台 - IM 群聊消息分页 Request VO")
  14 +@Data
  15 +@EqualsAndHashCode(callSuper = true)
  16 +public class ImGroupMessageManagerPageReqVO extends PageParam {
  17 +
  18 + @Schema(description = "群编号", example = "1024")
  19 + private Long groupId;
  20 +
  21 + @Schema(description = "发送人编号", example = "1024")
  22 + private Long senderId;
  23 +
  24 + // TODO @AI:参见 ImMessageTypeEnum 枚举类,都放到 // 注释后面。注意加到我的 memory 里。其它类似也处理下。im 的。
  25 + @Schema(description = "消息类型,参见 ImMessageTypeEnum 枚举类", example = "1")
  26 + private Integer type;
  27 +
  28 + @Schema(description = "消息状态,参见 ImMessageStatusEnum 枚举类", example = "0")
  29 + private Integer status;
  30 +
  31 + @Schema(description = "发送时间", example = "[2026-04-01 00:00:00, 2026-04-30 23:59:59]")
  32 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  33 + private LocalDateTime[] sendTime;
  34 +
  35 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/group/ImGroupMessageManagerRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +import java.util.List;
  8 +
  9 +@Schema(description = "管理后台 - IM 群聊消息 Response VO")
  10 +@Data
  11 +public class ImGroupMessageManagerRespVO {
  12 +
  13 + @Schema(description = "消息编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  14 + private Long id;
  15 +
  16 + @Schema(description = "客户端消息编号", example = "c-uuid-xxx")
  17 + private String clientMessageId;
  18 +
  19 + @Schema(description = "群编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  20 + private Long groupId;
  21 +
  22 + @Schema(description = "群名称", example = "技术交流群")
  23 + private String groupName;
  24 +
  25 + @Schema(description = "发送人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  26 + private Long senderId;
  27 +
  28 + @Schema(description = "发送人昵称", example = "张三")
  29 + private String senderNickname;
  30 +
  31 + @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  32 + private Integer type; // 参见 ImMessageTypeEnum 枚举类
  33 +
  34 + @Schema(description = "消息内容(JSON 格式)", requiredMode = Schema.RequiredMode.REQUIRED)
  35 + private String content;
  36 +
  37 + @Schema(description = "消息状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  38 + private Integer status; // 参见 ImMessageStatusEnum 枚举类
  39 +
  40 + @Schema(description = "@ 目标用户编号列表")
  41 + private List<Long> atUserIds;
  42 +
  43 + @Schema(description = "回执状态", example = "0")
  44 + private Integer receiptStatus; // 参见 ImGroupMessageReceiptStatusEnum 枚举类
  45 +
  46 + @Schema(description = "发送时间", requiredMode = Schema.RequiredMode.REQUIRED)
  47 + private LocalDateTime sendTime;
  48 +
  49 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  50 + private LocalDateTime createTime;
  51 +
  52 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/privates/ImPrivateMessageManagerPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.privates;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  7 +import org.springframework.format.annotation.DateTimeFormat;
  8 +
  9 +import java.time.LocalDateTime;
  10 +
  11 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  12 +
  13 +@Schema(description = "管理后台 - IM 私聊消息分页 Request VO")
  14 +@Data
  15 +@EqualsAndHashCode(callSuper = true)
  16 +public class ImPrivateMessageManagerPageReqVO extends PageParam {
  17 +
  18 + @Schema(description = "发送人编号", example = "1024")
  19 + private Long senderId;
  20 +
  21 + @Schema(description = "接收人编号", example = "2048")
  22 + private Long receiverId;
  23 +
  24 + @Schema(description = "消息类型,参见 ImMessageTypeEnum 枚举类", example = "1")
  25 + private Integer type;
  26 +
  27 + @Schema(description = "消息状态,参见 ImMessageStatusEnum 枚举类", example = "0")
  28 + private Integer status;
  29 +
  30 + @Schema(description = "发送时间", example = "[2026-04-01 00:00:00, 2026-04-30 23:59:59]")
  31 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  32 + private LocalDateTime[] sendTime;
  33 +
  34 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/privates/ImPrivateMessageManagerRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.privates;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +
  8 +@Schema(description = "管理后台 - IM 私聊消息 Response VO")
  9 +@Data
  10 +public class ImPrivateMessageManagerRespVO {
  11 +
  12 + @Schema(description = "消息编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  13 + private Long id;
  14 +
  15 + @Schema(description = "客户端消息编号", example = "c-uuid-xxx")
  16 + private String clientMessageId;
  17 +
  18 + @Schema(description = "发送人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  19 + private Long senderId;
  20 +
  21 + @Schema(description = "发送人昵称", example = "张三")
  22 + private String senderNickname;
  23 +
  24 + @Schema(description = "接收人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
  25 + private Long receiverId;
  26 +
  27 + @Schema(description = "接收人昵称", example = "李四")
  28 + private String receiverNickname;
  29 +
  30 + @Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
  31 + private Integer type; // 参见 ImMessageTypeEnum 枚举类
  32 +
  33 + @Schema(description = "消息内容(JSON 格式)", requiredMode = Schema.RequiredMode.REQUIRED)
  34 + private String content;
  35 +
  36 + @Schema(description = "消息状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  37 + private Integer status; // 参见 ImMessageStatusEnum 枚举类
  38 +
  39 + @Schema(description = "发送时间", requiredMode = Schema.RequiredMode.REQUIRED)
  40 + private LocalDateTime sendTime;
  41 +
  42 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  43 + private LocalDateTime createTime;
  44 +
  45 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/sensitiveword/ImSensitiveWordManagerController.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  5 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  6 +import cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo.ImSensitiveWordPageReqVO;
  7 +import cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo.ImSensitiveWordRespVO;
  8 +import cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo.ImSensitiveWordSaveReqVO;
  9 +import cn.iocoder.yudao.module.im.dal.dataobject.sensitiveword.ImSensitiveWordDO;
  10 +import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService;
  11 +import io.swagger.v3.oas.annotations.Operation;
  12 +import io.swagger.v3.oas.annotations.Parameter;
  13 +import io.swagger.v3.oas.annotations.tags.Tag;
  14 +import jakarta.annotation.Resource;
  15 +import jakarta.validation.Valid;
  16 +import org.springframework.security.access.prepost.PreAuthorize;
  17 +import org.springframework.validation.annotation.Validated;
  18 +import org.springframework.web.bind.annotation.*;
  19 +
  20 +import java.util.List;
  21 +
  22 +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
  23 +
  24 +@Tag(name = "管理后台 - IM 敏感词")
  25 +@RestController
  26 +@RequestMapping("/im/manager/sensitive-word")
  27 +@Validated
  28 +public class ImSensitiveWordManagerController {
  29 +
  30 + @Resource
  31 + private ImSensitiveWordService sensitiveWordService;
  32 +
  33 + @PostMapping("/create")
  34 + @Operation(summary = "新增敏感词")
  35 + @PreAuthorize("@ss.hasPermission('im:manager:sensitive-word:create')")
  36 + public CommonResult<Long> createSensitiveWord(@Valid @RequestBody ImSensitiveWordSaveReqVO reqVO) {
  37 + return success(sensitiveWordService.createSensitiveWord(reqVO));
  38 + }
  39 +
  40 + @PutMapping("/update")
  41 + @Operation(summary = "修改敏感词")
  42 + @PreAuthorize("@ss.hasPermission('im:manager:sensitive-word:update')")
  43 + public CommonResult<Boolean> updateSensitiveWord(@Valid @RequestBody ImSensitiveWordSaveReqVO reqVO) {
  44 + sensitiveWordService.updateSensitiveWord(reqVO);
  45 + return success(true);
  46 + }
  47 +
  48 + @DeleteMapping("/delete")
  49 + @Operation(summary = "删除敏感词")
  50 + @Parameter(name = "id", description = "编号", required = true, example = "1024")
  51 + @PreAuthorize("@ss.hasPermission('im:manager:sensitive-word:delete')")
  52 + public CommonResult<Boolean> deleteSensitiveWord(@RequestParam("id") Long id) {
  53 + sensitiveWordService.deleteSensitiveWord(id);
  54 + return success(true);
  55 + }
  56 +
  57 + @DeleteMapping("/delete-list")
  58 + @Operation(summary = "批量删除敏感词")
  59 + @Parameter(name = "ids", description = "编号列表", required = true)
  60 + @PreAuthorize("@ss.hasPermission('im:manager:sensitive-word:delete')")
  61 + public CommonResult<Boolean> deleteSensitiveWordList(@RequestParam("ids") List<Long> ids) {
  62 + sensitiveWordService.deleteSensitiveWordList(ids);
  63 + return success(true);
  64 + }
  65 +
  66 + @GetMapping("/page")
  67 + @Operation(summary = "获得敏感词分页")
  68 + @PreAuthorize("@ss.hasPermission('im:manager:sensitive-word:query')")
  69 + public CommonResult<PageResult<ImSensitiveWordRespVO>> getSensitiveWordPage(
  70 + @Valid ImSensitiveWordPageReqVO pageReqVO) {
  71 + PageResult<ImSensitiveWordDO> pageResult = sensitiveWordService.getSensitiveWordPage(pageReqVO);
  72 + return success(BeanUtils.toBean(pageResult, ImSensitiveWordRespVO.class));
  73 + }
  74 +
  75 + @GetMapping("/get")
  76 + @Operation(summary = "获得敏感词详情")
  77 + @Parameter(name = "id", description = "编号", required = true, example = "1024")
  78 + @PreAuthorize("@ss.hasPermission('im:manager:sensitive-word:query')")
  79 + public CommonResult<ImSensitiveWordRespVO> getSensitiveWord(@RequestParam("id") Long id) {
  80 + ImSensitiveWordDO word = sensitiveWordService.getSensitiveWord(id);
  81 + return success(BeanUtils.toBean(word, ImSensitiveWordRespVO.class));
  82 + }
  83 +
  84 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/sensitiveword/vo/ImSensitiveWordPageReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo;
  2 +
  3 +import cn.iocoder.yudao.framework.common.pojo.PageParam;
  4 +import io.swagger.v3.oas.annotations.media.Schema;
  5 +import lombok.Data;
  6 +import lombok.EqualsAndHashCode;
  7 +import org.springframework.format.annotation.DateTimeFormat;
  8 +
  9 +import java.time.LocalDateTime;
  10 +
  11 +import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
  12 +
  13 +@Schema(description = "管理后台 - IM 敏感词分页 Request VO")
  14 +@Data
  15 +@EqualsAndHashCode(callSuper = true)
  16 +public class ImSensitiveWordPageReqVO extends PageParam {
  17 +
  18 + @Schema(description = "敏感词,模糊匹配", example = "敏感")
  19 + private String word;
  20 +
  21 + @Schema(description = "状态,参见 CommonStatusEnum 枚举类(0 启用 / 1 禁用)", example = "0")
  22 + private Integer status;
  23 +
  24 + @Schema(description = "创建时间", example = "[2026-04-01 00:00:00, 2026-04-30 23:59:59]")
  25 + @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
  26 + private LocalDateTime[] createTime;
  27 +
  28 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/sensitiveword/vo/ImSensitiveWordRespVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import lombok.Data;
  5 +
  6 +import java.time.LocalDateTime;
  7 +
  8 +@Schema(description = "管理后台 - IM 敏感词 Response VO")
  9 +@Data
  10 +public class ImSensitiveWordRespVO {
  11 +
  12 + @Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
  13 + private Long id;
  14 +
  15 + @Schema(description = "敏感词", requiredMode = Schema.RequiredMode.REQUIRED, example = "敏感词内容")
  16 + private String word;
  17 +
  18 + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  19 + private Integer status; // 参见 CommonStatusEnum 枚举类
  20 +
  21 + @Schema(description = "创建人")
  22 + private String creator;
  23 +
  24 + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
  25 + private LocalDateTime createTime;
  26 +
  27 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/sensitiveword/vo/ImSensitiveWordSaveReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo;
  2 +
  3 +import io.swagger.v3.oas.annotations.media.Schema;
  4 +import jakarta.validation.constraints.NotBlank;
  5 +import jakarta.validation.constraints.NotNull;
  6 +import jakarta.validation.constraints.Size;
  7 +import lombok.Data;
  8 +
  9 +@Schema(description = "管理后台 - IM 敏感词新增 / 修改 Request VO")
  10 +@Data
  11 +public class ImSensitiveWordSaveReqVO {
  12 +
  13 + @Schema(description = "编号(修改时必填)", example = "1024")
  14 + private Long id;
  15 +
  16 + @Schema(description = "敏感词", requiredMode = Schema.RequiredMode.REQUIRED, example = "敏感词内容")
  17 + @NotBlank(message = "敏感词不能为空")
  18 + @Size(max = 64, message = "敏感词长度不能超过 64")
  19 + private String word;
  20 +
  21 + @Schema(description = "状态,参见 CommonStatusEnum 枚举类(0 启用 / 1 禁用)",
  22 + requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
  23 + @NotNull(message = "状态不能为空")
  24 + private Integer status;
  25 +
  26 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/friend/ImFriendMapper.java
1 1 package cn.iocoder.yudao.module.im.dal.mysql.friend;
2 2  
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
3 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
4 5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  6 +import cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo.ImFriendManagerPageReqVO;
5 7 import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO;
6 8 import org.apache.ibatis.annotations.Mapper;
7 9  
... ... @@ -43,4 +45,14 @@ public interface ImFriendMapper extends BaseMapperX&lt;ImFriendDO&gt; {
43 45 .eq(ImFriendDO::getStatus, status));
44 46 }
45 47  
  48 + default PageResult<ImFriendDO> selectPage(ImFriendManagerPageReqVO reqVO) {
  49 + return selectPage(reqVO, new LambdaQueryWrapperX<ImFriendDO>()
  50 + .eqIfPresent(ImFriendDO::getUserId, reqVO.getUserId())
  51 + .eqIfPresent(ImFriendDO::getFriendUserId, reqVO.getFriendUserId())
  52 + .eqIfPresent(ImFriendDO::getStatus, reqVO.getStatus())
  53 + .eqIfPresent(ImFriendDO::getMuted, reqVO.getMuted())
  54 + .betweenIfPresent(ImFriendDO::getAddTime, reqVO.getAddTime())
  55 + .orderByDesc(ImFriendDO::getId));
  56 + }
  57 +
46 58 }
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupMapper.java
1 1 package cn.iocoder.yudao.module.im.dal.mysql.group;
2 2  
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
3 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
  5 +import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  6 +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerPageReqVO;
4 7 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
5 8 import org.apache.ibatis.annotations.Mapper;
6 9  
... ... @@ -11,4 +14,15 @@ import org.apache.ibatis.annotations.Mapper;
11 14 */
12 15 @Mapper
13 16 public interface ImGroupMapper extends BaseMapperX<ImGroupDO> {
  17 +
  18 + default PageResult<ImGroupDO> selectPage(ImGroupManagerPageReqVO reqVO) {
  19 + return selectPage(reqVO, new LambdaQueryWrapperX<ImGroupDO>()
  20 + .likeIfPresent(ImGroupDO::getName, reqVO.getName())
  21 + .eqIfPresent(ImGroupDO::getOwnerUserId, reqVO.getOwnerUserId())
  22 + .eqIfPresent(ImGroupDO::getStatus, reqVO.getStatus())
  23 + .eqIfPresent(ImGroupDO::getBanned, reqVO.getBanned())
  24 + .betweenIfPresent(ImGroupDO::getCreateTime, reqVO.getCreateTime())
  25 + .orderByDesc(ImGroupDO::getId));
  26 + }
  27 +
14 28 }
15 29 \ No newline at end of file
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupMemberMapper.java
1 1 package cn.iocoder.yudao.module.im.dal.mysql.group;
2 2  
  3 +import cn.hutool.core.collection.CollUtil;
3 4 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
4 5 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5 6 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
6 7 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
  8 +import com.baomidou.mybatisplus.core.toolkit.Wrappers;
7 9 import org.apache.ibatis.annotations.Mapper;
8 10  
9 11 import java.time.LocalDateTime;
10 12 import java.util.Collection;
  13 +import java.util.Collections;
  14 +import java.util.HashMap;
11 15 import java.util.List;
  16 +import java.util.Map;
12 17  
13 18 /**
14 19 * IM 群成员 Mapper
... ... @@ -78,4 +83,25 @@ public interface ImGroupMemberMapper extends BaseMapperX&lt;ImGroupMemberDO&gt; {
78 83 .eq(ImGroupMemberDO::getStatus, oldStatus));
79 84 }
80 85  
  86 + /**
  87 + * 批量按 group_id 统计指定状态的成员数
  88 + */
  89 + default Map<Long, Long> selectCountMapByGroupIdsAndStatus(Collection<Long> groupIds, Integer status) {
  90 + if (CollUtil.isEmpty(groupIds)) {
  91 + return Collections.emptyMap();
  92 + }
  93 + // TODO @AI:使用 mybatis plus join,可以简化这个操作么?
  94 + List<Map<String, Object>> rows = selectMaps(Wrappers.<ImGroupMemberDO>query()
  95 + .select("group_id AS groupId", "COUNT(*) AS cnt")
  96 + .in("group_id", groupIds)
  97 + .eq("status", status)
  98 + .groupBy("group_id"));
  99 + // 转换成 Map<Long, Long>
  100 + Map<Long, Long> result = new HashMap<>(rows.size());
  101 + rows.forEach(row -> result.put(
  102 + ((Number) row.get("groupId")).longValue(),
  103 + ((Number) row.get("cnt")).longValue()));
  104 + return result;
  105 + }
  106 +
81 107 }
82 108 \ No newline at end of file
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImGroupMessageMapper.java
1 1 package cn.iocoder.yudao.module.im.dal.mysql.message;
2 2  
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
3 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
4 5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
5 6 import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;
  7 +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group.ImGroupMessageManagerPageReqVO;
6 8 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO;
7 9 import cn.iocoder.yudao.module.im.enums.message.ImGroupMessageReceiptStatusEnum;
8 10 import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum;
... ... @@ -113,4 +115,14 @@ public interface ImGroupMessageMapper extends BaseMapperX&lt;ImGroupMessageDO&gt; {
113 115 .ne(ImGroupMessageDO::getStatus, ImMessageStatusEnum.RECALL.getStatus()));
114 116 }
115 117  
  118 + default PageResult<ImGroupMessageDO> selectPage(ImGroupMessageManagerPageReqVO reqVO) {
  119 + return selectPage(reqVO, new LambdaQueryWrapperX<ImGroupMessageDO>()
  120 + .eqIfPresent(ImGroupMessageDO::getGroupId, reqVO.getGroupId())
  121 + .eqIfPresent(ImGroupMessageDO::getSenderId, reqVO.getSenderId())
  122 + .eqIfPresent(ImGroupMessageDO::getType, reqVO.getType())
  123 + .eqIfPresent(ImGroupMessageDO::getStatus, reqVO.getStatus())
  124 + .betweenIfPresent(ImGroupMessageDO::getSendTime, reqVO.getSendTime())
  125 + .orderByDesc(ImGroupMessageDO::getId));
  126 + }
  127 +
116 128 }
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/message/ImPrivateMessageMapper.java
1 1 package cn.iocoder.yudao.module.im.dal.mysql.message;
2 2  
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
3 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
4 5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
5 6 import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;
  7 +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.privates.ImPrivateMessageManagerPageReqVO;
6 8 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO;
7 9 import cn.iocoder.yudao.module.im.enums.message.ImMessageStatusEnum;
8 10 import org.apache.ibatis.annotations.Mapper;
... ... @@ -86,4 +88,14 @@ public interface ImPrivateMessageMapper extends BaseMapperX&lt;ImPrivateMessageDO&gt;
86 88 .eq(ImPrivateMessageDO::getStatus, whereStatus));
87 89 }
88 90  
  91 + default PageResult<ImPrivateMessageDO> selectPage(ImPrivateMessageManagerPageReqVO reqVO) {
  92 + return selectPage(reqVO, new LambdaQueryWrapperX<ImPrivateMessageDO>()
  93 + .eqIfPresent(ImPrivateMessageDO::getSenderId, reqVO.getSenderId())
  94 + .eqIfPresent(ImPrivateMessageDO::getReceiverId, reqVO.getReceiverId())
  95 + .eqIfPresent(ImPrivateMessageDO::getType, reqVO.getType())
  96 + .eqIfPresent(ImPrivateMessageDO::getStatus, reqVO.getStatus())
  97 + .betweenIfPresent(ImPrivateMessageDO::getSendTime, reqVO.getSendTime())
  98 + .orderByDesc(ImPrivateMessageDO::getId));
  99 + }
  100 +
89 101 }
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/sensitiveword/ImSensitiveWordMapper.java
1 1 package cn.iocoder.yudao.module.im.dal.mysql.sensitiveword;
2 2  
3   -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
5 5 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
  6 +import cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo.ImSensitiveWordPageReqVO;
6 7 import cn.iocoder.yudao.module.im.dal.dataobject.sensitiveword.ImSensitiveWordDO;
7 8 import org.apache.ibatis.annotations.Mapper;
8 9  
... ... @@ -16,10 +17,22 @@ import java.util.List;
16 17 @Mapper
17 18 public interface ImSensitiveWordMapper extends BaseMapperX<ImSensitiveWordDO> {
18 19  
19   - // DONE @AI:已改名为 selectListByStatus,status 作为参数
20 20 default List<ImSensitiveWordDO> selectListByStatus(Integer status) {
21 21 return selectList(new LambdaQueryWrapperX<ImSensitiveWordDO>()
22 22 .eq(ImSensitiveWordDO::getStatus, status));
23 23 }
24 24  
  25 + default ImSensitiveWordDO selectByWord(String word) {
  26 + return selectOne(new LambdaQueryWrapperX<ImSensitiveWordDO>()
  27 + .eq(ImSensitiveWordDO::getWord, word));
  28 + }
  29 +
  30 + default PageResult<ImSensitiveWordDO> selectPage(ImSensitiveWordPageReqVO reqVO) {
  31 + return selectPage(reqVO, new LambdaQueryWrapperX<ImSensitiveWordDO>()
  32 + .likeIfPresent(ImSensitiveWordDO::getWord, reqVO.getWord())
  33 + .eqIfPresent(ImSensitiveWordDO::getStatus, reqVO.getStatus())
  34 + .betweenIfPresent(ImSensitiveWordDO::getCreateTime, reqVO.getCreateTime())
  35 + .orderByDesc(ImSensitiveWordDO::getId));
  36 + }
  37 +
25 38 }
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ErrorCodeConstants.java
... ... @@ -34,4 +34,8 @@ public interface ErrorCodeConstants {
34 34 ErrorCode FRIEND_NOT_FRIEND = new ErrorCode(1_040_600_001, "对方不是您的好友");
35 35 ErrorCode FRIEND_ADD_SELF = new ErrorCode(1_040_600_002, "不允许添加自己为好友");
36 36  
  37 + // ========== 敏感词 (1-040-700-000) ==========
  38 + ErrorCode SENSITIVE_WORD_NOT_EXISTS = new ErrorCode(1_040_700_000, "敏感词不存在");
  39 + ErrorCode SENSITIVE_WORD_DUPLICATED = new ErrorCode(1_040_700_001, "敏感词 '{}' 已存在");
  40 +
37 41 }
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/sensitiveword/ImSensitiveWordConfiguration.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.framework.sensitiveword;
  2 +
  3 +import com.github.houbb.sensitive.word.bs.SensitiveWordBs;
  4 +import org.springframework.context.annotation.Bean;
  5 +import org.springframework.context.annotation.Configuration;
  6 +
  7 +/**
  8 + * IM 敏感词检测配置
  9 + *
  10 + * @author 芋道源码
  11 + */
  12 +@Configuration
  13 +public class ImSensitiveWordConfiguration {
  14 +
  15 + // TODO @AI:可能需要按需创建;因为多租户。
  16 + /**
  17 + * 装配 {@link SensitiveWordBs}:
  18 + * 词库来源 = {@link ImSensitiveWordDeny}(DB 里启用的敏感词),并开启常用规范化能力。
  19 + */
  20 + @Bean
  21 + public SensitiveWordBs sensitiveWordBs(ImSensitiveWordDeny imSensitiveWordDeny) {
  22 + return SensitiveWordBs.newInstance()
  23 + .wordDeny(imSensitiveWordDeny)
  24 + .ignoreCase(true)
  25 + .ignoreWidth(true) // 忽略全/半角
  26 + .ignoreNumStyle(true) // 忽略数字风格(中文/阿拉伯)
  27 + .ignoreChineseStyle(true) // 忽略繁简体
  28 + .enableWordCheck(true)
  29 + .init();
  30 + }
  31 +
  32 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/sensitiveword/ImSensitiveWordDeny.java 0 → 100644
  1 +package cn.iocoder.yudao.module.im.framework.sensitiveword;
  2 +
  3 +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  4 +import cn.iocoder.yudao.module.im.dal.dataobject.sensitiveword.ImSensitiveWordDO;
  5 +import cn.iocoder.yudao.module.im.dal.mysql.sensitiveword.ImSensitiveWordMapper;
  6 +import com.github.houbb.sensitive.word.api.IWordDeny;
  7 +import jakarta.annotation.Resource;
  8 +import org.springframework.stereotype.Component;
  9 +
  10 +import java.util.List;
  11 +
  12 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  13 +
  14 +// TODO @AI:是不是可以,直接塞入一个数组。而不是这里查询???
  15 +/**
  16 + * 自定义敏感词数据来源:从 im_sensitive_word 表加载启用状态的敏感词
  17 + * <p>
  18 + * 由 {@link com.github.houbb.sensitive.word.bs.SensitiveWordBs} 在 init() 时回调,
  19 + * 因此 CRUD 完成后调用 sensitiveWordBs.init() 即可实现热更新。
  20 + *
  21 + * @author 芋道源码
  22 + */
  23 +@Component
  24 +public class ImSensitiveWordDeny implements IWordDeny {
  25 +
  26 + @Resource
  27 + private ImSensitiveWordMapper sensitiveWordMapper;
  28 +
  29 + @Override
  30 + public List<String> deny() {
  31 + return convertList(
  32 + sensitiveWordMapper.selectListByStatus(CommonStatusEnum.ENABLE.getStatus()),
  33 + ImSensitiveWordDO::getWord);
  34 + }
  35 +
  36 +}
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendService.java
1 1 package cn.iocoder.yudao.module.im.service.friend;
2 2  
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
3 4 import cn.iocoder.yudao.module.im.controller.admin.friend.vo.ImFriendUpdateReqVO;
  5 +import cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo.ImFriendManagerPageReqVO;
  6 +import cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo.ImFriendManagerRespVO;
4 7 import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO;
5 8  
6 9 import java.util.Collection;
... ... @@ -84,5 +87,11 @@ public interface ImFriendService {
84 87 */
85 88 void updateFriend(Long userId, ImFriendUpdateReqVO reqVO);
86 89  
  90 + // ==================== 管理后台 ====================
  91 +
  92 + /**
  93 + * 【管理后台】分页查询好友关系(已填充用户 / 好友昵称)
  94 + */
  95 + PageResult<ImFriendManagerRespVO> getFriendPage(ImFriendManagerPageReqVO reqVO);
87 96  
88 97 }
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImpl.java
1 1 package cn.iocoder.yudao.module.im.service.friend;
2 2  
  3 +import cn.hutool.core.collection.CollUtil;
3 4 import cn.hutool.extra.spring.SpringUtil;
4 5 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  6 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  7 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
  8 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
5 9 import cn.iocoder.yudao.module.im.controller.admin.friend.vo.ImFriendUpdateReqVO;
  10 +import cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo.ImFriendManagerPageReqVO;
  11 +import cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo.ImFriendManagerRespVO;
6 12 import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO;
7 13 import cn.iocoder.yudao.module.im.dal.mysql.friend.ImFriendMapper;
8 14 import cn.iocoder.yudao.module.im.service.message.ImPrivateMessageService;
9 15 import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService;
10 16 import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO;
11 17 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  18 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
12 19 import jakarta.annotation.Resource;
13 20 import lombok.extern.slf4j.Slf4j;
14 21 import org.springframework.cache.annotation.CacheEvict;
... ... @@ -20,11 +27,10 @@ import org.springframework.transaction.annotation.Transactional;
20 27 import org.springframework.validation.annotation.Validated;
21 28  
22 29 import java.time.LocalDateTime;
23   -import java.util.Collection;
24   -import java.util.List;
25   -import java.util.Objects;
  30 +import java.util.*;
26 31  
27 32 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  33 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
28 34 import static cn.iocoder.yudao.module.im.dal.redis.RedisKeyConstants.FRIEND;
29 35 import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FRIEND_ADD_SELF;
30 36 import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FRIEND_NOT_FRIEND;
... ... @@ -181,4 +187,28 @@ public class ImFriendServiceImpl implements ImFriendService {
181 187 return SpringUtil.getBean(getClass());
182 188 }
183 189  
  190 + // ==================== 管理后台 ====================
  191 +
  192 + // TODO @AI:拿到 controller 拼接数据,简化 service;
  193 + @Override
  194 + public PageResult<ImFriendManagerRespVO> getFriendPage(ImFriendManagerPageReqVO reqVO) {
  195 + // 1. 分页查询
  196 + PageResult<ImFriendDO> pageResult = imFriendMapper.selectPage(reqVO);
  197 + if (CollUtil.isEmpty(pageResult.getList())) {
  198 + return PageResult.empty(pageResult.getTotal());
  199 + }
  200 +
  201 + // 2. 一次性批量查询用户 + 好友的昵称
  202 + Set<Long> userIds = new HashSet<>();
  203 + userIds.addAll(convertSet(pageResult.getList(), ImFriendDO::getUserId));
  204 + userIds.addAll(convertSet(pageResult.getList(), ImFriendDO::getFriendUserId));
  205 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
  206 +
  207 + // 3. 转换为 VO,填充昵称
  208 + return BeanUtils.toBean(pageResult, ImFriendManagerRespVO.class, vo -> {
  209 + MapUtils.findAndThen(userMap, vo.getUserId(), user -> vo.setUserNickname(user.getNickname()));
  210 + MapUtils.findAndThen(userMap, vo.getFriendUserId(), user -> vo.setFriendNickname(user.getNickname()));
  211 + });
  212 + }
  213 +
184 214 }
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberService.java
1 1 package cn.iocoder.yudao.module.im.service.group;
2 2  
3 3 import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberUpdateReqVO;
  4 +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.member.ImGroupMemberManagerRespVO;
4 5 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
5 6 import jakarta.validation.Valid;
6 7  
7 8 import java.time.LocalDateTime;
8 9 import java.util.Collection;
9 10 import java.util.List;
  11 +import java.util.Map;
10 12  
11 13 /**
12 14 * 群成员 Service 接口
... ... @@ -24,6 +26,15 @@ public interface ImGroupMemberService {
24 26 ImGroupMemberDO getGroupMember(Long id);
25 27  
26 28 /**
  29 + * 获得群成员
  30 + *
  31 + * @param groupId 群编号
  32 + * @param userId 用户编号
  33 + * @return 群成员
  34 + */
  35 + ImGroupMemberDO getGroupMember(Long groupId, Long userId);
  36 +
  37 + /**
27 38 * 根据群组 id 查询群成员(包含所有状态)
28 39 *
29 40 * @param groupId 群组id
... ... @@ -137,4 +148,22 @@ public interface ImGroupMemberService {
137 148 */
138 149 void removeGroupMembersByGroupId(Long groupId);
139 150  
  151 + /**
  152 + * 批量按 group 统计活跃成员数:(group_id → count)
  153 + *
  154 + * @param groupIds 群编号集合
  155 + * @return 群成员数 Map
  156 + */
  157 + Map<Long, Long> getActiveMemberCountMap(Collection<Long> groupIds);
  158 +
  159 + // ==================== 管理后台 ====================
  160 +
  161 + /**
  162 + * 【管理后台】获取群成员列表
  163 + *
  164 + * @param groupId 群编号
  165 + * @return 群成员列表
  166 + */
  167 + List<ImGroupMemberManagerRespVO> getGroupMemberList(Long groupId);
  168 +
140 169 }
... ...
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;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
4 4 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  5 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
5 6 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
6 7 import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberUpdateReqVO;
  8 +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.member.ImGroupMemberManagerRespVO;
7 9 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
8 10 import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMemberMapper;
9 11 import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService;
10 12 import cn.iocoder.yudao.module.im.service.websocket.dto.ImGroupMessageDTO;
  13 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  14 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
11 15 import jakarta.annotation.Resource;
12 16 import lombok.extern.slf4j.Slf4j;
13 17 import org.springframework.cache.annotation.CacheEvict;
... ... @@ -17,14 +21,10 @@ import org.springframework.stereotype.Service;
17 21 import org.springframework.validation.annotation.Validated;
18 22  
19 23 import java.time.LocalDateTime;
20   -import java.util.ArrayList;
21   -import java.util.Collection;
22   -import java.util.List;
23   -import java.util.Map;
  24 +import java.util.*;
24 25  
25 26 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
26   -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
27   -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
  27 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
28 28 import static cn.iocoder.yudao.module.im.dal.redis.RedisKeyConstants.GROUP_MEMBER_IDS;
29 29 import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.GROUP_MEMBER_NOT_IN_GROUP;
30 30  
... ... @@ -44,12 +44,20 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService {
44 44 @Resource
45 45 private ImWebSocketService webSocketService;
46 46  
  47 + @Resource
  48 + private AdminUserApi adminUserApi;
  49 +
47 50 @Override
48 51 public ImGroupMemberDO getGroupMember(Long id) {
49 52 return groupMemberMapper.selectById(id);
50 53 }
51 54  
52 55 @Override
  56 + public ImGroupMemberDO getGroupMember(Long groupId, Long userId) {
  57 + return groupMemberMapper.selectByGroupIdAndUserId(groupId, userId);
  58 + }
  59 +
  60 + @Override
53 61 public List<ImGroupMemberDO> getGroupMemberListByGroupId(Long groupId) {
54 62 return groupMemberMapper.selectListByGroupId(groupId);
55 63 }
... ... @@ -209,4 +217,30 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService {
209 217 .setQuitTime(LocalDateTime.now()));
210 218 }
211 219  
  220 + @Override
  221 + public Map<Long, Long> getActiveMemberCountMap(Collection<Long> groupIds) {
  222 + return groupMemberMapper.selectCountMapByGroupIdsAndStatus(groupIds, CommonStatusEnum.ENABLE.getStatus());
  223 + }
  224 +
  225 + // ==================== 管理后台 ====================
  226 +
  227 + // TODO @AI:这个拼接的逻辑,拿到 controller,这里只返回 list do;
  228 + @Override
  229 + public List<ImGroupMemberManagerRespVO> getGroupMemberList(Long groupId) {
  230 + // 1. 查询有效群成员
  231 + List<ImGroupMemberDO> members = getActiveGroupMemberListByGroupId(groupId);
  232 + if (CollUtil.isEmpty(members)) {
  233 + return Collections.emptyList();
  234 + }
  235 +
  236 + // 2. 批量查询用户信息
  237 + Set<Long> userIds = convertSet(members, ImGroupMemberDO::getUserId);
  238 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
  239 +
  240 + // 3. 转换为 VO,填充昵称、头像
  241 + return BeanUtils.toBean(members, ImGroupMemberManagerRespVO.class, vo ->
  242 + MapUtils.findAndThen(userMap, vo.getUserId(), user ->
  243 + vo.setNickname(user.getNickname()).setAvatar(user.getAvatar())));
  244 + }
  245 +
212 246 }
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupService.java
1 1 package cn.iocoder.yudao.module.im.service.group;
2 2  
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
3 4 import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupCreateReqVO;
4 5 import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupUpdateReqVO;
5 6 import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberInviteReqVO;
6 7 import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberRemoveReqVO;
  8 +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerBanReqVO;
  9 +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerPageReqVO;
  10 +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerRespVO;
7 11 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
8 12 import jakarta.validation.Valid;
9 13  
  14 +import java.util.Collection;
10 15 import java.util.List;
  16 +import java.util.Map;
11 17  
12 18 /**
13 19 * 群 Service 接口
... ... @@ -59,6 +65,14 @@ public interface ImGroupService {
59 65 ImGroupDO getGroup(Long id);
60 66  
61 67 /**
  68 + * 批量获得群 Map
  69 + *
  70 + * @param ids 群编号集合
  71 + * @return 群 Map(key = 群编号)
  72 + */
  73 + Map<Long, ImGroupDO> getGroupMap(Collection<Long> ids);
  74 +
  75 + /**
62 76 * 校验群存在且未封禁、未解散
63 77 *
64 78 * @param groupId 群编号
... ... @@ -122,4 +136,30 @@ public interface ImGroupService {
122 136 */
123 137 void removeGroupMember(Long userId, @Valid ImGroupMemberRemoveReqVO removeReqVO);
124 138  
  139 + // ==================== 管理后台 ====================
  140 +
  141 + // TODO DONE @AI:是不是 controller 拆分哈;(getGroupPage 留在 ImGroupService;getGroupMemberList 已拆到 ImGroupMemberService + ImGroupMemberManagerController)
  142 + // TODO @AI:不对;应该 VO 放在 controller 拼接
  143 + /**
  144 + * 【管理后台】分页查询群列表
  145 + *
  146 + * @param pageReqVO 分页查询条件
  147 + * @return 群分页列表
  148 + */
  149 + PageResult<ImGroupManagerRespVO> getGroupPage(ImGroupManagerPageReqVO pageReqVO);
  150 +
  151 + /**
  152 + * 【管理后台】封禁群
  153 + *
  154 + * @param banReqVO 封禁信息(含群编号、封禁原因)
  155 + */
  156 + void banGroup(@Valid ImGroupManagerBanReqVO banReqVO);
  157 +
  158 + /**
  159 + * 【管理后台】解封群
  160 + *
  161 + * @param id 群编号
  162 + */
  163 + void unbanGroup(Long id);
  164 +
125 165 }
... ...
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;
7 7 import cn.hutool.core.util.StrUtil;
8 8 import cn.hutool.extra.spring.SpringUtil;
9 9 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  10 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  11 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
10 12 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
11 13 import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupCreateReqVO;
12 14 import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupUpdateReqVO;
13 15 import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberInviteReqVO;
14 16 import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberRemoveReqVO;
  17 +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerBanReqVO;
  18 +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerPageReqVO;
  19 +import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerRespVO;
15 20 import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO;
16 21 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
17 22 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
... ... @@ -35,6 +40,7 @@ import java.util.*;
35 40 import java.util.stream.Collectors;
36 41  
37 42 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  43 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
38 44 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
39 45 import static cn.iocoder.yudao.module.im.dal.redis.RedisKeyConstants.GROUP;
40 46 import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*;
... ... @@ -256,6 +262,14 @@ public class ImGroupServiceImpl implements ImGroupService {
256 262 }
257 263  
258 264 @Override
  265 + public Map<Long, ImGroupDO> getGroupMap(Collection<Long> ids) {
  266 + if (CollUtil.isEmpty(ids)) {
  267 + return Collections.emptyMap();
  268 + }
  269 + return convertMap(groupMapper.selectByIds(ids), ImGroupDO::getId);
  270 + }
  271 +
  272 + @Override
259 273 public List<ImGroupDO> getMyGroupList(Long userId) {
260 274 // 1.1 查用户所在的、仍有效的群成员记录(仅 ENABLE 状态)
261 275 List<ImGroupMemberDO> members = groupMemberService.getActiveGroupMemberListByUserId(userId);
... ... @@ -271,6 +285,53 @@ public class ImGroupServiceImpl implements ImGroupService {
271 285 return groupMapper.selectByIds(groupIds);
272 286 }
273 287  
  288 + // ==================== 管理后台 ====================
  289 +
  290 + @Override
  291 + public PageResult<ImGroupManagerRespVO> getGroupPage(ImGroupManagerPageReqVO pageReqVO) {
  292 + // 1. 分页查询群
  293 + PageResult<ImGroupDO> pageResult = groupMapper.selectPage(pageReqVO);
  294 + if (CollUtil.isEmpty(pageResult.getList())) {
  295 + return PageResult.empty(pageResult.getTotal());
  296 + }
  297 +
  298 + // 2.1 批量查询群主昵称
  299 + Set<Long> ownerUserIds = convertSet(pageResult.getList(), ImGroupDO::getOwnerUserId);
  300 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(ownerUserIds);
  301 + // 2.2 一次 GROUP BY 查出本页所有群的活跃成员数,避免按行 N+1
  302 + Set<Long> groupIds = convertSet(pageResult.getList(), ImGroupDO::getId);
  303 + Map<Long, Long> memberCountMap = groupMemberService.getActiveMemberCountMap(groupIds);
  304 + // 2.3 转换为 VO,填充群主昵称、群成员数量
  305 + return BeanUtils.toBean(pageResult, ImGroupManagerRespVO.class, vo -> {
  306 + MapUtils.findAndThen(userMap, vo.getOwnerUserId(), user -> vo.setOwnerNickname(user.getNickname()));
  307 + vo.setMemberCount(memberCountMap.getOrDefault(vo.getId(), 0L).intValue());
  308 + });
  309 + }
  310 +
  311 + @Override
  312 + @CacheEvict(cacheNames = GROUP, key = "#banReqVO.id")
  313 + public void banGroup(ImGroupManagerBanReqVO banReqVO) {
  314 + // 1. 校验群存在
  315 + if (getSelf().getGroup(banReqVO.getId()) == null) {
  316 + throw exception(GROUP_NOT_EXISTS);
  317 + }
  318 + // 2. 更新封禁状态
  319 + groupMapper.updateById(new ImGroupDO().setId(banReqVO.getId())
  320 + .setBanned(true).setBannedReason(banReqVO.getReason()).setBannedTime(LocalDateTime.now()));
  321 + }
  322 +
  323 + @Override
  324 + @CacheEvict(cacheNames = GROUP, key = "#id")
  325 + public void unbanGroup(Long id) {
  326 + // 1. 校验群存在
  327 + if (getSelf().getGroup(id) == null) {
  328 + throw exception(GROUP_NOT_EXISTS);
  329 + }
  330 + // 2. 解封(保留 bannedReason / bannedTime 作为历史记录)
  331 + // TODO DONE @AI:不要使用 mybatis plus 的方法,直接操作。另外,感觉封禁时间、封禁原因,没必要置空,留着问题不大。
  332 + groupMapper.updateById(new ImGroupDO().setId(id).setBanned(false));
  333 + }
  334 +
274 335 private ImGroupServiceImpl getSelf() {
275 336 return SpringUtil.getBean(getClass());
276 337 }
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageService.java
1 1 package cn.iocoder.yudao.module.im.service.message;
2 2  
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group.ImGroupMessageManagerPageReqVO;
  5 +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group.ImGroupMessageManagerRespVO;
3 6 import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageListReqVO;
4 7 import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageSendReqVO;
5 8 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO;
... ... @@ -112,5 +115,17 @@ public interface ImGroupMessageService {
112 115 */
113 116 void sendTipGroupMessage(Long senderId, Long groupId, Set<Long> memberUserIds, String content);
114 117  
  118 + // ==================== 管理后台 ====================
  119 +
  120 + /**
  121 + * 【管理后台】分页查询群聊消息(已填充群名称、发送人昵称)
  122 + */
  123 + PageResult<ImGroupMessageManagerRespVO> getGroupMessagePage(ImGroupMessageManagerPageReqVO reqVO);
  124 +
  125 + /**
  126 + * 【管理后台】获取群聊消息详情
  127 + */
  128 + ImGroupMessageDO getGroupMessage(Long id);
  129 +
115 130 }
116 131  
... ...
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;
6 6 import cn.hutool.core.util.ObjUtil;
7 7 import cn.hutool.extra.spring.SpringUtil;
8 8 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  9 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  10 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
9 11 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
10 12 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  13 +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group.ImGroupMessageManagerPageReqVO;
  14 +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group.ImGroupMessageManagerRespVO;
11 15 import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageListReqVO;
12 16 import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageSendReqVO;
  17 +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
13 18 import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
14 19 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO;
15   -import cn.iocoder.yudao.module.im.service.websocket.dto.message.RecallMessage;
16 20 import cn.iocoder.yudao.module.im.dal.mysql.message.ImGroupMessageMapper;
17 21 import cn.iocoder.yudao.module.im.dal.redis.message.GroupMessageReadRedisDAO;
18 22 import cn.iocoder.yudao.module.im.enums.message.ImGroupMessageReceiptStatusEnum;
... ... @@ -23,6 +27,9 @@ import cn.iocoder.yudao.module.im.service.group.ImGroupService;
23 27 import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService;
24 28 import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService;
25 29 import cn.iocoder.yudao.module.im.service.websocket.dto.ImGroupMessageDTO;
  30 +import cn.iocoder.yudao.module.im.service.websocket.dto.message.RecallMessage;
  31 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  32 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
26 33 import jakarta.annotation.Resource;
27 34 import lombok.extern.slf4j.Slf4j;
28 35 import org.springframework.scheduling.annotation.Async;
... ... @@ -70,6 +77,9 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService {
70 77 @Resource
71 78 private ImWebSocketService imWebSocketService;
72 79  
  80 + @Resource
  81 + private AdminUserApi adminUserApi;
  82 +
73 83 @Override
74 84 public ImGroupMessageDO sendGroupMessage(Long senderId, ImGroupMessageSendReqVO reqVO) {
75 85 // 1.1 幂等校验:根据 senderId + clientMessageId 查重
... ... @@ -542,6 +552,35 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService {
542 552 ImGroupMessageDTO.ofSend(tipMessage));
543 553 }
544 554  
  555 + // ==================== 管理后台 ====================
  556 +
  557 + // TODO @AI:这些挪到 controller 拼接 VO
  558 + @Override
  559 + public PageResult<ImGroupMessageManagerRespVO> getGroupMessagePage(ImGroupMessageManagerPageReqVO reqVO) {
  560 + // 1. 分页查询
  561 + PageResult<ImGroupMessageDO> pageResult = groupMessageMapper.selectPage(reqVO);
  562 + if (CollUtil.isEmpty(pageResult.getList())) {
  563 + return PageResult.empty(pageResult.getTotal());
  564 + }
  565 +
  566 + // 2. 批量查询群名称、发送人昵称
  567 + Set<Long> groupIds = convertSet(pageResult.getList(), ImGroupMessageDO::getGroupId);
  568 + Set<Long> senderIds = convertSet(pageResult.getList(), ImGroupMessageDO::getSenderId);
  569 + Map<Long, ImGroupDO> groupMap = groupService.getGroupMap(groupIds);
  570 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(senderIds);
  571 +
  572 + // 3. 转换为 VO,填充群名 / 发送人昵称
  573 + return BeanUtils.toBean(pageResult, ImGroupMessageManagerRespVO.class, vo -> {
  574 + MapUtils.findAndThen(groupMap, vo.getGroupId(), group -> vo.setGroupName(group.getName()));
  575 + MapUtils.findAndThen(userMap, vo.getSenderId(), user -> vo.setSenderNickname(user.getNickname()));
  576 + });
  577 + }
  578 +
  579 + @Override
  580 + public ImGroupMessageDO getGroupMessage(Long id) {
  581 + return groupMessageMapper.selectById(id);
  582 + }
  583 +
545 584 private ImGroupMessageServiceImpl getSelf() {
546 585 return SpringUtil.getBean(getClass());
547 586 }
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageService.java
1 1 package cn.iocoder.yudao.module.im.service.message;
2 2  
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.privates.ImPrivateMessageManagerPageReqVO;
  5 +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.privates.ImPrivateMessageManagerRespVO;
3 6 import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageListReqVO;
4 7 import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageSendReqVO;
5 8 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO;
... ... @@ -85,4 +88,16 @@ public interface ImPrivateMessageService {
85 88 */
86 89 void sendTipPrivateMessage(Long senderId, Long receiverId, String content);
87 90  
  91 + // ==================== 管理后台 ====================
  92 +
  93 + /**
  94 + * 【管理后台】分页查询私聊消息(已填充发送人 / 接收人昵称)
  95 + */
  96 + PageResult<ImPrivateMessageManagerRespVO> getPrivateMessagePage(ImPrivateMessageManagerPageReqVO reqVO);
  97 +
  98 + /**
  99 + * 【管理后台】获取私聊消息详情
  100 + */
  101 + ImPrivateMessageDO getPrivateMessage(Long id);
  102 +
88 103 }
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java
1 1 package cn.iocoder.yudao.module.im.service.message;
2 2  
  3 +import cn.hutool.core.collection.CollUtil;
3 4 import cn.hutool.core.lang.Assert;
4 5 import cn.hutool.core.util.IdUtil;
5 6 import cn.hutool.core.util.ObjUtil;
  7 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  8 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
6 9 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
7 10 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  11 +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.privates.ImPrivateMessageManagerPageReqVO;
  12 +import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.privates.ImPrivateMessageManagerRespVO;
8 13 import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageListReqVO;
9 14 import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageSendReqVO;
10 15 import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO;
... ... @@ -16,6 +21,8 @@ import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService;
16 21 import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService;
17 22 import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO;
18 23 import cn.iocoder.yudao.module.im.service.websocket.dto.message.RecallMessage;
  24 +import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
  25 +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
19 26 import jakarta.annotation.Resource;
20 27 import lombok.extern.slf4j.Slf4j;
21 28 import org.springframework.stereotype.Service;
... ... @@ -23,9 +30,13 @@ import org.springframework.transaction.annotation.Transactional;
23 30 import org.springframework.validation.annotation.Validated;
24 31  
25 32 import java.time.LocalDateTime;
  33 +import java.util.HashSet;
26 34 import java.util.List;
  35 +import java.util.Map;
  36 +import java.util.Set;
27 37  
28 38 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  39 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
29 40 import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*;
30 41 import static cn.iocoder.yudao.module.im.enums.ImCommonConstants.*;
31 42  
... ... @@ -50,6 +61,9 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService {
50 61 @Resource
51 62 private ImWebSocketService imWebSocketService;
52 63  
  64 + @Resource
  65 + private AdminUserApi adminUserApi;
  66 +
53 67 @Override
54 68 public ImPrivateMessageDO sendPrivateMessage(Long senderId, ImPrivateMessageSendReqVO reqVO) {
55 69 // 1.1 幂等校验:根据 senderId + clientMessageId 查重
... ... @@ -167,6 +181,35 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService {
167 181 return privateMessageMapper.selectHistoryList(userId, reqVO.getReceiverId(), reqVO.getMaxId(), reqVO.getLimit());
168 182 }
169 183  
  184 + // ==================== 管理后台 ====================
  185 +
  186 + // TODO @AI:这些挪到 controller 拼接 VO
  187 + @Override
  188 + public PageResult<ImPrivateMessageManagerRespVO> getPrivateMessagePage(ImPrivateMessageManagerPageReqVO reqVO) {
  189 + // 1. 分页查询
  190 + PageResult<ImPrivateMessageDO> pageResult = privateMessageMapper.selectPage(reqVO);
  191 + if (CollUtil.isEmpty(pageResult.getList())) {
  192 + return PageResult.empty(pageResult.getTotal());
  193 + }
  194 +
  195 + // 2. 一次性批量查询发送人 + 接收人昵称
  196 + Set<Long> userIds = new HashSet<>();
  197 + userIds.addAll(convertSet(pageResult.getList(), ImPrivateMessageDO::getSenderId));
  198 + userIds.addAll(convertSet(pageResult.getList(), ImPrivateMessageDO::getReceiverId));
  199 + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
  200 +
  201 + // 3. 转换为 VO,填充昵称
  202 + return BeanUtils.toBean(pageResult, ImPrivateMessageManagerRespVO.class, vo -> {
  203 + MapUtils.findAndThen(userMap, vo.getSenderId(), user -> vo.setSenderNickname(user.getNickname()));
  204 + MapUtils.findAndThen(userMap, vo.getReceiverId(), user -> vo.setReceiverNickname(user.getNickname()));
  205 + });
  206 + }
  207 +
  208 + @Override
  209 + public ImPrivateMessageDO getPrivateMessage(Long id) {
  210 + return privateMessageMapper.selectById(id);
  211 + }
  212 +
170 213 @Override
171 214 public void sendTipPrivateMessage(Long senderId, Long receiverId, String content) {
172 215 // 1. 插入 TIP_TEXT 消息
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/sensitiveword/ImSensitiveWordService.java
1 1 package cn.iocoder.yudao.module.im.service.sensitiveword;
2 2  
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo.ImSensitiveWordPageReqVO;
  5 +import cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo.ImSensitiveWordSaveReqVO;
  6 +import cn.iocoder.yudao.module.im.dal.dataobject.sensitiveword.ImSensitiveWordDO;
  7 +import jakarta.validation.Valid;
  8 +
  9 +import java.util.List;
  10 +
3 11 /**
4 12 * IM 敏感词 Service 接口
5 13 *
... ... @@ -14,4 +22,36 @@ public interface ImSensitiveWordService {
14 22 */
15 23 void validateText(String text);
16 24  
  25 + // ==================== 管理后台 ====================
  26 +
  27 + /**
  28 + * 【管理后台】分页查询敏感词
  29 + */
  30 + PageResult<ImSensitiveWordDO> getSensitiveWordPage(ImSensitiveWordPageReqVO reqVO);
  31 +
  32 + /**
  33 + * 【管理后台】获取敏感词详情
  34 + */
  35 + ImSensitiveWordDO getSensitiveWord(Long id);
  36 +
  37 + /**
  38 + * 【管理后台】新增敏感词,返回新增 id
  39 + */
  40 + Long createSensitiveWord(@Valid ImSensitiveWordSaveReqVO reqVO);
  41 +
  42 + /**
  43 + * 【管理后台】修改敏感词
  44 + */
  45 + void updateSensitiveWord(@Valid ImSensitiveWordSaveReqVO reqVO);
  46 +
  47 + /**
  48 + * 【管理后台】删除敏感词
  49 + */
  50 + void deleteSensitiveWord(Long id);
  51 +
  52 + /**
  53 + * 【管理后台】批量删除敏感词
  54 + */
  55 + void deleteSensitiveWordList(List<Long> ids);
  56 +
17 57 }
... ...
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/sensitiveword/ImSensitiveWordServiceImpl.java
1 1 package cn.iocoder.yudao.module.im.service.sensitiveword;
2 2  
3   -import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
  3 +import cn.hutool.core.collection.CollUtil;
  4 +import cn.hutool.core.util.ObjUtil;
  5 +import cn.hutool.core.util.StrUtil;
  6 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  7 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
  8 +import cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo.ImSensitiveWordPageReqVO;
  9 +import cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo.ImSensitiveWordSaveReqVO;
4 10 import cn.iocoder.yudao.module.im.dal.dataobject.sensitiveword.ImSensitiveWordDO;
5 11 import cn.iocoder.yudao.module.im.dal.mysql.sensitiveword.ImSensitiveWordMapper;
6   -import jakarta.annotation.PostConstruct;
  12 +import com.github.houbb.sensitive.word.bs.SensitiveWordBs;
7 13 import jakarta.annotation.Resource;
8 14 import lombok.extern.slf4j.Slf4j;
9   -import org.springframework.scheduling.annotation.Scheduled;
10 15 import org.springframework.stereotype.Service;
11 16 import org.springframework.validation.annotation.Validated;
12 17  
13   -import java.util.Collections;
14 18 import java.util.List;
15   -import java.util.stream.Collectors;
16 19  
17 20 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
18 21 import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.MESSAGE_SENSITIVE_WORD_BLOCKED;
  22 +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.SENSITIVE_WORD_DUPLICATED;
  23 +import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.SENSITIVE_WORD_NOT_EXISTS;
19 24  
20   -// DONE @AI:已实现内存化,启动时加载 + 定时刷新
21 25 /**
22 26 * IM 敏感词 Service 实现类
  27 + * <p>
  28 + * 词库匹配交给 houbb sensitive-word 库(trie 树 + 全/半角 / 大小写 / 繁简体 / 数字风格规范化)
23 29 *
24 30 * @author 芋道源码
25 31 */
... ... @@ -29,48 +35,122 @@ import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.MESSAGE_SENSIT
29 35 public class ImSensitiveWordServiceImpl implements ImSensitiveWordService {
30 36  
31 37 @Resource
32   - private ImSensitiveWordMapper imSensitiveWordMapper;
  38 + private ImSensitiveWordMapper sensitiveWordMapper;
33 39  
  40 + // TODO @AI:当前节点,立即刷新。其他节点,通过 job 扫描最后更新时间(只要有不同于目前最后一条记录的最大更新时间,就重新构建对应的树)。
34 41 /**
35   - * 内存缓存的敏感词列表
  42 + * houbb 敏感词检测器,词库由 ImSensitiveWordDeny 在 init 时回调加载
36 43 */
37   - private volatile List<String> sensitiveWords = Collections.emptyList();
  44 + @Resource
  45 + private SensitiveWordBs sensitiveWordBs;
38 46  
  47 + // TODO @AI:你看看现有仓库里,是否有类似这种的。按照最后更新判断,是否需要刷新本地缓存。
39 48 /**
40   - * 初始化:启动时加载敏感词到内存
  49 + * 触发 houbb 词库重新加载(重建 trie)
  50 + * <p>
  51 + * 加 synchronized 是因为 SensitiveWordBs.init() 内部对 wordData 做就地写入,
  52 + * 若与并发的 contains() 重叠会读到半构建的 trie。互斥后短暂阻塞 contains 即可保证可见性。
  53 + * <p>
  54 + * init() 本身幂等,单实例可以直接调;多实例部署时需配合 Redis pub/sub 广播让各实例 init。
41 55 */
42   - @PostConstruct
43   - public void init() {
44   - loadSensitiveWords();
  56 + private synchronized void reloadSensitiveWords() {
  57 + sensitiveWordBs.init();
  58 + log.info("[reloadSensitiveWords][敏感词库已 reload]");
45 59 }
46 60  
47   - /**
48   - * 定时刷新:每 5 分钟重新加载一次
49   - */
50   - @Scheduled(fixedDelay = 5 * 60 * 1000, initialDelay = 5 * 60 * 1000)
51   - public void refresh() {
52   - loadSensitiveWords();
  61 + // TODO @AI:需要按照租户来。
  62 + @Override
  63 + public void validateText(String text) {
  64 + if (StrUtil.isBlank(text)) {
  65 + return;
  66 + }
  67 + if (sensitiveWordBs.contains(text)) {
  68 + throw exception(MESSAGE_SENSITIVE_WORD_BLOCKED);
  69 + }
53 70 }
54 71  
55   - private void loadSensitiveWords() {
56   - List<ImSensitiveWordDO> enabledWords = imSensitiveWordMapper.selectListByStatus(
57   - CommonStatusEnum.ENABLE.getStatus());
58   - sensitiveWords = enabledWords.stream()
59   - .map(ImSensitiveWordDO::getWord)
60   - .collect(Collectors.toList());
61   - log.info("[loadSensitiveWords][加载 IM 敏感词 {} 个]", sensitiveWords.size());
  72 + // ==================== 管理后台 ====================
  73 +
  74 + @Override
  75 + public PageResult<ImSensitiveWordDO> getSensitiveWordPage(ImSensitiveWordPageReqVO reqVO) {
  76 + return sensitiveWordMapper.selectPage(reqVO);
62 77 }
63 78  
64 79 @Override
65   - public void validateText(String text) {
66   - if (text == null || text.isEmpty()) {
  80 + public ImSensitiveWordDO getSensitiveWord(Long id) {
  81 + return sensitiveWordMapper.selectById(id);
  82 + }
  83 +
  84 + @Override
  85 + public Long createSensitiveWord(ImSensitiveWordSaveReqVO reqVO) {
  86 + // 1. 校验唯一
  87 + validateWordUnique(null, reqVO.getWord());
  88 +
  89 + // 2.1 入库
  90 + ImSensitiveWordDO word = BeanUtils.toBean(reqVO, ImSensitiveWordDO.class);
  91 + sensitiveWordMapper.insert(word);
  92 + // TODO @AI:houbb 这个注释有点奇怪!
  93 + // 2.2 触发 houbb 词库 reload
  94 + reloadSensitiveWords();
  95 + return word.getId();
  96 + }
  97 +
  98 + @Override
  99 + public void updateSensitiveWord(ImSensitiveWordSaveReqVO reqVO) {
  100 + // 1.1 校验存在
  101 + validateSensitiveWordExists(reqVO.getId());
  102 + // 1.2 校验唯一(排除自身)
  103 + validateWordUnique(reqVO.getId(), reqVO.getWord());
  104 +
  105 + // 2.1 更新
  106 + ImSensitiveWordDO updateObj = BeanUtils.toBean(reqVO, ImSensitiveWordDO.class);
  107 + sensitiveWordMapper.updateById(updateObj);
  108 + // 2.2 TODO @AI:houbb 这个注释有点奇怪!
  109 + // 2.2 触发 houbb 词库 reload
  110 + reloadSensitiveWords();
  111 + }
  112 +
  113 + @Override
  114 + public void deleteSensitiveWord(Long id) {
  115 + // 1. 校验存在
  116 + validateSensitiveWordExists(id);
  117 +
  118 + // 2.1 删除
  119 + sensitiveWordMapper.deleteById(id);
  120 + // 2.2 触发 houbb 词库 reload
  121 + reloadSensitiveWords();
  122 + }
  123 +
  124 +
  125 + @Override
  126 + public void deleteSensitiveWordList(List<Long> ids) {
  127 + if (CollUtil.isEmpty(ids)) {
  128 + return;
  129 + }
  130 +
  131 + // 1. 删除
  132 + sensitiveWordMapper.deleteByIds(ids);
  133 + // 触发 houbb 词库 reload
  134 + reloadSensitiveWords();
  135 + }
  136 +
  137 + private void validateSensitiveWordExists(Long id) {
  138 + if (sensitiveWordMapper.selectById(id) == null) {
  139 + throw exception(SENSITIVE_WORD_NOT_EXISTS);
  140 + }
  141 + }
  142 +
  143 + /**
  144 + * 校验敏感词唯一(修改时排除自身)
  145 + */
  146 + private void validateWordUnique(Long id, String word) {
  147 + ImSensitiveWordDO exist = sensitiveWordMapper.selectByWord(word);
  148 + if (exist == null) {
67 149 return;
68 150 }
69   - // 内存匹配
70   - for (String word : sensitiveWords) {
71   - if (text.contains(word)) {
72   - throw exception(MESSAGE_SENSITIVE_WORD_BLOCKED);
73   - }
  151 + // TODO @AI:是不是 id 不用判断
  152 + if (id == null || ObjUtil.notEqual(exist.getId(), id)) {
  153 + throw exception(SENSITIVE_WORD_DUPLICATED, word);
74 154 }
75 155 }
76 156  
... ...