使用Go语言实现简单敏感词过滤

使用Go语言实现简单敏感词过滤

August 17, 2023
后端开发
敏感词, Go

敏感词过滤,算是一个比较常见的功能,尤其是在内容、社交类应用中更是如此。本文介绍如何使用Go语言实现简单的敏感词过滤功能。

简单敏感词过滤-ai版 #

先列出一个gpt给出来的一个简单前缀树的实现:

// 初始化敏感词切片
var sensitiveWords = []string{}

// TrieNode 表示Trie树的节点
type TrieNode struct {
	children map[rune]*TrieNode
	isEnd    bool
	Text     string
}

// Trie 表示敏感词的Trie树
type Trie struct {
	root *TrieNode
}

// NewTrie 创建一个新的Trie树
func NewTrie() *Trie {
	return &Trie{
		root: &TrieNode{
			children: make(map[rune]*TrieNode),
			isEnd:    false,
		},
	}
}

// Insert 将一个敏感词插入到Trie树中
func (t *Trie) Insert(word string) {
	node := t.root
	for _, char := range []rune(word) {
		if _, ok := node.children[char]; !ok {
			node.children[char] = &TrieNode{
				children: make(map[rune]*TrieNode),
				isEnd:    false,
			}
		}
		node = node.children[char]
	}

	node.Text = word
	node.isEnd = true
}

// Contains 检测文本中是否包含敏感词
func (t *Trie) Contains(text string) bool {
	node := t.root
	for _, char := range []rune(text) {
		if _, ok := node.children[char]; !ok {
			continue
		}
		node = node.children[char]
		if node.isEnd {
			return true
		}
	}
	return false
}

这个版本的代码中,构建了一个简单的前缀树来存储敏感词,如果某个节点存储的是敏感词的最后一个字符,则isEnd值为true。这样,当我们检测到某个节点的isEnd值为true时,就说明检测到了敏感词。

如果只是为了检测到一段文本是否包含敏感词,而不需要匹配出所有的敏感词,那实际上在敏感词a包含敏感词b时,我们可以只存储单词b。

我们编写一个测试用例,测试一下上面的代码:

func TestCheckWord1(t *testing.T) {
	trie := NewTrie()
	for _, word := range sensitiveWords {
		trie.Insert(word)
	}

	content := "这里是一段非法活动文本。"

	search := trie.Contains(content)

	assert.Equal(t, search, true)
}

测试结果如下:

测试结果

测试通过。(再这样下去程序员真要失业了!)

当然,上面的代码不完善,例如:不是并发安全的、不支持删除敏感词、没有返回检测到的敏感词。我们来完善一下。

完善敏感词过滤 #

下面我们在上面的代码基础上,添加一些功能。

在上面的版本中,我们添加了读写锁来保证并发安全,并且添加了删除敏感词的功能。
敏感词库的变更,是一个并不频繁的操作,而可以预见的时,敏感词库不会太大。所以,我们是否可以在敏感词库发生变更时,直接重构整个敏感词库,在重构完成后,再切换到新的敏感词库上呢?

测试代码:

上面的测试用例中,我们添加了添加、删除敏感词功能,并校验了删除敏感词的正确性,以及在有更长的敏感词时是否会无删除。 上述用例在本机测试通过。

后记 #

以上,我们实现了一个简单的敏感词过滤功能。实际上,敏感词过滤还可以做得更复杂,添加更多功能,比如,检测拼音、过滤特殊字符等等。这些功能,可以在上面的代码基础上,自行扩展。但是需要考虑的是:扩展功能的同时,是否会影响性能,尤其是在检测超长文本时。