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

 更新时间:2023年12月18日 08:26:14   作者:郭强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敏感词过滤内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • golang中for range的取地址操作陷阱介绍

    golang中for range的取地址操作陷阱介绍

    这篇文章主要介绍了golang中for range的取地址操作陷阱,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-04-04
  • 详解Golang中的各种时间操作

    详解Golang中的各种时间操作

    这篇文章主要介绍了详解Golang中的各种时间操作,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-10-10
  • go自动下载所有的依赖包go module使用详解

    go自动下载所有的依赖包go module使用详解

    这篇文章主要介绍了go自动下载所有的依赖包go module使用详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-03-03
  • 使用Go语言实现心跳机制

    使用Go语言实现心跳机制

    心跳最典型的应用场景是是探测服务是否存活,这篇文章主要来和大家介绍一下如何使用Go语言实现一个简单的心跳程序,感兴趣的可以了解下
    2024-01-01
  • 详解golang 定时任务time.Sleep和time.Tick实现结果比较

    详解golang 定时任务time.Sleep和time.Tick实现结果比较

    本文主要介绍了golang 定时任务time.Sleep和time.Tick实现结果比较,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-02-02
  • Go 热加载之fresh详解

    Go 热加载之fresh详解

    这篇文章主要为大家介绍了Go 热加载之fresh详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-08-08
  • Go语言中函数的参数传递与调用的基本方法

    Go语言中函数的参数传递与调用的基本方法

    这篇文章主要介绍了Go语言中函数的参数传递与调用的基本方法,是golang入门学习中的基础知识,需要的朋友可以参考下
    2015-10-10
  • 使用Golang进行比较版本号大小

    使用Golang进行比较版本号大小

    在日常开发中,比较版本号大小的情况是经常遇到的,这篇文章主要为大家详细介绍了如何使用Golang进行比较版本号大小,需要的小伙伴可以参考下
    2024-01-01
  • 一文详解Golang内存管理之栈空间管理

    一文详解Golang内存管理之栈空间管理

    这篇文章主要介绍了Golang内存管理的栈空间管理,文章通过代码示例介绍的非常详细,对我们学习Golang内存管理有一定的帮助,需要的朋友跟着小编一起来学习吧
    2023-06-06
  • 浅析Golang如何向已关闭的chan读写数据

    浅析Golang如何向已关闭的chan读写数据

    这篇文章主要为大家详细介绍了Golang如何向已关闭的chan读写数据,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2024-02-02

最新评论