Commit fc90635abbf739518f18204ceeae90f3f00e748e
1 parent
37f0a8a0
✨ feat(im): 新增 friend、group 等管理后台接口
Showing
27 changed files
with
282 additions
and
278 deletions
yudao-dependencies/pom.xml
| ... | ... | @@ -50,6 +50,7 @@ |
| 50 | 50 | <!-- 工具类相关 --> |
| 51 | 51 | <anji-plus-captcha.version>1.4.0</anji-plus-captcha.version> |
| 52 | 52 | <jsoup.version>1.21.2</jsoup.version> |
| 53 | + <sensitive-word.version>0.29.5</sensitive-word.version> | |
| 53 | 54 | <lombok.version>1.18.42</lombok.version> |
| 54 | 55 | <mapstruct.version>1.6.3</mapstruct.version> |
| 55 | 56 | <hutool-5.version>5.8.42</hutool-5.version> |
| ... | ... | @@ -558,6 +559,12 @@ |
| 558 | 559 | <version>${jsoup.version}</version> |
| 559 | 560 | </dependency> |
| 560 | 561 | |
| 562 | + <dependency> | |
| 563 | + <groupId>com.github.houbb</groupId> | |
| 564 | + <artifactId>sensitive-word</artifactId> <!-- 敏感词检测:trie 树高效匹配 --> | |
| 565 | + <version>${sensitive-word.version}</version> | |
| 566 | + </dependency> | |
| 567 | + | |
| 561 | 568 | <!-- Vert.x --> |
| 562 | 569 | <dependency> |
| 563 | 570 | <groupId>io.vertx</groupId> | ... | ... |
yudao-module-im/pom.xml
| ... | ... | @@ -62,12 +62,10 @@ |
| 62 | 62 | <artifactId>yudao-spring-boot-starter-excel</artifactId> |
| 63 | 63 | </dependency> |
| 64 | 64 | |
| 65 | - <!-- 敏感词检测:houbb sensitive-word(trie 树高效匹配,支持忽略大小写 / 全半角 / 数字风格) --> | |
| 66 | - <!-- TODO @AI:放到基础 pom 里管理。 --> | |
| 65 | + <!-- 敏感词检测:sensitive-word(trie 树高效匹配,支持忽略大小写 / 全半角 / 数字风格),版本由 yudao-dependencies 统一管理 --> | |
| 67 | 66 | <dependency> |
| 68 | 67 | <groupId>com.github.houbb</groupId> |
| 69 | 68 | <artifactId>sensitive-word</artifactId> |
| 70 | - <version>0.29.5</version> | |
| 71 | 69 | </dependency> |
| 72 | 70 | |
| 73 | 71 | <!-- Test 测试相关 --> | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/friend/ImFriendManagerController.java
| 1 | 1 | package cn.iocoder.yudao.module.im.controller.admin.manager.friend; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.collection.CollUtil; | |
| 3 | 4 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 4 | 5 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 6 | +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; | |
| 7 | +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | |
| 5 | 8 | import cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo.ImFriendManagerPageReqVO; |
| 6 | 9 | import cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo.ImFriendManagerRespVO; |
| 10 | +import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; | |
| 7 | 11 | import cn.iocoder.yudao.module.im.service.friend.ImFriendService; |
| 12 | +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | |
| 13 | +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; | |
| 8 | 14 | import io.swagger.v3.oas.annotations.Operation; |
| 9 | 15 | import io.swagger.v3.oas.annotations.tags.Tag; |
| 10 | 16 | import jakarta.annotation.Resource; |
| ... | ... | @@ -15,7 +21,12 @@ import org.springframework.web.bind.annotation.GetMapping; |
| 15 | 21 | import org.springframework.web.bind.annotation.RequestMapping; |
| 16 | 22 | import org.springframework.web.bind.annotation.RestController; |
| 17 | 23 | |
| 24 | +import java.util.Map; | |
| 25 | +import java.util.Set; | |
| 26 | +import java.util.stream.Stream; | |
| 27 | + | |
| 18 | 28 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| 29 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap; | |
| 19 | 30 | |
| 20 | 31 | @Tag(name = "管理后台 - IM 好友管理") |
| 21 | 32 | @RestController |
| ... | ... | @@ -25,13 +36,30 @@ public class ImFriendManagerController { |
| 25 | 36 | |
| 26 | 37 | @Resource |
| 27 | 38 | private ImFriendService friendService; |
| 39 | + @Resource | |
| 40 | + private AdminUserApi adminUserApi; | |
| 28 | 41 | |
| 29 | 42 | @GetMapping("/page") |
| 30 | 43 | @Operation(summary = "获得好友关系分页") |
| 31 | 44 | @PreAuthorize("@ss.hasPermission('im:manager:friend:query')") |
| 32 | 45 | public CommonResult<PageResult<ImFriendManagerRespVO>> getFriendPage( |
| 33 | 46 | @Valid ImFriendManagerPageReqVO pageReqVO) { |
| 34 | - return success(friendService.getFriendPage(pageReqVO)); | |
| 47 | + // 1. 分页查询 | |
| 48 | + PageResult<ImFriendDO> pageResult = friendService.getFriendPage(pageReqVO); | |
| 49 | + if (CollUtil.isEmpty(pageResult.getList())) { | |
| 50 | + return success(PageResult.empty(pageResult.getTotal())); | |
| 51 | + } | |
| 52 | + // 2.1 一次性批量查询用户 + 好友的昵称 | |
| 53 | + Set<Long> userIds = convertSetByFlatMap(pageResult.getList(), | |
| 54 | + f -> Stream.of(f.getUserId(), f.getFriendUserId())); | |
| 55 | + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds); | |
| 56 | + // 2.2 转换为 VO,填充昵称 | |
| 57 | + return success(BeanUtils.toBean(pageResult, ImFriendManagerRespVO.class, vo -> { | |
| 58 | + MapUtils.findAndThen(userMap, vo.getUserId(), | |
| 59 | + user -> vo.setUserNickname(user.getNickname())); | |
| 60 | + MapUtils.findAndThen(userMap, vo.getFriendUserId(), | |
| 61 | + user -> vo.setFriendNickname(user.getNickname())); | |
| 62 | + })); | |
| 35 | 63 | } |
| 36 | 64 | |
| 37 | 65 | } | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/ImGroupManagerController.java
| 1 | 1 | package cn.iocoder.yudao.module.im.controller.admin.manager.group; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.collection.CollUtil; | |
| 3 | 4 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 4 | 5 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 6 | +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; | |
| 5 | 7 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 6 | 8 | import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerBanReqVO; |
| 7 | 9 | import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerPageReqVO; |
| 8 | 10 | import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerRespVO; |
| 9 | 11 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; |
| 12 | +import cn.iocoder.yudao.module.im.service.group.ImGroupMemberService; | |
| 10 | 13 | import cn.iocoder.yudao.module.im.service.group.ImGroupService; |
| 14 | +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | |
| 15 | +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; | |
| 11 | 16 | import io.swagger.v3.oas.annotations.Operation; |
| 12 | 17 | import io.swagger.v3.oas.annotations.Parameter; |
| 13 | 18 | import io.swagger.v3.oas.annotations.tags.Tag; |
| ... | ... | @@ -17,7 +22,10 @@ import org.springframework.security.access.prepost.PreAuthorize; |
| 17 | 22 | import org.springframework.validation.annotation.Validated; |
| 18 | 23 | import org.springframework.web.bind.annotation.*; |
| 19 | 24 | |
| 25 | +import java.util.Map; | |
| 26 | + | |
| 20 | 27 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| 28 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; | |
| 21 | 29 | |
| 22 | 30 | @Tag(name = "管理后台 - IM 群聊管理") |
| 23 | 31 | @RestController |
| ... | ... | @@ -27,12 +35,32 @@ public class ImGroupManagerController { |
| 27 | 35 | |
| 28 | 36 | @Resource |
| 29 | 37 | private ImGroupService groupService; |
| 38 | + @Resource | |
| 39 | + private ImGroupMemberService groupMemberService; | |
| 40 | + | |
| 41 | + @Resource | |
| 42 | + private AdminUserApi adminUserApi; | |
| 30 | 43 | |
| 31 | 44 | @GetMapping("/page") |
| 32 | 45 | @Operation(summary = "获得群分页") |
| 33 | 46 | @PreAuthorize("@ss.hasPermission('im:manager:group:query')") |
| 34 | 47 | public CommonResult<PageResult<ImGroupManagerRespVO>> getGroupPage(@Valid ImGroupManagerPageReqVO pageReqVO) { |
| 35 | - return success(groupService.getGroupPage(pageReqVO)); | |
| 48 | + // 1. 分页查询群 | |
| 49 | + PageResult<ImGroupDO> pageResult = groupService.getGroupPage(pageReqVO); | |
| 50 | + if (CollUtil.isEmpty(pageResult.getList())) { | |
| 51 | + return success(PageResult.empty(pageResult.getTotal())); | |
| 52 | + } | |
| 53 | + // 2.1 批量查询相关数据 | |
| 54 | + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap( | |
| 55 | + convertSet(pageResult.getList(), ImGroupDO::getOwnerUserId)); | |
| 56 | + Map<Long, Long> memberCountMap = groupMemberService.getActiveMemberCountMap( | |
| 57 | + convertSet(pageResult.getList(), ImGroupDO::getId)); | |
| 58 | + // 2.2 转换为 VO,填充群主昵称、群成员数量 | |
| 59 | + return success(BeanUtils.toBean(pageResult, ImGroupManagerRespVO.class, vo -> { | |
| 60 | + MapUtils.findAndThen(userMap, vo.getOwnerUserId(), | |
| 61 | + user -> vo.setOwnerNickname(user.getNickname())); | |
| 62 | + vo.setMemberCount(memberCountMap.getOrDefault(vo.getId(), 0L).intValue()); | |
| 63 | + })); | |
| 36 | 64 | } |
| 37 | 65 | |
| 38 | 66 | @GetMapping("/get") | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/ImGroupMemberManagerController.java
| 1 | 1 | package cn.iocoder.yudao.module.im.controller.admin.manager.group; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.collection.CollUtil; | |
| 3 | 4 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 5 | +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; | |
| 6 | +import cn.iocoder.yudao.framework.common.util.object.BeanUtils; | |
| 4 | 7 | import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.member.ImGroupMemberManagerRespVO; |
| 8 | +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; | |
| 5 | 9 | import cn.iocoder.yudao.module.im.service.group.ImGroupMemberService; |
| 10 | +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | |
| 11 | +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; | |
| 6 | 12 | import io.swagger.v3.oas.annotations.Operation; |
| 7 | 13 | import io.swagger.v3.oas.annotations.Parameter; |
| 8 | 14 | import io.swagger.v3.oas.annotations.tags.Tag; |
| ... | ... | @@ -14,9 +20,13 @@ import org.springframework.web.bind.annotation.RequestMapping; |
| 14 | 20 | import org.springframework.web.bind.annotation.RequestParam; |
| 15 | 21 | import org.springframework.web.bind.annotation.RestController; |
| 16 | 22 | |
| 23 | +import java.util.Collections; | |
| 17 | 24 | import java.util.List; |
| 25 | +import java.util.Map; | |
| 26 | +import java.util.Set; | |
| 18 | 27 | |
| 19 | 28 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| 29 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; | |
| 20 | 30 | |
| 21 | 31 | @Tag(name = "管理后台 - IM 群成员管理") |
| 22 | 32 | @RestController |
| ... | ... | @@ -26,13 +36,26 @@ public class ImGroupMemberManagerController { |
| 26 | 36 | |
| 27 | 37 | @Resource |
| 28 | 38 | private ImGroupMemberService groupMemberService; |
| 39 | + @Resource | |
| 40 | + private AdminUserApi adminUserApi; | |
| 29 | 41 | |
| 30 | 42 | @GetMapping("/list") |
| 31 | 43 | @Operation(summary = "获得群成员列表") |
| 32 | 44 | @Parameter(name = "groupId", description = "群编号", required = true, example = "1024") |
| 33 | 45 | @PreAuthorize("@ss.hasPermission('im:manager:group:query')") |
| 34 | 46 | public CommonResult<List<ImGroupMemberManagerRespVO>> getGroupMemberList(@RequestParam("groupId") Long groupId) { |
| 35 | - return success(groupMemberService.getGroupMemberList(groupId)); | |
| 47 | + // 1. 查询有效群成员 | |
| 48 | + List<ImGroupMemberDO> members = groupMemberService.getActiveGroupMemberListByGroupId(groupId); | |
| 49 | + if (CollUtil.isEmpty(members)) { | |
| 50 | + return success(Collections.emptyList()); | |
| 51 | + } | |
| 52 | + // 2.1 批量查询用户信息 | |
| 53 | + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap( | |
| 54 | + convertSet(members, ImGroupMemberDO::getUserId)); | |
| 55 | + // 2.2 转换为 VO,填充昵称、头像 | |
| 56 | + return success(BeanUtils.toBean(members, ImGroupMemberManagerRespVO.class, vo -> | |
| 57 | + MapUtils.findAndThen(userMap, vo.getUserId(), user -> | |
| 58 | + vo.setNickname(user.getNickname()).setAvatar(user.getAvatar())))); | |
| 36 | 59 | } |
| 37 | 60 | |
| 38 | 61 | } | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/group/vo/ImGroupManagerPageReqVO.java
| ... | ... | @@ -21,8 +21,8 @@ public class ImGroupManagerPageReqVO extends PageParam { |
| 21 | 21 | @Schema(description = "群主用户编号", example = "1024") |
| 22 | 22 | private Long ownerUserId; |
| 23 | 23 | |
| 24 | - @Schema(description = "群状态,参见 CommonStatusEnum 枚举类(0 正常 / 1 已解散)", example = "0") | |
| 25 | - private Integer status; | |
| 24 | + @Schema(description = "群状态", example = "0") | |
| 25 | + private Integer status; // 参见 CommonStatusEnum 枚举类(0 正常 / 1 已解散) | |
| 26 | 26 | |
| 27 | 27 | @Schema(description = "是否封禁", example = "false") |
| 28 | 28 | private Boolean banned; | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/ImGroupMessageManagerController.java
| 1 | 1 | package cn.iocoder.yudao.module.im.controller.admin.manager.message; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.collection.CollUtil; | |
| 3 | 4 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 4 | 5 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 6 | +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; | |
| 5 | 7 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 6 | 8 | import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group.ImGroupMessageManagerPageReqVO; |
| 7 | 9 | import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group.ImGroupMessageManagerRespVO; |
| 10 | +import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; | |
| 8 | 11 | import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; |
| 12 | +import cn.iocoder.yudao.module.im.service.group.ImGroupService; | |
| 9 | 13 | import cn.iocoder.yudao.module.im.service.message.ImGroupMessageService; |
| 14 | +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | |
| 15 | +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; | |
| 10 | 16 | import io.swagger.v3.oas.annotations.Operation; |
| 11 | 17 | import io.swagger.v3.oas.annotations.Parameter; |
| 12 | 18 | import io.swagger.v3.oas.annotations.tags.Tag; |
| ... | ... | @@ -14,9 +20,15 @@ import jakarta.annotation.Resource; |
| 14 | 20 | import jakarta.validation.Valid; |
| 15 | 21 | import org.springframework.security.access.prepost.PreAuthorize; |
| 16 | 22 | import org.springframework.validation.annotation.Validated; |
| 17 | -import org.springframework.web.bind.annotation.*; | |
| 23 | +import org.springframework.web.bind.annotation.GetMapping; | |
| 24 | +import org.springframework.web.bind.annotation.RequestMapping; | |
| 25 | +import org.springframework.web.bind.annotation.RequestParam; | |
| 26 | +import org.springframework.web.bind.annotation.RestController; | |
| 27 | + | |
| 28 | +import java.util.Map; | |
| 18 | 29 | |
| 19 | 30 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| 31 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; | |
| 20 | 32 | |
| 21 | 33 | @Tag(name = "管理后台 - IM 群聊消息") |
| 22 | 34 | @RestController |
| ... | ... | @@ -26,13 +38,31 @@ public class ImGroupMessageManagerController { |
| 26 | 38 | |
| 27 | 39 | @Resource |
| 28 | 40 | private ImGroupMessageService groupMessageService; |
| 41 | + @Resource | |
| 42 | + private ImGroupService groupService; | |
| 43 | + @Resource | |
| 44 | + private AdminUserApi adminUserApi; | |
| 29 | 45 | |
| 30 | 46 | @GetMapping("/page") |
| 31 | 47 | @Operation(summary = "获得群聊消息分页") |
| 32 | 48 | @PreAuthorize("@ss.hasPermission('im:manager:message:query')") |
| 33 | 49 | public CommonResult<PageResult<ImGroupMessageManagerRespVO>> getGroupMessagePage( |
| 34 | 50 | @Valid ImGroupMessageManagerPageReqVO pageReqVO) { |
| 35 | - return success(groupMessageService.getGroupMessagePage(pageReqVO)); | |
| 51 | + // 1. 分页查询 | |
| 52 | + PageResult<ImGroupMessageDO> pageResult = groupMessageService.getGroupMessagePage(pageReqVO); | |
| 53 | + if (CollUtil.isEmpty(pageResult.getList())) { | |
| 54 | + return success(PageResult.empty(pageResult.getTotal())); | |
| 55 | + } | |
| 56 | + // 2.1 批量查询群名称、发送人昵称 | |
| 57 | + Map<Long, ImGroupDO> groupMap = groupService.getGroupMap( | |
| 58 | + convertSet(pageResult.getList(), ImGroupMessageDO::getGroupId)); | |
| 59 | + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap( | |
| 60 | + convertSet(pageResult.getList(), ImGroupMessageDO::getSenderId)); | |
| 61 | + // 2.2 转换为 VO,填充群名 / 发送人昵称 | |
| 62 | + return success(BeanUtils.toBean(pageResult, ImGroupMessageManagerRespVO.class, vo -> { | |
| 63 | + MapUtils.findAndThen(groupMap, vo.getGroupId(), group -> vo.setGroupName(group.getName())); | |
| 64 | + MapUtils.findAndThen(userMap, vo.getSenderId(), user -> vo.setSenderNickname(user.getNickname())); | |
| 65 | + })); | |
| 36 | 66 | } |
| 37 | 67 | |
| 38 | 68 | @GetMapping("/get") | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/ImPrivateMessageManagerController.java
| 1 | 1 | package cn.iocoder.yudao.module.im.controller.admin.manager.message; |
| 2 | 2 | |
| 3 | +import cn.hutool.core.collection.CollUtil; | |
| 3 | 4 | import cn.iocoder.yudao.framework.common.pojo.CommonResult; |
| 4 | 5 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 6 | +import cn.iocoder.yudao.framework.common.util.collection.MapUtils; | |
| 5 | 7 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 6 | 8 | import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.privates.ImPrivateMessageManagerPageReqVO; |
| 7 | 9 | import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.privates.ImPrivateMessageManagerRespVO; |
| 8 | 10 | import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; |
| 9 | 11 | import cn.iocoder.yudao.module.im.service.message.ImPrivateMessageService; |
| 12 | +import cn.iocoder.yudao.module.system.api.user.AdminUserApi; | |
| 13 | +import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; | |
| 10 | 14 | import io.swagger.v3.oas.annotations.Operation; |
| 11 | 15 | import io.swagger.v3.oas.annotations.Parameter; |
| 12 | 16 | import io.swagger.v3.oas.annotations.tags.Tag; |
| ... | ... | @@ -14,9 +18,16 @@ import jakarta.annotation.Resource; |
| 14 | 18 | import jakarta.validation.Valid; |
| 15 | 19 | import org.springframework.security.access.prepost.PreAuthorize; |
| 16 | 20 | import org.springframework.validation.annotation.Validated; |
| 17 | -import org.springframework.web.bind.annotation.*; | |
| 21 | +import org.springframework.web.bind.annotation.GetMapping; | |
| 22 | +import org.springframework.web.bind.annotation.RequestMapping; | |
| 23 | +import org.springframework.web.bind.annotation.RequestParam; | |
| 24 | +import org.springframework.web.bind.annotation.RestController; | |
| 25 | + | |
| 26 | +import java.util.Map; | |
| 27 | +import java.util.stream.Stream; | |
| 18 | 28 | |
| 19 | 29 | import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; |
| 30 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap; | |
| 20 | 31 | |
| 21 | 32 | @Tag(name = "管理后台 - IM 私聊消息") |
| 22 | 33 | @RestController |
| ... | ... | @@ -26,13 +37,27 @@ public class ImPrivateMessageManagerController { |
| 26 | 37 | |
| 27 | 38 | @Resource |
| 28 | 39 | private ImPrivateMessageService privateMessageService; |
| 40 | + @Resource | |
| 41 | + private AdminUserApi adminUserApi; | |
| 29 | 42 | |
| 30 | 43 | @GetMapping("/page") |
| 31 | 44 | @Operation(summary = "获得私聊消息分页") |
| 32 | 45 | @PreAuthorize("@ss.hasPermission('im:manager:message:query')") |
| 33 | 46 | public CommonResult<PageResult<ImPrivateMessageManagerRespVO>> getPrivateMessagePage( |
| 34 | 47 | @Valid ImPrivateMessageManagerPageReqVO pageReqVO) { |
| 35 | - return success(privateMessageService.getPrivateMessagePage(pageReqVO)); | |
| 48 | + // 1. 分页查询 | |
| 49 | + PageResult<ImPrivateMessageDO> pageResult = privateMessageService.getPrivateMessagePage(pageReqVO); | |
| 50 | + if (CollUtil.isEmpty(pageResult.getList())) { | |
| 51 | + return success(PageResult.empty(pageResult.getTotal())); | |
| 52 | + } | |
| 53 | + // 2.1 一次性批量查询发送人 + 接收人昵称 | |
| 54 | + Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertSetByFlatMap(pageResult.getList(), | |
| 55 | + m -> Stream.of(m.getSenderId(), m.getReceiverId()))); | |
| 56 | + // 2.2 转换为 VO,填充昵称 | |
| 57 | + return success(BeanUtils.toBean(pageResult, ImPrivateMessageManagerRespVO.class, vo -> { | |
| 58 | + MapUtils.findAndThen(userMap, vo.getSenderId(), user -> vo.setSenderNickname(user.getNickname())); | |
| 59 | + MapUtils.findAndThen(userMap, vo.getReceiverId(), user -> vo.setReceiverNickname(user.getNickname())); | |
| 60 | + })); | |
| 36 | 61 | } |
| 37 | 62 | |
| 38 | 63 | @GetMapping("/get") | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/group/ImGroupMessageManagerPageReqVO.java
| ... | ... | @@ -21,12 +21,11 @@ public class ImGroupMessageManagerPageReqVO extends PageParam { |
| 21 | 21 | @Schema(description = "发送人编号", example = "1024") |
| 22 | 22 | private Long senderId; |
| 23 | 23 | |
| 24 | - // TODO @AI:参见 ImMessageTypeEnum 枚举类,都放到 // 注释后面。注意加到我的 memory 里。其它类似也处理下。im 的。 | |
| 25 | - @Schema(description = "消息类型,参见 ImMessageTypeEnum 枚举类", example = "1") | |
| 26 | - private Integer type; | |
| 24 | + @Schema(description = "消息类型", example = "1") | |
| 25 | + private Integer type; // 参见 ImMessageTypeEnum 枚举类 | |
| 27 | 26 | |
| 28 | - @Schema(description = "消息状态,参见 ImMessageStatusEnum 枚举类", example = "0") | |
| 29 | - private Integer status; | |
| 27 | + @Schema(description = "消息状态", example = "0") | |
| 28 | + private Integer status; // 参见 ImMessageStatusEnum 枚举类 | |
| 30 | 29 | |
| 31 | 30 | @Schema(description = "发送时间", example = "[2026-04-01 00:00:00, 2026-04-30 23:59:59]") |
| 32 | 31 | @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/message/vo/privates/ImPrivateMessageManagerPageReqVO.java
| ... | ... | @@ -21,11 +21,11 @@ public class ImPrivateMessageManagerPageReqVO extends PageParam { |
| 21 | 21 | @Schema(description = "接收人编号", example = "2048") |
| 22 | 22 | private Long receiverId; |
| 23 | 23 | |
| 24 | - @Schema(description = "消息类型,参见 ImMessageTypeEnum 枚举类", example = "1") | |
| 25 | - private Integer type; | |
| 24 | + @Schema(description = "消息类型", example = "1") | |
| 25 | + private Integer type; // 参见 ImMessageTypeEnum 枚举类 | |
| 26 | 26 | |
| 27 | - @Schema(description = "消息状态,参见 ImMessageStatusEnum 枚举类", example = "0") | |
| 28 | - private Integer status; | |
| 27 | + @Schema(description = "消息状态", example = "0") | |
| 28 | + private Integer status; // 参见 ImMessageStatusEnum 枚举类 | |
| 29 | 29 | |
| 30 | 30 | @Schema(description = "发送时间", example = "[2026-04-01 00:00:00, 2026-04-30 23:59:59]") |
| 31 | 31 | @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/sensitiveword/vo/ImSensitiveWordPageReqVO.java
| ... | ... | @@ -18,8 +18,8 @@ public class ImSensitiveWordPageReqVO extends PageParam { |
| 18 | 18 | @Schema(description = "敏感词,模糊匹配", example = "敏感") |
| 19 | 19 | private String word; |
| 20 | 20 | |
| 21 | - @Schema(description = "状态,参见 CommonStatusEnum 枚举类(0 启用 / 1 禁用)", example = "0") | |
| 22 | - private Integer status; | |
| 21 | + @Schema(description = "状态", example = "0") | |
| 22 | + private Integer status; // 参见 CommonStatusEnum 枚举类(0 启用 / 1 禁用) | |
| 23 | 23 | |
| 24 | 24 | @Schema(description = "创建时间", example = "[2026-04-01 00:00:00, 2026-04-30 23:59:59]") |
| 25 | 25 | @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND) | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/controller/admin/manager/sensitiveword/vo/ImSensitiveWordSaveReqVO.java
| ... | ... | @@ -18,9 +18,8 @@ public class ImSensitiveWordSaveReqVO { |
| 18 | 18 | @Size(max = 64, message = "敏感词长度不能超过 64") |
| 19 | 19 | private String word; |
| 20 | 20 | |
| 21 | - @Schema(description = "状态,参见 CommonStatusEnum 枚举类(0 启用 / 1 禁用)", | |
| 22 | - requiredMode = Schema.RequiredMode.REQUIRED, example = "0") | |
| 21 | + @Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0") | |
| 23 | 22 | @NotNull(message = "状态不能为空") |
| 24 | - private Integer status; | |
| 23 | + private Integer status; // 参见 CommonStatusEnum 枚举类(0 启用 / 1 禁用) | |
| 25 | 24 | |
| 26 | 25 | } | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/group/ImGroupMemberMapper.java
| ... | ... | @@ -90,7 +90,6 @@ public interface ImGroupMemberMapper extends BaseMapperX<ImGroupMemberDO> { |
| 90 | 90 | if (CollUtil.isEmpty(groupIds)) { |
| 91 | 91 | return Collections.emptyMap(); |
| 92 | 92 | } |
| 93 | - // TODO @AI:使用 mybatis plus join,可以简化这个操作么? | |
| 94 | 93 | List<Map<String, Object>> rows = selectMaps(Wrappers.<ImGroupMemberDO>query() |
| 95 | 94 | .select("group_id AS groupId", "COUNT(*) AS cnt") |
| 96 | 95 | .in("group_id", groupIds) | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/dal/mysql/sensitiveword/ImSensitiveWordMapper.java
| ... | ... | @@ -6,7 +6,10 @@ import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX; |
| 6 | 6 | import cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo.ImSensitiveWordPageReqVO; |
| 7 | 7 | import cn.iocoder.yudao.module.im.dal.dataobject.sensitiveword.ImSensitiveWordDO; |
| 8 | 8 | import org.apache.ibatis.annotations.Mapper; |
| 9 | +import org.apache.ibatis.annotations.Param; | |
| 10 | +import org.apache.ibatis.annotations.Select; | |
| 9 | 11 | |
| 12 | +import java.time.LocalDateTime; | |
| 10 | 13 | import java.util.List; |
| 11 | 14 | |
| 12 | 15 | /** |
| ... | ... | @@ -35,4 +38,7 @@ public interface ImSensitiveWordMapper extends BaseMapperX<ImSensitiveWordDO> { |
| 35 | 38 | .orderByDesc(ImSensitiveWordDO::getId)); |
| 36 | 39 | } |
| 37 | 40 | |
| 41 | + @Select("SELECT MAX(update_time) FROM im_sensitive_word") | |
| 42 | + LocalDateTime selectMaxUpdateTime(@Param("tenantId") Long tenantId); | |
| 43 | + | |
| 38 | 44 | } | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/sensitiveword/ImSensitiveWordConfiguration.java deleted
| 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 deleted
| 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
| ... | ... | @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.im.service.friend; |
| 3 | 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | 4 | import cn.iocoder.yudao.module.im.controller.admin.friend.vo.ImFriendUpdateReqVO; |
| 5 | 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 | 6 | import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; |
| 8 | 7 | |
| 9 | 8 | import java.util.Collection; |
| ... | ... | @@ -90,8 +89,8 @@ public interface ImFriendService { |
| 90 | 89 | // ==================== 管理后台 ==================== |
| 91 | 90 | |
| 92 | 91 | /** |
| 93 | - * 【管理后台】分页查询好友关系(已填充用户 / 好友昵称) | |
| 92 | + * 【管理后台】分页查询好友关系 | |
| 94 | 93 | */ |
| 95 | - PageResult<ImFriendManagerRespVO> getFriendPage(ImFriendManagerPageReqVO reqVO); | |
| 94 | + PageResult<ImFriendDO> getFriendPage(ImFriendManagerPageReqVO reqVO); | |
| 96 | 95 | |
| 97 | 96 | } | ... | ... |
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; | |
| 4 | 3 | import cn.hutool.extra.spring.SpringUtil; |
| 5 | 4 | import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; |
| 6 | 5 | 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; | |
| 9 | 6 | import cn.iocoder.yudao.module.im.controller.admin.friend.vo.ImFriendUpdateReqVO; |
| 10 | 7 | 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; | |
| 12 | 8 | import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; |
| 13 | 9 | import cn.iocoder.yudao.module.im.dal.mysql.friend.ImFriendMapper; |
| 14 | 10 | import cn.iocoder.yudao.module.im.service.message.ImPrivateMessageService; |
| 15 | 11 | import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; |
| 16 | 12 | import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO; |
| 17 | 13 | import cn.iocoder.yudao.module.system.api.user.AdminUserApi; |
| 18 | -import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; | |
| 19 | 14 | import jakarta.annotation.Resource; |
| 20 | 15 | import lombok.extern.slf4j.Slf4j; |
| 21 | 16 | import org.springframework.cache.annotation.CacheEvict; |
| ... | ... | @@ -27,10 +22,11 @@ import org.springframework.transaction.annotation.Transactional; |
| 27 | 22 | import org.springframework.validation.annotation.Validated; |
| 28 | 23 | |
| 29 | 24 | import java.time.LocalDateTime; |
| 30 | -import java.util.*; | |
| 25 | +import java.util.Collection; | |
| 26 | +import java.util.List; | |
| 27 | +import java.util.Objects; | |
| 31 | 28 | |
| 32 | 29 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 33 | -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; | |
| 34 | 30 | import static cn.iocoder.yudao.module.im.dal.redis.RedisKeyConstants.FRIEND; |
| 35 | 31 | import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FRIEND_ADD_SELF; |
| 36 | 32 | import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FRIEND_NOT_FRIEND; |
| ... | ... | @@ -189,26 +185,9 @@ public class ImFriendServiceImpl implements ImFriendService { |
| 189 | 185 | |
| 190 | 186 | // ==================== 管理后台 ==================== |
| 191 | 187 | |
| 192 | - // TODO @AI:拿到 controller 拼接数据,简化 service; | |
| 193 | 188 | @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 | - }); | |
| 189 | + public PageResult<ImFriendDO> getFriendPage(ImFriendManagerPageReqVO reqVO) { | |
| 190 | + return imFriendMapper.selectPage(reqVO); | |
| 212 | 191 | } |
| 213 | 192 | |
| 214 | 193 | } | ... | ... |
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; | |
| 5 | 4 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; |
| 6 | 5 | import jakarta.validation.Valid; |
| 7 | 6 | |
| ... | ... | @@ -156,14 +155,4 @@ public interface ImGroupMemberService { |
| 156 | 155 | */ |
| 157 | 156 | Map<Long, Long> getActiveMemberCountMap(Collection<Long> groupIds); |
| 158 | 157 | |
| 159 | - // ==================== 管理后台 ==================== | |
| 160 | - | |
| 161 | - /** | |
| 162 | - * 【管理后台】获取群成员列表 | |
| 163 | - * | |
| 164 | - * @param groupId 群编号 | |
| 165 | - * @return 群成员列表 | |
| 166 | - */ | |
| 167 | - List<ImGroupMemberManagerRespVO> getGroupMemberList(Long groupId); | |
| 168 | - | |
| 169 | 158 | } | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberServiceImpl.java
| ... | ... | @@ -2,16 +2,12 @@ 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; | |
| 6 | 5 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 7 | 6 | 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; | |
| 9 | 7 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; |
| 10 | 8 | import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMemberMapper; |
| 11 | 9 | import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; |
| 12 | 10 | 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; | |
| 15 | 11 | import jakarta.annotation.Resource; |
| 16 | 12 | import lombok.extern.slf4j.Slf4j; |
| 17 | 13 | import org.springframework.cache.annotation.CacheEvict; |
| ... | ... | @@ -44,9 +40,6 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService { |
| 44 | 40 | @Resource |
| 45 | 41 | private ImWebSocketService webSocketService; |
| 46 | 42 | |
| 47 | - @Resource | |
| 48 | - private AdminUserApi adminUserApi; | |
| 49 | - | |
| 50 | 43 | @Override |
| 51 | 44 | public ImGroupMemberDO getGroupMember(Long id) { |
| 52 | 45 | return groupMemberMapper.selectById(id); |
| ... | ... | @@ -222,25 +215,4 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService { |
| 222 | 215 | return groupMemberMapper.selectCountMapByGroupIdsAndStatus(groupIds, CommonStatusEnum.ENABLE.getStatus()); |
| 223 | 216 | } |
| 224 | 217 | |
| 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 | - | |
| 246 | 218 | } | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupService.java
| ... | ... | @@ -7,7 +7,6 @@ import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMember |
| 7 | 7 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberRemoveReqVO; |
| 8 | 8 | import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerBanReqVO; |
| 9 | 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; | |
| 11 | 10 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; |
| 12 | 11 | import jakarta.validation.Valid; |
| 13 | 12 | |
| ... | ... | @@ -138,15 +137,13 @@ public interface ImGroupService { |
| 138 | 137 | |
| 139 | 138 | // ==================== 管理后台 ==================== |
| 140 | 139 | |
| 141 | - // TODO DONE @AI:是不是 controller 拆分哈;(getGroupPage 留在 ImGroupService;getGroupMemberList 已拆到 ImGroupMemberService + ImGroupMemberManagerController) | |
| 142 | - // TODO @AI:不对;应该 VO 放在 controller 拼接 | |
| 143 | 140 | /** |
| 144 | 141 | * 【管理后台】分页查询群列表 |
| 145 | 142 | * |
| 146 | 143 | * @param pageReqVO 分页查询条件 |
| 147 | 144 | * @return 群分页列表 |
| 148 | 145 | */ |
| 149 | - PageResult<ImGroupManagerRespVO> getGroupPage(ImGroupManagerPageReqVO pageReqVO); | |
| 146 | + PageResult<ImGroupDO> getGroupPage(ImGroupManagerPageReqVO pageReqVO); | |
| 150 | 147 | |
| 151 | 148 | /** |
| 152 | 149 | * 【管理后台】封禁群 | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImpl.java
| ... | ... | @@ -8,7 +8,6 @@ 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 | 10 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 11 | -import cn.iocoder.yudao.framework.common.util.collection.MapUtils; | |
| 12 | 11 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 13 | 12 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupCreateReqVO; |
| 14 | 13 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupUpdateReqVO; |
| ... | ... | @@ -16,7 +15,6 @@ import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMember |
| 16 | 15 | import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberRemoveReqVO; |
| 17 | 16 | import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerBanReqVO; |
| 18 | 17 | 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; | |
| 20 | 18 | import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO; |
| 21 | 19 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; |
| 22 | 20 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; |
| ... | ... | @@ -288,24 +286,8 @@ public class ImGroupServiceImpl implements ImGroupService { |
| 288 | 286 | // ==================== 管理后台 ==================== |
| 289 | 287 | |
| 290 | 288 | @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 | - }); | |
| 289 | + public PageResult<ImGroupDO> getGroupPage(ImGroupManagerPageReqVO pageReqVO) { | |
| 290 | + return groupMapper.selectPage(pageReqVO); | |
| 309 | 291 | } |
| 310 | 292 | |
| 311 | 293 | @Override |
| ... | ... | @@ -328,7 +310,6 @@ public class ImGroupServiceImpl implements ImGroupService { |
| 328 | 310 | throw exception(GROUP_NOT_EXISTS); |
| 329 | 311 | } |
| 330 | 312 | // 2. 解封(保留 bannedReason / bannedTime 作为历史记录) |
| 331 | - // TODO DONE @AI:不要使用 mybatis plus 的方法,直接操作。另外,感觉封禁时间、封禁原因,没必要置空,留着问题不大。 | |
| 332 | 313 | groupMapper.updateById(new ImGroupDO().setId(id).setBanned(false)); |
| 333 | 314 | } |
| 334 | 315 | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageService.java
| ... | ... | @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.im.service.message; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | 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; | |
| 6 | 5 | import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageListReqVO; |
| 7 | 6 | import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageSendReqVO; |
| 8 | 7 | import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; |
| ... | ... | @@ -118,9 +117,9 @@ public interface ImGroupMessageService { |
| 118 | 117 | // ==================== 管理后台 ==================== |
| 119 | 118 | |
| 120 | 119 | /** |
| 121 | - * 【管理后台】分页查询群聊消息(已填充群名称、发送人昵称) | |
| 120 | + * 【管理后台】分页查询群聊消息 | |
| 122 | 121 | */ |
| 123 | - PageResult<ImGroupMessageManagerRespVO> getGroupMessagePage(ImGroupMessageManagerPageReqVO reqVO); | |
| 122 | + PageResult<ImGroupMessageDO> getGroupMessagePage(ImGroupMessageManagerPageReqVO reqVO); | |
| 124 | 123 | |
| 125 | 124 | /** |
| 126 | 125 | * 【管理后台】获取群聊消息详情 | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java
| ... | ... | @@ -7,14 +7,11 @@ 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 | 9 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 10 | -import cn.iocoder.yudao.framework.common.util.collection.MapUtils; | |
| 11 | 10 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; |
| 12 | 11 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 13 | 12 | 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; | |
| 15 | 13 | import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageListReqVO; |
| 16 | 14 | import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageSendReqVO; |
| 17 | -import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO; | |
| 18 | 15 | import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO; |
| 19 | 16 | import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO; |
| 20 | 17 | import cn.iocoder.yudao.module.im.dal.mysql.message.ImGroupMessageMapper; |
| ... | ... | @@ -28,8 +25,6 @@ import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService; |
| 28 | 25 | import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; |
| 29 | 26 | import cn.iocoder.yudao.module.im.service.websocket.dto.ImGroupMessageDTO; |
| 30 | 27 | 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; | |
| 33 | 28 | import jakarta.annotation.Resource; |
| 34 | 29 | import lombok.extern.slf4j.Slf4j; |
| 35 | 30 | import org.springframework.scheduling.annotation.Async; |
| ... | ... | @@ -77,9 +72,6 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { |
| 77 | 72 | @Resource |
| 78 | 73 | private ImWebSocketService imWebSocketService; |
| 79 | 74 | |
| 80 | - @Resource | |
| 81 | - private AdminUserApi adminUserApi; | |
| 82 | - | |
| 83 | 75 | @Override |
| 84 | 76 | public ImGroupMessageDO sendGroupMessage(Long senderId, ImGroupMessageSendReqVO reqVO) { |
| 85 | 77 | // 1.1 幂等校验:根据 senderId + clientMessageId 查重 |
| ... | ... | @@ -554,26 +546,9 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService { |
| 554 | 546 | |
| 555 | 547 | // ==================== 管理后台 ==================== |
| 556 | 548 | |
| 557 | - // TODO @AI:这些挪到 controller 拼接 VO | |
| 558 | 549 | @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 | - }); | |
| 550 | + public PageResult<ImGroupMessageDO> getGroupMessagePage(ImGroupMessageManagerPageReqVO reqVO) { | |
| 551 | + return groupMessageMapper.selectPage(reqVO); | |
| 577 | 552 | } |
| 578 | 553 | |
| 579 | 554 | @Override | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageService.java
| ... | ... | @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.im.service.message; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 4 | 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; | |
| 6 | 5 | import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageListReqVO; |
| 7 | 6 | import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageSendReqVO; |
| 8 | 7 | import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; |
| ... | ... | @@ -91,9 +90,9 @@ public interface ImPrivateMessageService { |
| 91 | 90 | // ==================== 管理后台 ==================== |
| 92 | 91 | |
| 93 | 92 | /** |
| 94 | - * 【管理后台】分页查询私聊消息(已填充发送人 / 接收人昵称) | |
| 93 | + * 【管理后台】分页查询私聊消息 | |
| 95 | 94 | */ |
| 96 | - PageResult<ImPrivateMessageManagerRespVO> getPrivateMessagePage(ImPrivateMessageManagerPageReqVO reqVO); | |
| 95 | + PageResult<ImPrivateMessageDO> getPrivateMessagePage(ImPrivateMessageManagerPageReqVO reqVO); | |
| 97 | 96 | |
| 98 | 97 | /** |
| 99 | 98 | * 【管理后台】获取私聊消息详情 | ... | ... |
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; | |
| 4 | 3 | import cn.hutool.core.lang.Assert; |
| 5 | 4 | import cn.hutool.core.util.IdUtil; |
| 6 | 5 | import cn.hutool.core.util.ObjUtil; |
| 7 | 6 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 8 | -import cn.iocoder.yudao.framework.common.util.collection.MapUtils; | |
| 9 | 7 | import cn.iocoder.yudao.framework.common.util.json.JsonUtils; |
| 10 | 8 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 11 | 9 | 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; | |
| 13 | 10 | import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageListReqVO; |
| 14 | 11 | import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageSendReqVO; |
| 15 | 12 | import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO; |
| ... | ... | @@ -21,8 +18,6 @@ import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService; |
| 21 | 18 | import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService; |
| 22 | 19 | import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO; |
| 23 | 20 | 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; | |
| 26 | 21 | import jakarta.annotation.Resource; |
| 27 | 22 | import lombok.extern.slf4j.Slf4j; |
| 28 | 23 | import org.springframework.stereotype.Service; |
| ... | ... | @@ -30,13 +25,9 @@ import org.springframework.transaction.annotation.Transactional; |
| 30 | 25 | import org.springframework.validation.annotation.Validated; |
| 31 | 26 | |
| 32 | 27 | import java.time.LocalDateTime; |
| 33 | -import java.util.HashSet; | |
| 34 | 28 | import java.util.List; |
| 35 | -import java.util.Map; | |
| 36 | -import java.util.Set; | |
| 37 | 29 | |
| 38 | 30 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 39 | -import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; | |
| 40 | 31 | import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*; |
| 41 | 32 | import static cn.iocoder.yudao.module.im.enums.ImCommonConstants.*; |
| 42 | 33 | |
| ... | ... | @@ -61,9 +52,6 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService { |
| 61 | 52 | @Resource |
| 62 | 53 | private ImWebSocketService imWebSocketService; |
| 63 | 54 | |
| 64 | - @Resource | |
| 65 | - private AdminUserApi adminUserApi; | |
| 66 | - | |
| 67 | 55 | @Override |
| 68 | 56 | public ImPrivateMessageDO sendPrivateMessage(Long senderId, ImPrivateMessageSendReqVO reqVO) { |
| 69 | 57 | // 1.1 幂等校验:根据 senderId + clientMessageId 查重 |
| ... | ... | @@ -183,26 +171,10 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService { |
| 183 | 171 | |
| 184 | 172 | // ==================== 管理后台 ==================== |
| 185 | 173 | |
| 186 | - // TODO @AI:这些挪到 controller 拼接 VO | |
| 174 | + // DONE @AI:这些挪到 controller 拼接 VO | |
| 187 | 175 | @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 | - }); | |
| 176 | + public PageResult<ImPrivateMessageDO> getPrivateMessagePage(ImPrivateMessageManagerPageReqVO reqVO) { | |
| 177 | + return privateMessageMapper.selectPage(reqVO); | |
| 206 | 178 | } |
| 207 | 179 | |
| 208 | 180 | @Override | ... | ... |
yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/sensitiveword/ImSensitiveWordServiceImpl.java
| ... | ... | @@ -3,21 +3,35 @@ package cn.iocoder.yudao.module.im.service.sensitiveword; |
| 3 | 3 | import cn.hutool.core.collection.CollUtil; |
| 4 | 4 | import cn.hutool.core.util.ObjUtil; |
| 5 | 5 | import cn.hutool.core.util.StrUtil; |
| 6 | +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | |
| 6 | 7 | import cn.iocoder.yudao.framework.common.pojo.PageResult; |
| 8 | +import cn.iocoder.yudao.framework.common.util.cache.CacheUtils; | |
| 7 | 9 | import cn.iocoder.yudao.framework.common.util.object.BeanUtils; |
| 10 | +import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; | |
| 11 | +import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; | |
| 8 | 12 | import cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo.ImSensitiveWordPageReqVO; |
| 9 | 13 | import cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo.ImSensitiveWordSaveReqVO; |
| 10 | 14 | import cn.iocoder.yudao.module.im.dal.dataobject.sensitiveword.ImSensitiveWordDO; |
| 11 | 15 | import cn.iocoder.yudao.module.im.dal.mysql.sensitiveword.ImSensitiveWordMapper; |
| 12 | 16 | import com.github.houbb.sensitive.word.bs.SensitiveWordBs; |
| 17 | +import com.google.common.cache.CacheLoader; | |
| 18 | +import com.google.common.cache.LoadingCache; | |
| 19 | +import com.google.common.util.concurrent.Futures; | |
| 20 | +import com.google.common.util.concurrent.ListenableFuture; | |
| 13 | 21 | import jakarta.annotation.Resource; |
| 22 | +import lombok.AllArgsConstructor; | |
| 23 | +import lombok.Data; | |
| 14 | 24 | import lombok.extern.slf4j.Slf4j; |
| 15 | 25 | import org.springframework.stereotype.Service; |
| 16 | 26 | import org.springframework.validation.annotation.Validated; |
| 17 | 27 | |
| 28 | +import java.time.Duration; | |
| 29 | +import java.time.LocalDateTime; | |
| 18 | 30 | import java.util.List; |
| 31 | +import java.util.Objects; | |
| 19 | 32 | |
| 20 | 33 | import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; |
| 34 | +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList; | |
| 21 | 35 | import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.MESSAGE_SENSITIVE_WORD_BLOCKED; |
| 22 | 36 | import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.SENSITIVE_WORD_DUPLICATED; |
| 23 | 37 | import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.SENSITIVE_WORD_NOT_EXISTS; |
| ... | ... | @@ -37,34 +51,92 @@ public class ImSensitiveWordServiceImpl implements ImSensitiveWordService { |
| 37 | 51 | @Resource |
| 38 | 52 | private ImSensitiveWordMapper sensitiveWordMapper; |
| 39 | 53 | |
| 40 | - // TODO @AI:当前节点,立即刷新。其他节点,通过 job 扫描最后更新时间(只要有不同于目前最后一条记录的最大更新时间,就重新构建对应的树)。 | |
| 41 | 54 | /** |
| 42 | - * houbb 敏感词检测器,词库由 ImSensitiveWordDeny 在 init 时回调加载 | |
| 55 | + * 缓存条目 | |
| 43 | 56 | */ |
| 44 | - @Resource | |
| 45 | - private SensitiveWordBs sensitiveWordBs; | |
| 57 | + @Data | |
| 58 | + @AllArgsConstructor | |
| 59 | + private static class SensitiveWordBsCache { | |
| 60 | + | |
| 61 | + /** | |
| 62 | + * 敏感词检测器 | |
| 63 | + */ | |
| 64 | + private SensitiveWordBs bs; | |
| 65 | + /** | |
| 66 | + * 构建本实例时数据库里的 max(update_time),作为下次刷新比对的基线 | |
| 67 | + */ | |
| 68 | + private LocalDateTime maxUpdateTime; | |
| 69 | + | |
| 70 | + } | |
| 46 | 71 | |
| 47 | - // TODO @AI:你看看现有仓库里,是否有类似这种的。按照最后更新判断,是否需要刷新本地缓存。 | |
| 48 | 72 | /** |
| 49 | - * 触发 houbb 词库重新加载(重建 trie) | |
| 73 | + * 租户 → SensitiveWordBs 实例的本地缓存 | |
| 50 | 74 | * <p> |
| 51 | - * 加 synchronized 是因为 SensitiveWordBs.init() 内部对 wordData 做就地写入, | |
| 52 | - * 若与并发的 contains() 重叠会读到半构建的 trie。互斥后短暂阻塞 contains 即可保证可见性。 | |
| 75 | + * 每分钟触发一次异步 reload,先读 max(update_time),没变就复用旧实例(避免 trie 重建),变了才重新读词库 + 重建。 | |
| 76 | + * 单实例 CRUD 后另外通过 {@link #invalidateSensitiveWordBsCaches()} 立即让本机失效,多实例靠定时刷新最长 1 分钟内收敛。 | |
| 77 | + */ | |
| 78 | + @SuppressWarnings({"Convert2Diamond", "NullableProblems"}) | |
| 79 | + private final LoadingCache<Long, SensitiveWordBsCache> sensitiveWordBsCaches = CacheUtils.buildAsyncReloadingCache( | |
| 80 | + Duration.ofMinutes(1L), // 1 分钟过期 | |
| 81 | + new CacheLoader<Long, SensitiveWordBsCache>() { | |
| 82 | + | |
| 83 | + @Override | |
| 84 | + public SensitiveWordBsCache load(Long tenantId) { | |
| 85 | + return loadFresh(tenantId); | |
| 86 | + } | |
| 87 | + | |
| 88 | + @Override | |
| 89 | + public ListenableFuture<SensitiveWordBsCache> reload(Long tenantId, SensitiveWordBsCache oldValue) { | |
| 90 | + // 先比对 max(update_time);没变 → 复用旧实例,避免无谓地重建 trie | |
| 91 | + LocalDateTime currentMax = sensitiveWordMapper.selectMaxUpdateTime(tenantId); | |
| 92 | + if (Objects.equals(oldValue.getMaxUpdateTime(), currentMax)) { | |
| 93 | + return Futures.immediateFuture(oldValue); | |
| 94 | + } | |
| 95 | + // 变了 → 重新读词库并重建 trie | |
| 96 | + return Futures.immediateFuture(loadFresh(tenantId)); | |
| 97 | + } | |
| 98 | + | |
| 99 | + }); | |
| 100 | + | |
| 101 | + private SensitiveWordBsCache loadFresh(Long tenantId) { | |
| 102 | + return TenantUtils.execute(tenantId, () -> { | |
| 103 | + // 先取基线时间再读词库:反过来在两次查询之间出现的新插入会被漏感知 | |
| 104 | + LocalDateTime maxUpdateTime = sensitiveWordMapper.selectMaxUpdateTime(tenantId); | |
| 105 | + List<ImSensitiveWordDO> words = sensitiveWordMapper.selectListByStatus(CommonStatusEnum.ENABLE.getStatus()); | |
| 106 | + // 构建敏感词检测器 | |
| 107 | + SensitiveWordBs bs = SensitiveWordBs.newInstance() | |
| 108 | + .wordDeny(() -> convertList(words, ImSensitiveWordDO::getWord)) | |
| 109 | + .ignoreCase(true) | |
| 110 | + .ignoreWidth(true) // 忽略全/半角 | |
| 111 | + .ignoreNumStyle(true) // 忽略数字风格(中文/阿拉伯) | |
| 112 | + .ignoreChineseStyle(true) // 忽略繁简体 | |
| 113 | + .enableWordCheck(true) | |
| 114 | + .init(); | |
| 115 | + return new SensitiveWordBsCache(bs, maxUpdateTime); | |
| 116 | + }); | |
| 117 | + } | |
| 118 | + | |
| 119 | + /** | |
| 120 | + * 强制让敏感词缓存失效,下次访问按最新 DB 重建 | |
| 53 | 121 | * <p> |
| 54 | - * init() 本身幂等,单实例可以直接调;多实例部署时需配合 Redis pub/sub 广播让各实例 init。 | |
| 122 | + * 有租户上下文:仅失效该租户。无租户上下文(如系统级 / 跨租户清理):兜底失效所有租户。 | |
| 55 | 123 | */ |
| 56 | - private synchronized void reloadSensitiveWords() { | |
| 57 | - sensitiveWordBs.init(); | |
| 58 | - log.info("[reloadSensitiveWords][敏感词库已 reload]"); | |
| 124 | + private void invalidateSensitiveWordBsCaches() { | |
| 125 | + Long tenantId = TenantContextHolder.getTenantId(); | |
| 126 | + if (tenantId != null) { | |
| 127 | + sensitiveWordBsCaches.invalidate(tenantId); | |
| 128 | + return; | |
| 129 | + } | |
| 130 | + sensitiveWordBsCaches.invalidateAll(); | |
| 59 | 131 | } |
| 60 | 132 | |
| 61 | - // TODO @AI:需要按照租户来。 | |
| 62 | 133 | @Override |
| 63 | 134 | public void validateText(String text) { |
| 64 | 135 | if (StrUtil.isBlank(text)) { |
| 65 | 136 | return; |
| 66 | 137 | } |
| 67 | - if (sensitiveWordBs.contains(text)) { | |
| 138 | + SensitiveWordBs bs = sensitiveWordBsCaches.getUnchecked(TenantContextHolder.getRequiredTenantId()).getBs(); | |
| 139 | + if (bs.contains(text)) { | |
| 68 | 140 | throw exception(MESSAGE_SENSITIVE_WORD_BLOCKED); |
| 69 | 141 | } |
| 70 | 142 | } |
| ... | ... | @@ -89,9 +161,8 @@ public class ImSensitiveWordServiceImpl implements ImSensitiveWordService { |
| 89 | 161 | // 2.1 入库 |
| 90 | 162 | ImSensitiveWordDO word = BeanUtils.toBean(reqVO, ImSensitiveWordDO.class); |
| 91 | 163 | sensitiveWordMapper.insert(word); |
| 92 | - // TODO @AI:houbb 这个注释有点奇怪! | |
| 93 | - // 2.2 触发 houbb 词库 reload | |
| 94 | - reloadSensitiveWords(); | |
| 164 | + // 2.2 强制失效本机缓存(多实例靠定时刷新收敛) | |
| 165 | + invalidateSensitiveWordBsCaches(); | |
| 95 | 166 | return word.getId(); |
| 96 | 167 | } |
| 97 | 168 | |
| ... | ... | @@ -105,9 +176,8 @@ public class ImSensitiveWordServiceImpl implements ImSensitiveWordService { |
| 105 | 176 | // 2.1 更新 |
| 106 | 177 | ImSensitiveWordDO updateObj = BeanUtils.toBean(reqVO, ImSensitiveWordDO.class); |
| 107 | 178 | sensitiveWordMapper.updateById(updateObj); |
| 108 | - // 2.2 TODO @AI:houbb 这个注释有点奇怪! | |
| 109 | - // 2.2 触发 houbb 词库 reload | |
| 110 | - reloadSensitiveWords(); | |
| 179 | + // 2.2 强制失效本机缓存 | |
| 180 | + invalidateSensitiveWordBsCaches(); | |
| 111 | 181 | } |
| 112 | 182 | |
| 113 | 183 | @Override |
| ... | ... | @@ -117,8 +187,8 @@ public class ImSensitiveWordServiceImpl implements ImSensitiveWordService { |
| 117 | 187 | |
| 118 | 188 | // 2.1 删除 |
| 119 | 189 | sensitiveWordMapper.deleteById(id); |
| 120 | - // 2.2 触发 houbb 词库 reload | |
| 121 | - reloadSensitiveWords(); | |
| 190 | + // 2.2 强制失效本机缓存 | |
| 191 | + invalidateSensitiveWordBsCaches(); | |
| 122 | 192 | } |
| 123 | 193 | |
| 124 | 194 | |
| ... | ... | @@ -127,11 +197,10 @@ public class ImSensitiveWordServiceImpl implements ImSensitiveWordService { |
| 127 | 197 | if (CollUtil.isEmpty(ids)) { |
| 128 | 198 | return; |
| 129 | 199 | } |
| 130 | - | |
| 131 | 200 | // 1. 删除 |
| 132 | 201 | sensitiveWordMapper.deleteByIds(ids); |
| 133 | - // 触发 houbb 词库 reload | |
| 134 | - reloadSensitiveWords(); | |
| 202 | + // 2. 强制失效本机缓存 | |
| 203 | + invalidateSensitiveWordBsCaches(); | |
| 135 | 204 | } |
| 136 | 205 | |
| 137 | 206 | private void validateSensitiveWordExists(Long id) { |
| ... | ... | @@ -148,7 +217,6 @@ public class ImSensitiveWordServiceImpl implements ImSensitiveWordService { |
| 148 | 217 | if (exist == null) { |
| 149 | 218 | return; |
| 150 | 219 | } |
| 151 | - // TODO @AI:是不是 id 不用判断 | |
| 152 | 220 | if (id == null || ObjUtil.notEqual(exist.getId(), id)) { |
| 153 | 221 | throw exception(SENSITIVE_WORD_DUPLICATED, word); |
| 154 | 222 | } | ... | ... |