{
.orderByDesc(ImSensitiveWordDO::getId));
}
+ @Select("SELECT MAX(update_time) FROM im_sensitive_word")
+ LocalDateTime selectMaxUpdateTime(@Param("tenantId") Long tenantId);
+
}
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/sensitiveword/ImSensitiveWordConfiguration.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/sensitiveword/ImSensitiveWordConfiguration.java
deleted file mode 100644
index 44c51c7..0000000
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/sensitiveword/ImSensitiveWordConfiguration.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package cn.iocoder.yudao.module.im.framework.sensitiveword;
-
-import com.github.houbb.sensitive.word.bs.SensitiveWordBs;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-/**
- * IM 敏感词检测配置
- *
- * @author 芋道源码
- */
-@Configuration
-public class ImSensitiveWordConfiguration {
-
- // TODO @AI:可能需要按需创建;因为多租户。
- /**
- * 装配 {@link SensitiveWordBs}:
- * 词库来源 = {@link ImSensitiveWordDeny}(DB 里启用的敏感词),并开启常用规范化能力。
- */
- @Bean
- public SensitiveWordBs sensitiveWordBs(ImSensitiveWordDeny imSensitiveWordDeny) {
- return SensitiveWordBs.newInstance()
- .wordDeny(imSensitiveWordDeny)
- .ignoreCase(true)
- .ignoreWidth(true) // 忽略全/半角
- .ignoreNumStyle(true) // 忽略数字风格(中文/阿拉伯)
- .ignoreChineseStyle(true) // 忽略繁简体
- .enableWordCheck(true)
- .init();
- }
-
-}
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/sensitiveword/ImSensitiveWordDeny.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/sensitiveword/ImSensitiveWordDeny.java
deleted file mode 100644
index 34e8b7c..0000000
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/sensitiveword/ImSensitiveWordDeny.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package cn.iocoder.yudao.module.im.framework.sensitiveword;
-
-import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
-import cn.iocoder.yudao.module.im.dal.dataobject.sensitiveword.ImSensitiveWordDO;
-import cn.iocoder.yudao.module.im.dal.mysql.sensitiveword.ImSensitiveWordMapper;
-import com.github.houbb.sensitive.word.api.IWordDeny;
-import jakarta.annotation.Resource;
-import org.springframework.stereotype.Component;
-
-import java.util.List;
-
-import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
-
-// TODO @AI:是不是可以,直接塞入一个数组。而不是这里查询???
-/**
- * 自定义敏感词数据来源:从 im_sensitive_word 表加载启用状态的敏感词
- *
- * 由 {@link com.github.houbb.sensitive.word.bs.SensitiveWordBs} 在 init() 时回调,
- * 因此 CRUD 完成后调用 sensitiveWordBs.init() 即可实现热更新。
- *
- * @author 芋道源码
- */
-@Component
-public class ImSensitiveWordDeny implements IWordDeny {
-
- @Resource
- private ImSensitiveWordMapper sensitiveWordMapper;
-
- @Override
- public List deny() {
- return convertList(
- sensitiveWordMapper.selectListByStatus(CommonStatusEnum.ENABLE.getStatus()),
- ImSensitiveWordDO::getWord);
- }
-
-}
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendService.java
index a14fe0d..bdedc73 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendService.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendService.java
@@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.im.service.friend;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.im.controller.admin.friend.vo.ImFriendUpdateReqVO;
import cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo.ImFriendManagerPageReqVO;
-import cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo.ImFriendManagerRespVO;
import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO;
import java.util.Collection;
@@ -90,8 +89,8 @@ public interface ImFriendService {
// ==================== 管理后台 ====================
/**
- * 【管理后台】分页查询好友关系(已填充用户 / 好友昵称)
+ * 【管理后台】分页查询好友关系
*/
- PageResult getFriendPage(ImFriendManagerPageReqVO reqVO);
+ PageResult getFriendPage(ImFriendManagerPageReqVO reqVO);
}
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImpl.java
index 14e2091..1e503cd 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImpl.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImpl.java
@@ -1,21 +1,16 @@
package cn.iocoder.yudao.module.im.service.friend;
-import cn.hutool.core.collection.CollUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
-import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.im.controller.admin.friend.vo.ImFriendUpdateReqVO;
import cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo.ImFriendManagerPageReqVO;
-import cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo.ImFriendManagerRespVO;
import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO;
import cn.iocoder.yudao.module.im.dal.mysql.friend.ImFriendMapper;
import cn.iocoder.yudao.module.im.service.message.ImPrivateMessageService;
import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService;
import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
-import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
@@ -27,10 +22,11 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.time.LocalDateTime;
-import java.util.*;
+import java.util.Collection;
+import java.util.List;
+import java.util.Objects;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.module.im.dal.redis.RedisKeyConstants.FRIEND;
import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FRIEND_ADD_SELF;
import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FRIEND_NOT_FRIEND;
@@ -189,26 +185,9 @@ public class ImFriendServiceImpl implements ImFriendService {
// ==================== 管理后台 ====================
- // TODO @AI:拿到 controller 拼接数据,简化 service;
@Override
- public PageResult getFriendPage(ImFriendManagerPageReqVO reqVO) {
- // 1. 分页查询
- PageResult pageResult = imFriendMapper.selectPage(reqVO);
- if (CollUtil.isEmpty(pageResult.getList())) {
- return PageResult.empty(pageResult.getTotal());
- }
-
- // 2. 一次性批量查询用户 + 好友的昵称
- Set userIds = new HashSet<>();
- userIds.addAll(convertSet(pageResult.getList(), ImFriendDO::getUserId));
- userIds.addAll(convertSet(pageResult.getList(), ImFriendDO::getFriendUserId));
- Map userMap = adminUserApi.getUserMap(userIds);
-
- // 3. 转换为 VO,填充昵称
- return BeanUtils.toBean(pageResult, ImFriendManagerRespVO.class, vo -> {
- MapUtils.findAndThen(userMap, vo.getUserId(), user -> vo.setUserNickname(user.getNickname()));
- MapUtils.findAndThen(userMap, vo.getFriendUserId(), user -> vo.setFriendNickname(user.getNickname()));
- });
+ public PageResult getFriendPage(ImFriendManagerPageReqVO reqVO) {
+ return imFriendMapper.selectPage(reqVO);
}
}
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberService.java
index 21d46ed..cfb0a4d 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberService.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberService.java
@@ -1,7 +1,6 @@
package cn.iocoder.yudao.module.im.service.group;
import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberUpdateReqVO;
-import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.member.ImGroupMemberManagerRespVO;
import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
import jakarta.validation.Valid;
@@ -156,14 +155,4 @@ public interface ImGroupMemberService {
*/
Map getActiveMemberCountMap(Collection groupIds);
- // ==================== 管理后台 ====================
-
- /**
- * 【管理后台】获取群成员列表
- *
- * @param groupId 群编号
- * @return 群成员列表
- */
- List getGroupMemberList(Long groupId);
-
}
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberServiceImpl.java
index b172ebb..b4697e6 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberServiceImpl.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberServiceImpl.java
@@ -2,16 +2,12 @@ package cn.iocoder.yudao.module.im.service.group;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
-import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberUpdateReqVO;
-import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.member.ImGroupMemberManagerRespVO;
import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMemberMapper;
import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService;
import cn.iocoder.yudao.module.im.service.websocket.dto.ImGroupMessageDTO;
-import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
-import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
@@ -44,9 +40,6 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService {
@Resource
private ImWebSocketService webSocketService;
- @Resource
- private AdminUserApi adminUserApi;
-
@Override
public ImGroupMemberDO getGroupMember(Long id) {
return groupMemberMapper.selectById(id);
@@ -222,25 +215,4 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService {
return groupMemberMapper.selectCountMapByGroupIdsAndStatus(groupIds, CommonStatusEnum.ENABLE.getStatus());
}
- // ==================== 管理后台 ====================
-
- // TODO @AI:这个拼接的逻辑,拿到 controller,这里只返回 list do;
- @Override
- public List getGroupMemberList(Long groupId) {
- // 1. 查询有效群成员
- List members = getActiveGroupMemberListByGroupId(groupId);
- if (CollUtil.isEmpty(members)) {
- return Collections.emptyList();
- }
-
- // 2. 批量查询用户信息
- Set userIds = convertSet(members, ImGroupMemberDO::getUserId);
- Map userMap = adminUserApi.getUserMap(userIds);
-
- // 3. 转换为 VO,填充昵称、头像
- return BeanUtils.toBean(members, ImGroupMemberManagerRespVO.class, vo ->
- MapUtils.findAndThen(userMap, vo.getUserId(), user ->
- vo.setNickname(user.getNickname()).setAvatar(user.getAvatar())));
- }
-
}
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupService.java
index b40768b..281cb64 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupService.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupService.java
@@ -7,7 +7,6 @@ import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMember
import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberRemoveReqVO;
import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerBanReqVO;
import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerPageReqVO;
-import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerRespVO;
import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
import jakarta.validation.Valid;
@@ -138,15 +137,13 @@ public interface ImGroupService {
// ==================== 管理后台 ====================
- // TODO DONE @AI:是不是 controller 拆分哈;(getGroupPage 留在 ImGroupService;getGroupMemberList 已拆到 ImGroupMemberService + ImGroupMemberManagerController)
- // TODO @AI:不对;应该 VO 放在 controller 拼接
/**
* 【管理后台】分页查询群列表
*
* @param pageReqVO 分页查询条件
* @return 群分页列表
*/
- PageResult getGroupPage(ImGroupManagerPageReqVO pageReqVO);
+ PageResult getGroupPage(ImGroupManagerPageReqVO pageReqVO);
/**
* 【管理后台】封禁群
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImpl.java
index 57c9496..ea8e0e1 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImpl.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImpl.java
@@ -8,7 +8,6 @@ import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupCreateReqVO;
import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupUpdateReqVO;
@@ -16,7 +15,6 @@ import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMember
import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberRemoveReqVO;
import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerBanReqVO;
import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerPageReqVO;
-import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerRespVO;
import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO;
import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
@@ -288,24 +286,8 @@ public class ImGroupServiceImpl implements ImGroupService {
// ==================== 管理后台 ====================
@Override
- public PageResult getGroupPage(ImGroupManagerPageReqVO pageReqVO) {
- // 1. 分页查询群
- PageResult pageResult = groupMapper.selectPage(pageReqVO);
- if (CollUtil.isEmpty(pageResult.getList())) {
- return PageResult.empty(pageResult.getTotal());
- }
-
- // 2.1 批量查询群主昵称
- Set ownerUserIds = convertSet(pageResult.getList(), ImGroupDO::getOwnerUserId);
- Map userMap = adminUserApi.getUserMap(ownerUserIds);
- // 2.2 一次 GROUP BY 查出本页所有群的活跃成员数,避免按行 N+1
- Set groupIds = convertSet(pageResult.getList(), ImGroupDO::getId);
- Map memberCountMap = groupMemberService.getActiveMemberCountMap(groupIds);
- // 2.3 转换为 VO,填充群主昵称、群成员数量
- return BeanUtils.toBean(pageResult, ImGroupManagerRespVO.class, vo -> {
- MapUtils.findAndThen(userMap, vo.getOwnerUserId(), user -> vo.setOwnerNickname(user.getNickname()));
- vo.setMemberCount(memberCountMap.getOrDefault(vo.getId(), 0L).intValue());
- });
+ public PageResult getGroupPage(ImGroupManagerPageReqVO pageReqVO) {
+ return groupMapper.selectPage(pageReqVO);
}
@Override
@@ -328,7 +310,6 @@ public class ImGroupServiceImpl implements ImGroupService {
throw exception(GROUP_NOT_EXISTS);
}
// 2. 解封(保留 bannedReason / bannedTime 作为历史记录)
- // TODO DONE @AI:不要使用 mybatis plus 的方法,直接操作。另外,感觉封禁时间、封禁原因,没必要置空,留着问题不大。
groupMapper.updateById(new ImGroupDO().setId(id).setBanned(false));
}
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageService.java
index ae89515..3551018 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageService.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageService.java
@@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.im.service.message;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group.ImGroupMessageManagerPageReqVO;
-import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group.ImGroupMessageManagerRespVO;
import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageListReqVO;
import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageSendReqVO;
import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO;
@@ -118,9 +117,9 @@ public interface ImGroupMessageService {
// ==================== 管理后台 ====================
/**
- * 【管理后台】分页查询群聊消息(已填充群名称、发送人昵称)
+ * 【管理后台】分页查询群聊消息
*/
- PageResult getGroupMessagePage(ImGroupMessageManagerPageReqVO reqVO);
+ PageResult getGroupMessagePage(ImGroupMessageManagerPageReqVO reqVO);
/**
* 【管理后台】获取群聊消息详情
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java
index 145f018..0590ce4 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java
@@ -7,14 +7,11 @@ import cn.hutool.core.util.ObjUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group.ImGroupMessageManagerPageReqVO;
-import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group.ImGroupMessageManagerRespVO;
import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageListReqVO;
import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageSendReqVO;
-import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO;
import cn.iocoder.yudao.module.im.dal.mysql.message.ImGroupMessageMapper;
@@ -28,8 +25,6 @@ import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService;
import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService;
import cn.iocoder.yudao.module.im.service.websocket.dto.ImGroupMessageDTO;
import cn.iocoder.yudao.module.im.service.websocket.dto.message.RecallMessage;
-import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
-import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
@@ -77,9 +72,6 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService {
@Resource
private ImWebSocketService imWebSocketService;
- @Resource
- private AdminUserApi adminUserApi;
-
@Override
public ImGroupMessageDO sendGroupMessage(Long senderId, ImGroupMessageSendReqVO reqVO) {
// 1.1 幂等校验:根据 senderId + clientMessageId 查重
@@ -554,26 +546,9 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService {
// ==================== 管理后台 ====================
- // TODO @AI:这些挪到 controller 拼接 VO
@Override
- public PageResult getGroupMessagePage(ImGroupMessageManagerPageReqVO reqVO) {
- // 1. 分页查询
- PageResult pageResult = groupMessageMapper.selectPage(reqVO);
- if (CollUtil.isEmpty(pageResult.getList())) {
- return PageResult.empty(pageResult.getTotal());
- }
-
- // 2. 批量查询群名称、发送人昵称
- Set groupIds = convertSet(pageResult.getList(), ImGroupMessageDO::getGroupId);
- Set senderIds = convertSet(pageResult.getList(), ImGroupMessageDO::getSenderId);
- Map groupMap = groupService.getGroupMap(groupIds);
- Map userMap = adminUserApi.getUserMap(senderIds);
-
- // 3. 转换为 VO,填充群名 / 发送人昵称
- return BeanUtils.toBean(pageResult, ImGroupMessageManagerRespVO.class, vo -> {
- MapUtils.findAndThen(groupMap, vo.getGroupId(), group -> vo.setGroupName(group.getName()));
- MapUtils.findAndThen(userMap, vo.getSenderId(), user -> vo.setSenderNickname(user.getNickname()));
- });
+ public PageResult getGroupMessagePage(ImGroupMessageManagerPageReqVO reqVO) {
+ return groupMessageMapper.selectPage(reqVO);
}
@Override
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageService.java
index 407eda0..1dfd668 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageService.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageService.java
@@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.im.service.message;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.privates.ImPrivateMessageManagerPageReqVO;
-import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.privates.ImPrivateMessageManagerRespVO;
import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageListReqVO;
import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageSendReqVO;
import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO;
@@ -91,9 +90,9 @@ public interface ImPrivateMessageService {
// ==================== 管理后台 ====================
/**
- * 【管理后台】分页查询私聊消息(已填充发送人 / 接收人昵称)
+ * 【管理后台】分页查询私聊消息
*/
- PageResult getPrivateMessagePage(ImPrivateMessageManagerPageReqVO reqVO);
+ PageResult getPrivateMessagePage(ImPrivateMessageManagerPageReqVO reqVO);
/**
* 【管理后台】获取私聊消息详情
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java
index 49ac482..a9b8fd6 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java
@@ -1,15 +1,12 @@
package cn.iocoder.yudao.module.im.service.message;
-import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.privates.ImPrivateMessageManagerPageReqVO;
-import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.privates.ImPrivateMessageManagerRespVO;
import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageListReqVO;
import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageSendReqVO;
import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO;
@@ -21,8 +18,6 @@ import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService;
import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService;
import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO;
import cn.iocoder.yudao.module.im.service.websocket.dto.message.RecallMessage;
-import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
-import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@@ -30,13 +25,9 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.time.LocalDateTime;
-import java.util.HashSet;
import java.util.List;
-import java.util.Map;
-import java.util.Set;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.im.enums.ImCommonConstants.*;
@@ -61,9 +52,6 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService {
@Resource
private ImWebSocketService imWebSocketService;
- @Resource
- private AdminUserApi adminUserApi;
-
@Override
public ImPrivateMessageDO sendPrivateMessage(Long senderId, ImPrivateMessageSendReqVO reqVO) {
// 1.1 幂等校验:根据 senderId + clientMessageId 查重
@@ -183,26 +171,10 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService {
// ==================== 管理后台 ====================
- // TODO @AI:这些挪到 controller 拼接 VO
+ // DONE @AI:这些挪到 controller 拼接 VO
@Override
- public PageResult getPrivateMessagePage(ImPrivateMessageManagerPageReqVO reqVO) {
- // 1. 分页查询
- PageResult pageResult = privateMessageMapper.selectPage(reqVO);
- if (CollUtil.isEmpty(pageResult.getList())) {
- return PageResult.empty(pageResult.getTotal());
- }
-
- // 2. 一次性批量查询发送人 + 接收人昵称
- Set userIds = new HashSet<>();
- userIds.addAll(convertSet(pageResult.getList(), ImPrivateMessageDO::getSenderId));
- userIds.addAll(convertSet(pageResult.getList(), ImPrivateMessageDO::getReceiverId));
- Map userMap = adminUserApi.getUserMap(userIds);
-
- // 3. 转换为 VO,填充昵称
- return BeanUtils.toBean(pageResult, ImPrivateMessageManagerRespVO.class, vo -> {
- MapUtils.findAndThen(userMap, vo.getSenderId(), user -> vo.setSenderNickname(user.getNickname()));
- MapUtils.findAndThen(userMap, vo.getReceiverId(), user -> vo.setReceiverNickname(user.getNickname()));
- });
+ public PageResult getPrivateMessagePage(ImPrivateMessageManagerPageReqVO reqVO) {
+ return privateMessageMapper.selectPage(reqVO);
}
@Override
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/sensitiveword/ImSensitiveWordServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/sensitiveword/ImSensitiveWordServiceImpl.java
index 1620b9e..3483526 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/sensitiveword/ImSensitiveWordServiceImpl.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/sensitiveword/ImSensitiveWordServiceImpl.java
@@ -3,21 +3,35 @@ package cn.iocoder.yudao.module.im.service.sensitiveword;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.cache.CacheUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
+import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
import cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo.ImSensitiveWordPageReqVO;
import cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo.ImSensitiveWordSaveReqVO;
import cn.iocoder.yudao.module.im.dal.dataobject.sensitiveword.ImSensitiveWordDO;
import cn.iocoder.yudao.module.im.dal.mysql.sensitiveword.ImSensitiveWordMapper;
import com.github.houbb.sensitive.word.bs.SensitiveWordBs;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
import jakarta.annotation.Resource;
+import lombok.AllArgsConstructor;
+import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
+import java.time.Duration;
+import java.time.LocalDateTime;
import java.util.List;
+import java.util.Objects;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.MESSAGE_SENSITIVE_WORD_BLOCKED;
import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.SENSITIVE_WORD_DUPLICATED;
import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.SENSITIVE_WORD_NOT_EXISTS;
@@ -37,34 +51,92 @@ public class ImSensitiveWordServiceImpl implements ImSensitiveWordService {
@Resource
private ImSensitiveWordMapper sensitiveWordMapper;
- // TODO @AI:当前节点,立即刷新。其他节点,通过 job 扫描最后更新时间(只要有不同于目前最后一条记录的最大更新时间,就重新构建对应的树)。
/**
- * houbb 敏感词检测器,词库由 ImSensitiveWordDeny 在 init 时回调加载
+ * 缓存条目
*/
- @Resource
- private SensitiveWordBs sensitiveWordBs;
+ @Data
+ @AllArgsConstructor
+ private static class SensitiveWordBsCache {
+
+ /**
+ * 敏感词检测器
+ */
+ private SensitiveWordBs bs;
+ /**
+ * 构建本实例时数据库里的 max(update_time),作为下次刷新比对的基线
+ */
+ private LocalDateTime maxUpdateTime;
+
+ }
- // TODO @AI:你看看现有仓库里,是否有类似这种的。按照最后更新判断,是否需要刷新本地缓存。
/**
- * 触发 houbb 词库重新加载(重建 trie)
+ * 租户 → SensitiveWordBs 实例的本地缓存
*
- * 加 synchronized 是因为 SensitiveWordBs.init() 内部对 wordData 做就地写入,
- * 若与并发的 contains() 重叠会读到半构建的 trie。互斥后短暂阻塞 contains 即可保证可见性。
+ * 每分钟触发一次异步 reload,先读 max(update_time),没变就复用旧实例(避免 trie 重建),变了才重新读词库 + 重建。
+ * 单实例 CRUD 后另外通过 {@link #invalidateSensitiveWordBsCaches()} 立即让本机失效,多实例靠定时刷新最长 1 分钟内收敛。
+ */
+ @SuppressWarnings({"Convert2Diamond", "NullableProblems"})
+ private final LoadingCache sensitiveWordBsCaches = CacheUtils.buildAsyncReloadingCache(
+ Duration.ofMinutes(1L), // 1 分钟过期
+ new CacheLoader() {
+
+ @Override
+ public SensitiveWordBsCache load(Long tenantId) {
+ return loadFresh(tenantId);
+ }
+
+ @Override
+ public ListenableFuture reload(Long tenantId, SensitiveWordBsCache oldValue) {
+ // 先比对 max(update_time);没变 → 复用旧实例,避免无谓地重建 trie
+ LocalDateTime currentMax = sensitiveWordMapper.selectMaxUpdateTime(tenantId);
+ if (Objects.equals(oldValue.getMaxUpdateTime(), currentMax)) {
+ return Futures.immediateFuture(oldValue);
+ }
+ // 变了 → 重新读词库并重建 trie
+ return Futures.immediateFuture(loadFresh(tenantId));
+ }
+
+ });
+
+ private SensitiveWordBsCache loadFresh(Long tenantId) {
+ return TenantUtils.execute(tenantId, () -> {
+ // 先取基线时间再读词库:反过来在两次查询之间出现的新插入会被漏感知
+ LocalDateTime maxUpdateTime = sensitiveWordMapper.selectMaxUpdateTime(tenantId);
+ List words = sensitiveWordMapper.selectListByStatus(CommonStatusEnum.ENABLE.getStatus());
+ // 构建敏感词检测器
+ SensitiveWordBs bs = SensitiveWordBs.newInstance()
+ .wordDeny(() -> convertList(words, ImSensitiveWordDO::getWord))
+ .ignoreCase(true)
+ .ignoreWidth(true) // 忽略全/半角
+ .ignoreNumStyle(true) // 忽略数字风格(中文/阿拉伯)
+ .ignoreChineseStyle(true) // 忽略繁简体
+ .enableWordCheck(true)
+ .init();
+ return new SensitiveWordBsCache(bs, maxUpdateTime);
+ });
+ }
+
+ /**
+ * 强制让敏感词缓存失效,下次访问按最新 DB 重建
*
- * init() 本身幂等,单实例可以直接调;多实例部署时需配合 Redis pub/sub 广播让各实例 init。
+ * 有租户上下文:仅失效该租户。无租户上下文(如系统级 / 跨租户清理):兜底失效所有租户。
*/
- private synchronized void reloadSensitiveWords() {
- sensitiveWordBs.init();
- log.info("[reloadSensitiveWords][敏感词库已 reload]");
+ private void invalidateSensitiveWordBsCaches() {
+ Long tenantId = TenantContextHolder.getTenantId();
+ if (tenantId != null) {
+ sensitiveWordBsCaches.invalidate(tenantId);
+ return;
+ }
+ sensitiveWordBsCaches.invalidateAll();
}
- // TODO @AI:需要按照租户来。
@Override
public void validateText(String text) {
if (StrUtil.isBlank(text)) {
return;
}
- if (sensitiveWordBs.contains(text)) {
+ SensitiveWordBs bs = sensitiveWordBsCaches.getUnchecked(TenantContextHolder.getRequiredTenantId()).getBs();
+ if (bs.contains(text)) {
throw exception(MESSAGE_SENSITIVE_WORD_BLOCKED);
}
}
@@ -89,9 +161,8 @@ public class ImSensitiveWordServiceImpl implements ImSensitiveWordService {
// 2.1 入库
ImSensitiveWordDO word = BeanUtils.toBean(reqVO, ImSensitiveWordDO.class);
sensitiveWordMapper.insert(word);
- // TODO @AI:houbb 这个注释有点奇怪!
- // 2.2 触发 houbb 词库 reload
- reloadSensitiveWords();
+ // 2.2 强制失效本机缓存(多实例靠定时刷新收敛)
+ invalidateSensitiveWordBsCaches();
return word.getId();
}
@@ -105,9 +176,8 @@ public class ImSensitiveWordServiceImpl implements ImSensitiveWordService {
// 2.1 更新
ImSensitiveWordDO updateObj = BeanUtils.toBean(reqVO, ImSensitiveWordDO.class);
sensitiveWordMapper.updateById(updateObj);
- // 2.2 TODO @AI:houbb 这个注释有点奇怪!
- // 2.2 触发 houbb 词库 reload
- reloadSensitiveWords();
+ // 2.2 强制失效本机缓存
+ invalidateSensitiveWordBsCaches();
}
@Override
@@ -117,8 +187,8 @@ public class ImSensitiveWordServiceImpl implements ImSensitiveWordService {
// 2.1 删除
sensitiveWordMapper.deleteById(id);
- // 2.2 触发 houbb 词库 reload
- reloadSensitiveWords();
+ // 2.2 强制失效本机缓存
+ invalidateSensitiveWordBsCaches();
}
@@ -127,11 +197,10 @@ public class ImSensitiveWordServiceImpl implements ImSensitiveWordService {
if (CollUtil.isEmpty(ids)) {
return;
}
-
// 1. 删除
sensitiveWordMapper.deleteByIds(ids);
- // 触发 houbb 词库 reload
- reloadSensitiveWords();
+ // 2. 强制失效本机缓存
+ invalidateSensitiveWordBsCaches();
}
private void validateSensitiveWordExists(Long id) {
@@ -148,7 +217,6 @@ public class ImSensitiveWordServiceImpl implements ImSensitiveWordService {
if (exist == null) {
return;
}
- // TODO @AI:是不是 id 不用判断
if (id == null || ObjUtil.notEqual(exist.getId(), id)) {
throw exception(SENSITIVE_WORD_DUPLICATED, word);
}