1658 字
约 5 分钟
2
Java 项目中的敏感词过滤设计与实现
2026-09-01
Java 项目中的敏感词过滤设计与实现
在评论、聊天、论坛、文章发布等业务中,经常需要对用户输入内容进行敏感词过滤。
例如用户提交:
这个商品真垃圾,客服太傻逼了
系统可能希望处理成:
这个商品真垃圾,客服太***了
这类功能本质上就是:
判断一段文本中是否包含敏感词,并根据业务需求进行拦截、替换或记录。
一、敏感词过滤常见业务场景
常见场景包括:
评论审核
聊天消息
论坛发帖
昵称检测
文章发布
弹幕审核
搜索词过滤
不同业务处理方式也不一样。
例如:
评论系统
发现敏感词
↓
替换成 ***
或者:
文章发布
发现敏感词
↓
直接禁止提交
甚至:
聊天系统
发现敏感词
↓
替换
↓
同时记录审核日志
二、最简单的实现方式
如果敏感词数量很少,可以直接维护一个集合:
private static final Set<String> SENSITIVE_WORDS = Set.of(
"敏感词1",
"敏感词2",
"敏感词3"
);
然后遍历判断:
public boolean containsSensitiveWord(String text) {
for (String word : SENSITIVE_WORDS) {
if (text.contains(word)) {
return true;
}
}
return false;
}
替换:
public String filter(String text) {
String result = text;
for (String word : SENSITIVE_WORDS) {
result = result.replace(word, "***");
}
return result;
}
例如:
String text = "这是一段包含敏感词1的内容";
String result = filter(text);
最终:
这是一段包含***的内容
这种方式实现非常简单。
但问题也很明显。
假设:
敏感词数量 = 10 万
文章长度 = 5000
如果每一个敏感词都执行一次:
text.contains(word)
性能就会越来越差。
三、项目里常用 Trie 前缀树
当敏感词数量比较多时,一般不会简单遍历所有敏感词。
可以使用:
Trie
也就是:
前缀树
假设敏感词:
赌博
赌钱
赌博网站
Trie 可以形成类似结构:
赌
├── 博
│ └── 网
│ └── 站
│
└── 钱
这样扫描文本时,可以沿着字符逐步匹配。
例如:
欢迎访问赌博网站
扫描到:
赌
继续判断:
博
继续:
网
继续:
站
最终识别:
赌博网站
相比遍历所有敏感词,这种方式在大量关键词场景下更加合适。
四、核心数据结构
可以定义一个 Trie 节点:
public class TrieNode {
private boolean end;
private Map<Character, TrieNode> children = new HashMap<>();
public boolean isEnd() {
return end;
}
public void setEnd(boolean end) {
this.end = end;
}
public Map<Character, TrieNode> getChildren() {
return children;
}
}
根节点:
private final TrieNode root = new TrieNode();
五、初始化敏感词
例如:
public void addWord(String word) {
TrieNode current = root;
for (char c : word.toCharArray()) {
current = current.getChildren()
.computeIfAbsent(c, key -> new TrieNode());
}
current.setEnd(true);
}
初始化:
addWord("赌博");
addWord("赌博网站");
addWord("敏感词");
最终这些词都会存入 Trie。
六、过滤文本
核心逻辑:
public String filter(String text) {
StringBuilder result = new StringBuilder();
int i = 0;
while (i < text.length()) {
TrieNode current = root;
int j = i;
int matchEnd = -1;
while (j < text.length()) {
current = current.getChildren().get(text.charAt(j));
if (current == null) {
break;
}
if (current.isEnd()) {
matchEnd = j;
}
j++;
}
if (matchEnd != -1) {
result.append("***");
i = matchEnd + 1;
} else {
result.append(text.charAt(i));
i++;
}
}
return result.toString();
}
例如:
filter("请远离赌博网站");
结果:
请远离***
七、项目中怎么使用
比如评论发布接口:
@PostMapping("/comment")
public void publish(@RequestBody CommentRequest request) {
String content = sensitiveWordService.filter(
request.getContent()
);
request.setContent(content);
commentService.save(request);
}
完整流程:
用户提交评论
↓
Controller
↓
敏感词过滤
↓
替换敏感内容
↓
保存数据库
如果业务要求不能发布,可以改成:
if (sensitiveWordService.contains(request.getContent())) {
throw new RuntimeException("内容包含敏感词");
}
流程变成:
用户提交
↓
敏感词检测
↓
命中
↓
拒绝发布
八、敏感词从哪里来?
实际项目通常不会把敏感词全部写死:
Set.of(...)
更常见的是存到数据库:
sensitive_word
------------------
id
word
status
level
create_time
项目启动时:
数据库
↓
查询启用的敏感词
↓
加载到内存
↓
构建 Trie
这样管理员可以在后台:
新增敏感词
删除敏感词
启用
禁用
而不需要修改代码。
九、为什么通常加载到内存?
因为敏感词检测属于:
高频操作
比如一个聊天系统每秒可能处理大量消息。
如果每次都:
查询数据库
↓
再判断敏感词
性能会非常差。
所以一般采用:
数据库
↓
加载内存
↓
Trie
↓
高频匹配
数据库负责:
维护数据
内存负责:
快速检测
十、实际项目还要考虑的问题
真正上线时,不能只处理普通字符串。
例如用户可能故意输入:
赌 博
赌-博
赌@博
试图绕过检测。
所以项目中可能需要先对文本进行标准化:
转小写
去除特殊字符
全角转半角
繁简转换
过滤无意义符号
然后再进行匹配。
另外还要考虑:
敏感词等级
白名单
误杀
动态更新
审核日志
命中次数
用户封禁策略
所以敏感词系统真正做大以后,已经不只是简单的字符串替换。
总结
敏感词过滤的核心思路可以概括成:
用户输入
↓
文本标准化
↓
敏感词检测
↓
Trie 匹配
↓
替换 / 拦截 / 记录
如果敏感词数量很少,可以直接:
String.contains()
或者:
String.replace()
实现。
如果敏感词很多,并且请求量较大,更适合:
数据库维护敏感词
↓
加载到内存
↓
构建 Trie
↓
进行快速匹配
对于普通 Java 业务项目来说,掌握:
Set 简单过滤
Trie 前缀树
数据库动态维护
内存加载
文本标准化
基本就能覆盖大部分敏感词过滤需求。
Java 项目中的敏感词过滤设计与实现
http://www.clxhxhhr.top/posts/402/ 评论
0 条
还没有评论,先写一条吧。