1658 字
约 5 分钟
3
Java 项目中的敏感词过滤设计与实现

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://clxhxhhr.top/posts/402/
作者
clxstart
发布于
2026-09-01
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。