Golang

关注公众号 jb51net

关闭
首页 > 脚本专栏 > Golang > Go敏感词过滤

使用Go语言实现敏感词过滤功能

作者:郭强112

敏感词过滤,算是一个比较常见的功能,尤其是在内容、社交类应用中更是如此,本文介绍如何使用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)
}

测试结果如下:

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

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

完善敏感词过滤

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

package sensitivewordcheck

import "sync"

// TrieV1Node 表示TrieV1树的节点
type TrieV1Node struct {
	children map[rune]*TrieV1Node // 子节点
	isEnd    bool
	Text     string
	Value    rune
	parent   *TrieV1Node // 父节点
}

// TrieV1 表示敏感词的TrieV1树
type TrieV1 struct {
	root *TrieV1Node
	lock sync.RWMutex
}

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

// Insert 将一个敏感词插入到TrieV1树中
func (t *TrieV1) Insert(word string) {
	t.lock.Lock()
	defer t.lock.Unlock()

	node := t.root
	for _, char := range []rune(word) {
		if _, ok := node.children[char]; !ok {
			node.children[char] = &TrieV1Node{
				children: make(map[rune]*TrieV1Node),
				isEnd:    false,
				parent:   node,
				Value:    char,
			}
		}
		node = node.children[char]
	}

	node.Text = word
	node.isEnd = true
}

// Contains 检测文本中是否包含敏感词
func (t *TrieV1) Contains(text string) bool {
	t.lock.RLock()
	defer t.lock.RUnlock()

	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
}

// Check 检测文本中是否包含敏感词,并返回第一个敏感词
func (t *TrieV1) Check(text string) string {
	t.lock.RLock()
	defer t.lock.RUnlock()

	node := t.root
	for _, char := range text {
		if _, ok := node.children[char]; !ok {
			continue
		}
		node = node.children[char]
		if node.isEnd {
			return node.Text
		}
	}

	return ""
}

// Rebuild 重新构建敏感词树
func (t *TrieV1) Rebuild(words []string) {
	t.lock.Lock()
	defer t.lock.Unlock()

	t.root = &TrieV1Node{}

	for _, word := range words {
		t.Insert(word)
	}
}

// Delete 删除一个敏感词
func (t *TrieV1) Delete(word string) {
	t.lock.Lock()
	defer t.lock.Unlock()

	node := t.root

	for _, char := range []rune(word) {
		if _, ok := node.children[char]; !ok {
			return
		}
		node = node.children[char]

		if node.isEnd {
			node.isEnd = false
			node.Text = ""

			if len(node.children) > 0 { // 有子节点,不能删除
				break
			}

			// 递归删除
			t.doDel(node)
		}

	}
}

func (t *TrieV1) doDel(node *TrieV1Node) {
	// 再次判断是否可以删除
	if node == nil || len(node.children) > 0 {
		return
	}

	// 从上级节点的children中删除本节点
	delete(node.parent.children, node.Value)

	// 判断上一层节点是否可以删除
	t.doDel(node.parent)
}

在上面的版本中,我们添加了读写锁来保证并发安全,并且添加了删除敏感词的功能。

敏感词库的变更,是一个并不频繁的操作,而可以预见的时,敏感词库不会太大。所以,我们是否可以在敏感词库发生变更时,直接重构整个敏感词库,在重构完成后,再切换到新的敏感词库上呢?

测试代码:

package sensitivewordcheck

import (
	"github.com/stretchr/testify/assert"
	"testing"
)

var trieV1 *TrieV1

func init() {
	trieV1 = NewTrieV1()
	for _, word := range sensitiveWords {
		trieV1.Insert(word)
	}
}

func TestCheckWordAndDelete(t *testing.T) {

	// 添加敏感词 非法捕鱼
	trieV1.Insert("非法捕鱼")

	assert.Equal(t, trieV1.Contains("你要去非法捕鱼吗?"), true)

	// 添加敏感词 非法打猎
	trieV1.Insert("非法打猎")

	assert.Equal(t, trieV1.Contains("你要去非法打猎吗?"), true)

	// 删除敏感词 非法打猎
	trieV1.Delete("非法打猎")

	// 不再包含 非法打猎
	assert.Equal(t, trieV1.Contains("你要去非法打猎吗?"), false)

	// 非法捕鱼 不受影响
	assert.Equal(t, trieV1.Contains("你要去非法捕鱼吗?"), true)

	// 更长的敏感词
	trieV1.Insert("非法捕鱼工具")
	assert.Equal(t, trieV1.Contains("你要去买非法捕鱼工具吗?"), true)

	// 删除 非法捕鱼
	trieV1.Delete("非法捕鱼")
	assert.Equal(t, trieV1.Contains("你要去非法捕鱼吗?"), false)
	// 如果有子节点,不删除
	assert.Equal(t, trieV1.Contains("你要去买非法捕鱼工具吗?"), true)

}

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

后记

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

到此这篇关于使用Go语言实现敏感词过滤功能的文章就介绍到这了,更多相关Go敏感词过滤内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文