敏感词
这篇解决什么
IM 私聊、群聊的文本不能直接入库。读完能改词库表、validateText、租户缓存,以及在其它文本入口接上同一套拦截。
默认端口 48080,管理端前缀 /admin-api。菜单在 IM 即时通讯 → 敏感词管理,路由 /im/sensitive-word。
示意图:发文本先过 validateText;词库按租户缓存,匹配交给 houbb SensitiveWordBs。
只拦文本,不改已入库消息
ImContentTypeEnum.TEXT(101)才会调用。图片、语音、视频、表情不走这层。命中抛 MESSAGE_SENSITIVE_WORD_BLOCKED,消息不会 insert。
组件位置
| 名称 | 说明 | 仓库路径 |
|---|---|---|
ImSensitiveWordManagerController | @RequestMapping("/im/manager/sensitive-word"),词库 CRUD | ruoyi-office/yudao-module-im/yudao-module-im-server/.../controller/admin/manager/sensitiveword/ImSensitiveWordManagerController.java |
ImSensitiveWordService | validateText(text);空白直接返回 | 同模块 .../service/sensitiveword/ImSensitiveWordService.java |
ImSensitiveWordServiceImpl | 租户 LoadingCache + houbb trie | ImSensitiveWordServiceImpl.java |
| 管理端列表 | useVbenVxeGrid,新增 / 批量删除 | ruoyi-office-vben/apps/web-antd/src/views/im/manager/sensitiveword/index.vue |
| 管理端 API | /im/manager/sensitive-word/* | .../src/api/im/manager/sensitiveword/index.ts |
| Maven 依赖 | com.github.houbb:sensitive-word,版本 0.29.5 | ruoyi-office/yudao-dependencies/pom.xml、yudao-module-im-server/pom.xml |
写操作挂 @PreAuthorize:im:manager:sensitive-word:create / update / delete。菜单权限是 im:manager:sensitive-word:list。
管理端词库
打开 IM 即时通讯 → 敏感词管理。列表按词、状态、创建时间筛。点新增写入一条,停用后不再进检测树。

本系统截图:敏感词列表。词库为空时显示「暂无数据」。

本系统截图:新增弹窗。必填词本身和状态(开启 / 关闭)。
| 名称 | 说明 | 仓库路径 |
|---|---|---|
| 新增 / 修改 | word 最长 64;status 走 CommonStatusEnum | ImSensitiveWordSaveReqVO.java |
| 分页 | word 模糊、status、createTime 区间 | ImSensitiveWordPageReqVO.java |
| 列表填充创建人 | /page 用 AdminUserApi.getUserMap 补 creatorName | ImSensitiveWordManagerController |
| 移动端页 | 同菜单,路径 /pages-im/manager/sensitive-word/index | ruoyi-office-uniapp/src/pages-im/manager/sensitive-word/ |
管理端没有「试一下这段话」的接口。要验证命中,发一条私聊 / 群聊文本,或在单测里调 validateText。
同租户词不能重复
(word, tenant_id) 唯一。重复抛 SENSITIVE_WORD_DUPLICATED。改词时排除自身。
表与字段
ImSensitiveWordDO 映射 im_sensitive_word。检测只读 status = 0 的行。
| 名称 | 说明 | 仓库路径 |
|---|---|---|
@TableName("im_sensitive_word") | IM 敏感词表 | .../dal/dataobject/sensitiveword/ImSensitiveWordDO.java |
word | 词本身。表列 varchar(128),入参仍按 VO 限 64 | 同上 |
status | 0 启用,1 停用 | CommonStatusEnum |
uk_im_sensitive_word | (word, tenant_id) | ruoyi-office-db/dump/latest/ 中该表 DDL |
selectListByStatus | 重建 trie 时只拉启用词 | ImSensitiveWordMapper.java |
selectMaxUpdateTime | 缓存刷新比对基线 | 同上 |
@TableName("im_sensitive_word")
@KeySequence("im_sensitive_word_seq")
public class ImSensitiveWordDO extends BaseDO {
@TableId
private Long id;
private String word;
private Integer status;
}BaseDO 带 creator / createTime / updater / updateTime / deleted / tenant_id。不要另加标签列,当前没有按场景分组。
发消息时过滤
私聊、群聊在校验好友 / 群成员之后、insert 之前,对文本调用 validateText。
if (ImContentTypeEnum.TEXT.getType().equals(reqVO.getType())) {
sensitiveWordService.validateText(reqVO.getContent());
}| 名称 | 说明 | 仓库路径 |
|---|---|---|
| 私聊 | sendPrivateMessage 1.4 步 | .../service/message/ImPrivateMessageServiceImpl.java |
| 群聊 | sendGroupMessage 1.5 步 | ImGroupMessageServiceImpl.java |
validateText | 空白跳过;bs.contains(text) 为 true 则抛错 | ImSensitiveWordServiceImpl |
MESSAGE_SENSITIVE_WORD_BLOCKED | 1_040_300_004,「消息包含敏感词,无法发送」 | .../enums/ErrorCodeConstants.java |
构建检测器时打开了忽略大小写、全半角、数字风格、繁简体。「違禁」 和 违禁 会按同一套规则命中。
不要自己 replace 后再入库
这里是拦截,不是打码。要在 JSON 里挡手机号、身份证,走 数据脱敏,不要把 validateText 当成掩码工具。
词库缓存
每个租户一份 SensitiveWordBs。首次 validateText 才加载。CRUD 后本机立刻 invalidate;其它实例靠 1 分钟异步 reload,先比 max(update_time),没变就不重建。
| 名称 | 说明 | 仓库路径 |
|---|---|---|
sensitiveWordBsCaches | CacheUtils.buildAsyncReloadingCache,1 分钟 | ImSensitiveWordServiceImpl |
loadFresh | 先取 max(update_time),再读启用词,最后 SensitiveWordBs.init() | 同上 |
reload | 无租户上下文,必须 TenantUtils.execute(tenantId, …) | 同上 |
| 本机失效 | 有租户只失效当前租户;没有则 invalidateAll | invalidateSensitiveWordBsCaches |
单测在 ImSensitiveWordServiceImplTest:空文本放过、命中抛错、增删后下次校验换词库。
刷新线程要带租户
reload 跑在缓存线程,没有 TenantContextHolder。漏掉 TenantUtils.execute,租户拦截器会按空上下文拼 SQL,读到错库或空词库。
接到其它文本
评论、群公告、频道标题若也要拦,注入 ImSensitiveWordService,在写库前调用 validateText。不要再抄一套 trie。
@Resource
private ImSensitiveWordService sensitiveWordService;
public void check(String text) {
sensitiveWordService.validateText(text);
}| 名称 | 说明 | 仓库路径 |
|---|---|---|
| 过滤入口 | 当前模块内 Service,不是独立 Feign API | ImSensitiveWordService |
| 词库 CRUD | 仍走 /im/manager/sensitive-word | Controller 见上文 |
| 权限标识 | 按钮级,见权限篇的 @ss.hasPermission | im:manager:sensitive-word:* |
跨模块不要反向依赖 IM。只在 IM 自己的文本链路上复用这个 Service。
配置与操作
词库在 IM → 敏感词管理(/im/sensitive-word),表 im_sensitive_word。框架没有敏感词 Starter,跨模块不要依赖 IM。
开启见 框架层。截图见上文列表。
