()
+ .likeIfPresent(ImSensitiveWordDO::getWord, reqVO.getWord())
+ .eqIfPresent(ImSensitiveWordDO::getStatus, reqVO.getStatus())
+ .betweenIfPresent(ImSensitiveWordDO::getCreateTime, reqVO.getCreateTime())
+ .orderByDesc(ImSensitiveWordDO::getId));
+ }
+
}
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ErrorCodeConstants.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ErrorCodeConstants.java
index db6d4ef..56c4289 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ErrorCodeConstants.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/enums/ErrorCodeConstants.java
@@ -34,4 +34,8 @@ public interface ErrorCodeConstants {
ErrorCode FRIEND_NOT_FRIEND = new ErrorCode(1_040_600_001, "对方不是您的好友");
ErrorCode FRIEND_ADD_SELF = new ErrorCode(1_040_600_002, "不允许添加自己为好友");
+ // ========== 敏感词 (1-040-700-000) ==========
+ ErrorCode SENSITIVE_WORD_NOT_EXISTS = new ErrorCode(1_040_700_000, "敏感词不存在");
+ ErrorCode SENSITIVE_WORD_DUPLICATED = new ErrorCode(1_040_700_001, "敏感词 '{}' 已存在");
+
}
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/sensitiveword/ImSensitiveWordConfiguration.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/sensitiveword/ImSensitiveWordConfiguration.java
new file mode 100644
index 0000000..44c51c7
--- /dev/null
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/sensitiveword/ImSensitiveWordConfiguration.java
@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.module.im.framework.sensitiveword;
+
+import com.github.houbb.sensitive.word.bs.SensitiveWordBs;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * IM 敏感词检测配置
+ *
+ * @author 芋道源码
+ */
+@Configuration
+public class ImSensitiveWordConfiguration {
+
+ // TODO @AI:可能需要按需创建;因为多租户。
+ /**
+ * 装配 {@link SensitiveWordBs}:
+ * 词库来源 = {@link ImSensitiveWordDeny}(DB 里启用的敏感词),并开启常用规范化能力。
+ */
+ @Bean
+ public SensitiveWordBs sensitiveWordBs(ImSensitiveWordDeny imSensitiveWordDeny) {
+ return SensitiveWordBs.newInstance()
+ .wordDeny(imSensitiveWordDeny)
+ .ignoreCase(true)
+ .ignoreWidth(true) // 忽略全/半角
+ .ignoreNumStyle(true) // 忽略数字风格(中文/阿拉伯)
+ .ignoreChineseStyle(true) // 忽略繁简体
+ .enableWordCheck(true)
+ .init();
+ }
+
+}
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/sensitiveword/ImSensitiveWordDeny.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/sensitiveword/ImSensitiveWordDeny.java
new file mode 100644
index 0000000..34e8b7c
--- /dev/null
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/framework/sensitiveword/ImSensitiveWordDeny.java
@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.im.framework.sensitiveword;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.module.im.dal.dataobject.sensitiveword.ImSensitiveWordDO;
+import cn.iocoder.yudao.module.im.dal.mysql.sensitiveword.ImSensitiveWordMapper;
+import com.github.houbb.sensitive.word.api.IWordDeny;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
+
+// TODO @AI:是不是可以,直接塞入一个数组。而不是这里查询???
+/**
+ * 自定义敏感词数据来源:从 im_sensitive_word 表加载启用状态的敏感词
+ *
+ * 由 {@link com.github.houbb.sensitive.word.bs.SensitiveWordBs} 在 init() 时回调,
+ * 因此 CRUD 完成后调用 sensitiveWordBs.init() 即可实现热更新。
+ *
+ * @author 芋道源码
+ */
+@Component
+public class ImSensitiveWordDeny implements IWordDeny {
+
+ @Resource
+ private ImSensitiveWordMapper sensitiveWordMapper;
+
+ @Override
+ public List deny() {
+ return convertList(
+ sensitiveWordMapper.selectListByStatus(CommonStatusEnum.ENABLE.getStatus()),
+ ImSensitiveWordDO::getWord);
+ }
+
+}
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendService.java
index 9dce3bd..a14fe0d 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendService.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendService.java
@@ -1,6 +1,9 @@
package cn.iocoder.yudao.module.im.service.friend;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.im.controller.admin.friend.vo.ImFriendUpdateReqVO;
+import cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo.ImFriendManagerPageReqVO;
+import cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo.ImFriendManagerRespVO;
import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO;
import java.util.Collection;
@@ -84,5 +87,11 @@ public interface ImFriendService {
*/
void updateFriend(Long userId, ImFriendUpdateReqVO reqVO);
+ // ==================== 管理后台 ====================
+
+ /**
+ * 【管理后台】分页查询好友关系(已填充用户 / 好友昵称)
+ */
+ PageResult getFriendPage(ImFriendManagerPageReqVO reqVO);
}
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImpl.java
index f4fe4f4..14e2091 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImpl.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/friend/ImFriendServiceImpl.java
@@ -1,14 +1,21 @@
package cn.iocoder.yudao.module.im.service.friend;
+import cn.hutool.core.collection.CollUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.im.controller.admin.friend.vo.ImFriendUpdateReqVO;
+import cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo.ImFriendManagerPageReqVO;
+import cn.iocoder.yudao.module.im.controller.admin.manager.friend.vo.ImFriendManagerRespVO;
import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO;
import cn.iocoder.yudao.module.im.dal.mysql.friend.ImFriendMapper;
import cn.iocoder.yudao.module.im.service.message.ImPrivateMessageService;
import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService;
import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
@@ -20,11 +27,10 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.time.LocalDateTime;
-import java.util.Collection;
-import java.util.List;
-import java.util.Objects;
+import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.module.im.dal.redis.RedisKeyConstants.FRIEND;
import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FRIEND_ADD_SELF;
import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.FRIEND_NOT_FRIEND;
@@ -181,4 +187,28 @@ public class ImFriendServiceImpl implements ImFriendService {
return SpringUtil.getBean(getClass());
}
+ // ==================== 管理后台 ====================
+
+ // TODO @AI:拿到 controller 拼接数据,简化 service;
+ @Override
+ public PageResult getFriendPage(ImFriendManagerPageReqVO reqVO) {
+ // 1. 分页查询
+ PageResult pageResult = imFriendMapper.selectPage(reqVO);
+ if (CollUtil.isEmpty(pageResult.getList())) {
+ return PageResult.empty(pageResult.getTotal());
+ }
+
+ // 2. 一次性批量查询用户 + 好友的昵称
+ Set userIds = new HashSet<>();
+ userIds.addAll(convertSet(pageResult.getList(), ImFriendDO::getUserId));
+ userIds.addAll(convertSet(pageResult.getList(), ImFriendDO::getFriendUserId));
+ Map userMap = adminUserApi.getUserMap(userIds);
+
+ // 3. 转换为 VO,填充昵称
+ return BeanUtils.toBean(pageResult, ImFriendManagerRespVO.class, vo -> {
+ MapUtils.findAndThen(userMap, vo.getUserId(), user -> vo.setUserNickname(user.getNickname()));
+ MapUtils.findAndThen(userMap, vo.getFriendUserId(), user -> vo.setFriendNickname(user.getNickname()));
+ });
+ }
+
}
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberService.java
index fbd4bb7..21d46ed 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberService.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberService.java
@@ -1,12 +1,14 @@
package cn.iocoder.yudao.module.im.service.group;
import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberUpdateReqVO;
+import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.member.ImGroupMemberManagerRespVO;
import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
import jakarta.validation.Valid;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.List;
+import java.util.Map;
/**
* 群成员 Service 接口
@@ -24,6 +26,15 @@ public interface ImGroupMemberService {
ImGroupMemberDO getGroupMember(Long id);
/**
+ * 获得群成员
+ *
+ * @param groupId 群编号
+ * @param userId 用户编号
+ * @return 群成员
+ */
+ ImGroupMemberDO getGroupMember(Long groupId, Long userId);
+
+ /**
* 根据群组 id 查询群成员(包含所有状态)
*
* @param groupId 群组id
@@ -137,4 +148,22 @@ public interface ImGroupMemberService {
*/
void removeGroupMembersByGroupId(Long groupId);
+ /**
+ * 批量按 group 统计活跃成员数:(group_id → count)
+ *
+ * @param groupIds 群编号集合
+ * @return 群成员数 Map
+ */
+ Map getActiveMemberCountMap(Collection groupIds);
+
+ // ==================== 管理后台 ====================
+
+ /**
+ * 【管理后台】获取群成员列表
+ *
+ * @param groupId 群编号
+ * @return 群成员列表
+ */
+ List getGroupMemberList(Long groupId);
+
}
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberServiceImpl.java
index 0b92f0d..b172ebb 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberServiceImpl.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupMemberServiceImpl.java
@@ -2,12 +2,16 @@ package cn.iocoder.yudao.module.im.service.group;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberUpdateReqVO;
+import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.member.ImGroupMemberManagerRespVO;
import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
import cn.iocoder.yudao.module.im.dal.mysql.group.ImGroupMemberMapper;
import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService;
import cn.iocoder.yudao.module.im.service.websocket.dto.ImGroupMessageDTO;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
@@ -17,14 +21,10 @@ import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.time.LocalDateTime;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
-import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
-import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
import static cn.iocoder.yudao.module.im.dal.redis.RedisKeyConstants.GROUP_MEMBER_IDS;
import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.GROUP_MEMBER_NOT_IN_GROUP;
@@ -44,12 +44,20 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService {
@Resource
private ImWebSocketService webSocketService;
+ @Resource
+ private AdminUserApi adminUserApi;
+
@Override
public ImGroupMemberDO getGroupMember(Long id) {
return groupMemberMapper.selectById(id);
}
@Override
+ public ImGroupMemberDO getGroupMember(Long groupId, Long userId) {
+ return groupMemberMapper.selectByGroupIdAndUserId(groupId, userId);
+ }
+
+ @Override
public List getGroupMemberListByGroupId(Long groupId) {
return groupMemberMapper.selectListByGroupId(groupId);
}
@@ -209,4 +217,30 @@ public class ImGroupMemberServiceImpl implements ImGroupMemberService {
.setQuitTime(LocalDateTime.now()));
}
+ @Override
+ public Map getActiveMemberCountMap(Collection groupIds) {
+ return groupMemberMapper.selectCountMapByGroupIdsAndStatus(groupIds, CommonStatusEnum.ENABLE.getStatus());
+ }
+
+ // ==================== 管理后台 ====================
+
+ // TODO @AI:这个拼接的逻辑,拿到 controller,这里只返回 list do;
+ @Override
+ public List getGroupMemberList(Long groupId) {
+ // 1. 查询有效群成员
+ List members = getActiveGroupMemberListByGroupId(groupId);
+ if (CollUtil.isEmpty(members)) {
+ return Collections.emptyList();
+ }
+
+ // 2. 批量查询用户信息
+ Set userIds = convertSet(members, ImGroupMemberDO::getUserId);
+ Map userMap = adminUserApi.getUserMap(userIds);
+
+ // 3. 转换为 VO,填充昵称、头像
+ return BeanUtils.toBean(members, ImGroupMemberManagerRespVO.class, vo ->
+ MapUtils.findAndThen(userMap, vo.getUserId(), user ->
+ vo.setNickname(user.getNickname()).setAvatar(user.getAvatar())));
+ }
+
}
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupService.java
index b38991e..b40768b 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupService.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupService.java
@@ -1,13 +1,19 @@
package cn.iocoder.yudao.module.im.service.group;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupCreateReqVO;
import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupUpdateReqVO;
import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberInviteReqVO;
import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberRemoveReqVO;
+import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerBanReqVO;
+import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerPageReqVO;
+import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerRespVO;
import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
import jakarta.validation.Valid;
+import java.util.Collection;
import java.util.List;
+import java.util.Map;
/**
* 群 Service 接口
@@ -59,6 +65,14 @@ public interface ImGroupService {
ImGroupDO getGroup(Long id);
/**
+ * 批量获得群 Map
+ *
+ * @param ids 群编号集合
+ * @return 群 Map(key = 群编号)
+ */
+ Map getGroupMap(Collection ids);
+
+ /**
* 校验群存在且未封禁、未解散
*
* @param groupId 群编号
@@ -122,4 +136,30 @@ public interface ImGroupService {
*/
void removeGroupMember(Long userId, @Valid ImGroupMemberRemoveReqVO removeReqVO);
+ // ==================== 管理后台 ====================
+
+ // TODO DONE @AI:是不是 controller 拆分哈;(getGroupPage 留在 ImGroupService;getGroupMemberList 已拆到 ImGroupMemberService + ImGroupMemberManagerController)
+ // TODO @AI:不对;应该 VO 放在 controller 拼接
+ /**
+ * 【管理后台】分页查询群列表
+ *
+ * @param pageReqVO 分页查询条件
+ * @return 群分页列表
+ */
+ PageResult getGroupPage(ImGroupManagerPageReqVO pageReqVO);
+
+ /**
+ * 【管理后台】封禁群
+ *
+ * @param banReqVO 封禁信息(含群编号、封禁原因)
+ */
+ void banGroup(@Valid ImGroupManagerBanReqVO banReqVO);
+
+ /**
+ * 【管理后台】解封群
+ *
+ * @param id 群编号
+ */
+ void unbanGroup(Long id);
+
}
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImpl.java
index 3801f4b..57c9496 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImpl.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/group/ImGroupServiceImpl.java
@@ -7,11 +7,16 @@ import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupCreateReqVO;
import cn.iocoder.yudao.module.im.controller.admin.group.vo.ImGroupUpdateReqVO;
import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberInviteReqVO;
import cn.iocoder.yudao.module.im.controller.admin.group.vo.member.ImGroupMemberRemoveReqVO;
+import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerBanReqVO;
+import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerPageReqVO;
+import cn.iocoder.yudao.module.im.controller.admin.manager.group.vo.ImGroupManagerRespVO;
import cn.iocoder.yudao.module.im.dal.dataobject.friend.ImFriendDO;
import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
@@ -35,6 +40,7 @@ import java.util.*;
import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.module.im.dal.redis.RedisKeyConstants.GROUP;
import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*;
@@ -256,6 +262,14 @@ public class ImGroupServiceImpl implements ImGroupService {
}
@Override
+ public Map getGroupMap(Collection ids) {
+ if (CollUtil.isEmpty(ids)) {
+ return Collections.emptyMap();
+ }
+ return convertMap(groupMapper.selectByIds(ids), ImGroupDO::getId);
+ }
+
+ @Override
public List getMyGroupList(Long userId) {
// 1.1 查用户所在的、仍有效的群成员记录(仅 ENABLE 状态)
List members = groupMemberService.getActiveGroupMemberListByUserId(userId);
@@ -271,6 +285,53 @@ public class ImGroupServiceImpl implements ImGroupService {
return groupMapper.selectByIds(groupIds);
}
+ // ==================== 管理后台 ====================
+
+ @Override
+ public PageResult getGroupPage(ImGroupManagerPageReqVO pageReqVO) {
+ // 1. 分页查询群
+ PageResult pageResult = groupMapper.selectPage(pageReqVO);
+ if (CollUtil.isEmpty(pageResult.getList())) {
+ return PageResult.empty(pageResult.getTotal());
+ }
+
+ // 2.1 批量查询群主昵称
+ Set ownerUserIds = convertSet(pageResult.getList(), ImGroupDO::getOwnerUserId);
+ Map userMap = adminUserApi.getUserMap(ownerUserIds);
+ // 2.2 一次 GROUP BY 查出本页所有群的活跃成员数,避免按行 N+1
+ Set groupIds = convertSet(pageResult.getList(), ImGroupDO::getId);
+ Map memberCountMap = groupMemberService.getActiveMemberCountMap(groupIds);
+ // 2.3 转换为 VO,填充群主昵称、群成员数量
+ return BeanUtils.toBean(pageResult, ImGroupManagerRespVO.class, vo -> {
+ MapUtils.findAndThen(userMap, vo.getOwnerUserId(), user -> vo.setOwnerNickname(user.getNickname()));
+ vo.setMemberCount(memberCountMap.getOrDefault(vo.getId(), 0L).intValue());
+ });
+ }
+
+ @Override
+ @CacheEvict(cacheNames = GROUP, key = "#banReqVO.id")
+ public void banGroup(ImGroupManagerBanReqVO banReqVO) {
+ // 1. 校验群存在
+ if (getSelf().getGroup(banReqVO.getId()) == null) {
+ throw exception(GROUP_NOT_EXISTS);
+ }
+ // 2. 更新封禁状态
+ groupMapper.updateById(new ImGroupDO().setId(banReqVO.getId())
+ .setBanned(true).setBannedReason(banReqVO.getReason()).setBannedTime(LocalDateTime.now()));
+ }
+
+ @Override
+ @CacheEvict(cacheNames = GROUP, key = "#id")
+ public void unbanGroup(Long id) {
+ // 1. 校验群存在
+ if (getSelf().getGroup(id) == null) {
+ throw exception(GROUP_NOT_EXISTS);
+ }
+ // 2. 解封(保留 bannedReason / bannedTime 作为历史记录)
+ // TODO DONE @AI:不要使用 mybatis plus 的方法,直接操作。另外,感觉封禁时间、封禁原因,没必要置空,留着问题不大。
+ groupMapper.updateById(new ImGroupDO().setId(id).setBanned(false));
+ }
+
private ImGroupServiceImpl getSelf() {
return SpringUtil.getBean(getClass());
}
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageService.java
index bfd279b..ae89515 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageService.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageService.java
@@ -1,5 +1,8 @@
package cn.iocoder.yudao.module.im.service.message;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group.ImGroupMessageManagerPageReqVO;
+import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group.ImGroupMessageManagerRespVO;
import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageListReqVO;
import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageSendReqVO;
import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO;
@@ -112,5 +115,17 @@ public interface ImGroupMessageService {
*/
void sendTipGroupMessage(Long senderId, Long groupId, Set memberUserIds, String content);
+ // ==================== 管理后台 ====================
+
+ /**
+ * 【管理后台】分页查询群聊消息(已填充群名称、发送人昵称)
+ */
+ PageResult getGroupMessagePage(ImGroupMessageManagerPageReqVO reqVO);
+
+ /**
+ * 【管理后台】获取群聊消息详情
+ */
+ ImGroupMessageDO getGroupMessage(Long id);
+
}
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java
index 79f0f2d..145f018 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImGroupMessageServiceImpl.java
@@ -6,13 +6,17 @@ import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group.ImGroupMessageManagerPageReqVO;
+import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.group.ImGroupMessageManagerRespVO;
import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageListReqVO;
import cn.iocoder.yudao.module.im.controller.admin.message.vo.group.ImGroupMessageSendReqVO;
+import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupDO;
import cn.iocoder.yudao.module.im.dal.dataobject.group.ImGroupMemberDO;
import cn.iocoder.yudao.module.im.dal.dataobject.message.ImGroupMessageDO;
-import cn.iocoder.yudao.module.im.service.websocket.dto.message.RecallMessage;
import cn.iocoder.yudao.module.im.dal.mysql.message.ImGroupMessageMapper;
import cn.iocoder.yudao.module.im.dal.redis.message.GroupMessageReadRedisDAO;
import cn.iocoder.yudao.module.im.enums.message.ImGroupMessageReceiptStatusEnum;
@@ -23,6 +27,9 @@ import cn.iocoder.yudao.module.im.service.group.ImGroupService;
import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService;
import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService;
import cn.iocoder.yudao.module.im.service.websocket.dto.ImGroupMessageDTO;
+import cn.iocoder.yudao.module.im.service.websocket.dto.message.RecallMessage;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
@@ -70,6 +77,9 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService {
@Resource
private ImWebSocketService imWebSocketService;
+ @Resource
+ private AdminUserApi adminUserApi;
+
@Override
public ImGroupMessageDO sendGroupMessage(Long senderId, ImGroupMessageSendReqVO reqVO) {
// 1.1 幂等校验:根据 senderId + clientMessageId 查重
@@ -542,6 +552,35 @@ public class ImGroupMessageServiceImpl implements ImGroupMessageService {
ImGroupMessageDTO.ofSend(tipMessage));
}
+ // ==================== 管理后台 ====================
+
+ // TODO @AI:这些挪到 controller 拼接 VO
+ @Override
+ public PageResult getGroupMessagePage(ImGroupMessageManagerPageReqVO reqVO) {
+ // 1. 分页查询
+ PageResult pageResult = groupMessageMapper.selectPage(reqVO);
+ if (CollUtil.isEmpty(pageResult.getList())) {
+ return PageResult.empty(pageResult.getTotal());
+ }
+
+ // 2. 批量查询群名称、发送人昵称
+ Set groupIds = convertSet(pageResult.getList(), ImGroupMessageDO::getGroupId);
+ Set senderIds = convertSet(pageResult.getList(), ImGroupMessageDO::getSenderId);
+ Map groupMap = groupService.getGroupMap(groupIds);
+ Map userMap = adminUserApi.getUserMap(senderIds);
+
+ // 3. 转换为 VO,填充群名 / 发送人昵称
+ return BeanUtils.toBean(pageResult, ImGroupMessageManagerRespVO.class, vo -> {
+ MapUtils.findAndThen(groupMap, vo.getGroupId(), group -> vo.setGroupName(group.getName()));
+ MapUtils.findAndThen(userMap, vo.getSenderId(), user -> vo.setSenderNickname(user.getNickname()));
+ });
+ }
+
+ @Override
+ public ImGroupMessageDO getGroupMessage(Long id) {
+ return groupMessageMapper.selectById(id);
+ }
+
private ImGroupMessageServiceImpl getSelf() {
return SpringUtil.getBean(getClass());
}
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageService.java
index fa1c7bd..407eda0 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageService.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageService.java
@@ -1,5 +1,8 @@
package cn.iocoder.yudao.module.im.service.message;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.privates.ImPrivateMessageManagerPageReqVO;
+import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.privates.ImPrivateMessageManagerRespVO;
import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageListReqVO;
import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageSendReqVO;
import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO;
@@ -85,4 +88,16 @@ public interface ImPrivateMessageService {
*/
void sendTipPrivateMessage(Long senderId, Long receiverId, String content);
+ // ==================== 管理后台 ====================
+
+ /**
+ * 【管理后台】分页查询私聊消息(已填充发送人 / 接收人昵称)
+ */
+ PageResult getPrivateMessagePage(ImPrivateMessageManagerPageReqVO reqVO);
+
+ /**
+ * 【管理后台】获取私聊消息详情
+ */
+ ImPrivateMessageDO getPrivateMessage(Long id);
+
}
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java
index 9412de5..49ac482 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/message/ImPrivateMessageServiceImpl.java
@@ -1,10 +1,15 @@
package cn.iocoder.yudao.module.im.service.message;
+import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.privates.ImPrivateMessageManagerPageReqVO;
+import cn.iocoder.yudao.module.im.controller.admin.manager.message.vo.privates.ImPrivateMessageManagerRespVO;
import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageListReqVO;
import cn.iocoder.yudao.module.im.controller.admin.message.vo.privates.ImPrivateMessageSendReqVO;
import cn.iocoder.yudao.module.im.dal.dataobject.message.ImPrivateMessageDO;
@@ -16,6 +21,8 @@ import cn.iocoder.yudao.module.im.service.sensitiveword.ImSensitiveWordService;
import cn.iocoder.yudao.module.im.service.websocket.ImWebSocketService;
import cn.iocoder.yudao.module.im.service.websocket.dto.ImPrivateMessageDTO;
import cn.iocoder.yudao.module.im.service.websocket.dto.message.RecallMessage;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@@ -23,9 +30,13 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.time.LocalDateTime;
+import java.util.HashSet;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.im.enums.ImCommonConstants.*;
@@ -50,6 +61,9 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService {
@Resource
private ImWebSocketService imWebSocketService;
+ @Resource
+ private AdminUserApi adminUserApi;
+
@Override
public ImPrivateMessageDO sendPrivateMessage(Long senderId, ImPrivateMessageSendReqVO reqVO) {
// 1.1 幂等校验:根据 senderId + clientMessageId 查重
@@ -167,6 +181,35 @@ public class ImPrivateMessageServiceImpl implements ImPrivateMessageService {
return privateMessageMapper.selectHistoryList(userId, reqVO.getReceiverId(), reqVO.getMaxId(), reqVO.getLimit());
}
+ // ==================== 管理后台 ====================
+
+ // TODO @AI:这些挪到 controller 拼接 VO
+ @Override
+ public PageResult getPrivateMessagePage(ImPrivateMessageManagerPageReqVO reqVO) {
+ // 1. 分页查询
+ PageResult pageResult = privateMessageMapper.selectPage(reqVO);
+ if (CollUtil.isEmpty(pageResult.getList())) {
+ return PageResult.empty(pageResult.getTotal());
+ }
+
+ // 2. 一次性批量查询发送人 + 接收人昵称
+ Set userIds = new HashSet<>();
+ userIds.addAll(convertSet(pageResult.getList(), ImPrivateMessageDO::getSenderId));
+ userIds.addAll(convertSet(pageResult.getList(), ImPrivateMessageDO::getReceiverId));
+ Map userMap = adminUserApi.getUserMap(userIds);
+
+ // 3. 转换为 VO,填充昵称
+ return BeanUtils.toBean(pageResult, ImPrivateMessageManagerRespVO.class, vo -> {
+ MapUtils.findAndThen(userMap, vo.getSenderId(), user -> vo.setSenderNickname(user.getNickname()));
+ MapUtils.findAndThen(userMap, vo.getReceiverId(), user -> vo.setReceiverNickname(user.getNickname()));
+ });
+ }
+
+ @Override
+ public ImPrivateMessageDO getPrivateMessage(Long id) {
+ return privateMessageMapper.selectById(id);
+ }
+
@Override
public void sendTipPrivateMessage(Long senderId, Long receiverId, String content) {
// 1. 插入 TIP_TEXT 消息
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/sensitiveword/ImSensitiveWordService.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/sensitiveword/ImSensitiveWordService.java
index 25bd621..1a5c3aa 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/sensitiveword/ImSensitiveWordService.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/sensitiveword/ImSensitiveWordService.java
@@ -1,5 +1,13 @@
package cn.iocoder.yudao.module.im.service.sensitiveword;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo.ImSensitiveWordPageReqVO;
+import cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo.ImSensitiveWordSaveReqVO;
+import cn.iocoder.yudao.module.im.dal.dataobject.sensitiveword.ImSensitiveWordDO;
+import jakarta.validation.Valid;
+
+import java.util.List;
+
/**
* IM 敏感词 Service 接口
*
@@ -14,4 +22,36 @@ public interface ImSensitiveWordService {
*/
void validateText(String text);
+ // ==================== 管理后台 ====================
+
+ /**
+ * 【管理后台】分页查询敏感词
+ */
+ PageResult getSensitiveWordPage(ImSensitiveWordPageReqVO reqVO);
+
+ /**
+ * 【管理后台】获取敏感词详情
+ */
+ ImSensitiveWordDO getSensitiveWord(Long id);
+
+ /**
+ * 【管理后台】新增敏感词,返回新增 id
+ */
+ Long createSensitiveWord(@Valid ImSensitiveWordSaveReqVO reqVO);
+
+ /**
+ * 【管理后台】修改敏感词
+ */
+ void updateSensitiveWord(@Valid ImSensitiveWordSaveReqVO reqVO);
+
+ /**
+ * 【管理后台】删除敏感词
+ */
+ void deleteSensitiveWord(Long id);
+
+ /**
+ * 【管理后台】批量删除敏感词
+ */
+ void deleteSensitiveWordList(List ids);
+
}
diff --git a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/sensitiveword/ImSensitiveWordServiceImpl.java b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/sensitiveword/ImSensitiveWordServiceImpl.java
index cacb7a6..1620b9e 100644
--- a/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/sensitiveword/ImSensitiveWordServiceImpl.java
+++ b/yudao-module-im/src/main/java/cn/iocoder/yudao/module/im/service/sensitiveword/ImSensitiveWordServiceImpl.java
@@ -1,25 +1,31 @@
package cn.iocoder.yudao.module.im.service.sensitiveword;
-import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo.ImSensitiveWordPageReqVO;
+import cn.iocoder.yudao.module.im.controller.admin.manager.sensitiveword.vo.ImSensitiveWordSaveReqVO;
import cn.iocoder.yudao.module.im.dal.dataobject.sensitiveword.ImSensitiveWordDO;
import cn.iocoder.yudao.module.im.dal.mysql.sensitiveword.ImSensitiveWordMapper;
-import jakarta.annotation.PostConstruct;
+import com.github.houbb.sensitive.word.bs.SensitiveWordBs;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
-import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
-import java.util.Collections;
import java.util.List;
-import java.util.stream.Collectors;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.MESSAGE_SENSITIVE_WORD_BLOCKED;
+import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.SENSITIVE_WORD_DUPLICATED;
+import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.SENSITIVE_WORD_NOT_EXISTS;
-// DONE @AI:已实现内存化,启动时加载 + 定时刷新
/**
* IM 敏感词 Service 实现类
+ *
+ * 词库匹配交给 houbb sensitive-word 库(trie 树 + 全/半角 / 大小写 / 繁简体 / 数字风格规范化)
*
* @author 芋道源码
*/
@@ -29,48 +35,122 @@ import static cn.iocoder.yudao.module.im.enums.ErrorCodeConstants.MESSAGE_SENSIT
public class ImSensitiveWordServiceImpl implements ImSensitiveWordService {
@Resource
- private ImSensitiveWordMapper imSensitiveWordMapper;
+ private ImSensitiveWordMapper sensitiveWordMapper;
+ // TODO @AI:当前节点,立即刷新。其他节点,通过 job 扫描最后更新时间(只要有不同于目前最后一条记录的最大更新时间,就重新构建对应的树)。
/**
- * 内存缓存的敏感词列表
+ * houbb 敏感词检测器,词库由 ImSensitiveWordDeny 在 init 时回调加载
*/
- private volatile List sensitiveWords = Collections.emptyList();
+ @Resource
+ private SensitiveWordBs sensitiveWordBs;
+ // TODO @AI:你看看现有仓库里,是否有类似这种的。按照最后更新判断,是否需要刷新本地缓存。
/**
- * 初始化:启动时加载敏感词到内存
+ * 触发 houbb 词库重新加载(重建 trie)
+ *
+ * 加 synchronized 是因为 SensitiveWordBs.init() 内部对 wordData 做就地写入,
+ * 若与并发的 contains() 重叠会读到半构建的 trie。互斥后短暂阻塞 contains 即可保证可见性。
+ *
+ * init() 本身幂等,单实例可以直接调;多实例部署时需配合 Redis pub/sub 广播让各实例 init。
*/
- @PostConstruct
- public void init() {
- loadSensitiveWords();
+ private synchronized void reloadSensitiveWords() {
+ sensitiveWordBs.init();
+ log.info("[reloadSensitiveWords][敏感词库已 reload]");
}
- /**
- * 定时刷新:每 5 分钟重新加载一次
- */
- @Scheduled(fixedDelay = 5 * 60 * 1000, initialDelay = 5 * 60 * 1000)
- public void refresh() {
- loadSensitiveWords();
+ // TODO @AI:需要按照租户来。
+ @Override
+ public void validateText(String text) {
+ if (StrUtil.isBlank(text)) {
+ return;
+ }
+ if (sensitiveWordBs.contains(text)) {
+ throw exception(MESSAGE_SENSITIVE_WORD_BLOCKED);
+ }
}
- private void loadSensitiveWords() {
- List enabledWords = imSensitiveWordMapper.selectListByStatus(
- CommonStatusEnum.ENABLE.getStatus());
- sensitiveWords = enabledWords.stream()
- .map(ImSensitiveWordDO::getWord)
- .collect(Collectors.toList());
- log.info("[loadSensitiveWords][加载 IM 敏感词 {} 个]", sensitiveWords.size());
+ // ==================== 管理后台 ====================
+
+ @Override
+ public PageResult getSensitiveWordPage(ImSensitiveWordPageReqVO reqVO) {
+ return sensitiveWordMapper.selectPage(reqVO);
}
@Override
- public void validateText(String text) {
- if (text == null || text.isEmpty()) {
+ public ImSensitiveWordDO getSensitiveWord(Long id) {
+ return sensitiveWordMapper.selectById(id);
+ }
+
+ @Override
+ public Long createSensitiveWord(ImSensitiveWordSaveReqVO reqVO) {
+ // 1. 校验唯一
+ validateWordUnique(null, reqVO.getWord());
+
+ // 2.1 入库
+ ImSensitiveWordDO word = BeanUtils.toBean(reqVO, ImSensitiveWordDO.class);
+ sensitiveWordMapper.insert(word);
+ // TODO @AI:houbb 这个注释有点奇怪!
+ // 2.2 触发 houbb 词库 reload
+ reloadSensitiveWords();
+ return word.getId();
+ }
+
+ @Override
+ public void updateSensitiveWord(ImSensitiveWordSaveReqVO reqVO) {
+ // 1.1 校验存在
+ validateSensitiveWordExists(reqVO.getId());
+ // 1.2 校验唯一(排除自身)
+ validateWordUnique(reqVO.getId(), reqVO.getWord());
+
+ // 2.1 更新
+ ImSensitiveWordDO updateObj = BeanUtils.toBean(reqVO, ImSensitiveWordDO.class);
+ sensitiveWordMapper.updateById(updateObj);
+ // 2.2 TODO @AI:houbb 这个注释有点奇怪!
+ // 2.2 触发 houbb 词库 reload
+ reloadSensitiveWords();
+ }
+
+ @Override
+ public void deleteSensitiveWord(Long id) {
+ // 1. 校验存在
+ validateSensitiveWordExists(id);
+
+ // 2.1 删除
+ sensitiveWordMapper.deleteById(id);
+ // 2.2 触发 houbb 词库 reload
+ reloadSensitiveWords();
+ }
+
+
+ @Override
+ public void deleteSensitiveWordList(List ids) {
+ if (CollUtil.isEmpty(ids)) {
+ return;
+ }
+
+ // 1. 删除
+ sensitiveWordMapper.deleteByIds(ids);
+ // 触发 houbb 词库 reload
+ reloadSensitiveWords();
+ }
+
+ private void validateSensitiveWordExists(Long id) {
+ if (sensitiveWordMapper.selectById(id) == null) {
+ throw exception(SENSITIVE_WORD_NOT_EXISTS);
+ }
+ }
+
+ /**
+ * 校验敏感词唯一(修改时排除自身)
+ */
+ private void validateWordUnique(Long id, String word) {
+ ImSensitiveWordDO exist = sensitiveWordMapper.selectByWord(word);
+ if (exist == null) {
return;
}
- // 内存匹配
- for (String word : sensitiveWords) {
- if (text.contains(word)) {
- throw exception(MESSAGE_SENSITIVE_WORD_BLOCKED);
- }
+ // TODO @AI:是不是 id 不用判断
+ if (id == null || ObjUtil.notEqual(exist.getId(), id)) {
+ throw exception(SENSITIVE_WORD_DUPLICATED, word);
}
}