敏感词过滤服务
在内容管理项目中,我们经常会用到敏感词检查/过滤服务,这里我们使用 Go 编写一个简单的敏感词过滤服务。
主要功能如下:
- 加载敏感词列表: 从文件或直接在代码中定义。
- 添加/删除敏感词: 允许动态修改敏感词列表。
- 文本过滤: 检测文本中是否包含敏感词,并进行替换或直接返回结果。
我们将使用 Trie (前缀树) 数据结构来提高查找效率。
以下是代码实现:
package main
import (
"bufio"
"fmt"
"io"
"log"
"os"
"strings"
"sync"
)
// TrieNode Trie树节点
type TrieNode struct {
children map[rune]*TrieNode
isEnd bool
}
// Trie Trie树
type Trie struct {
root *TrieNode
lock sync.RWMutex
}
// NewTrie 创建新的Trie树
func NewTrie() *Trie {
return &Trie{
root: &TrieNode{children: make(map[rune]*TrieNode)},
}
}
// Insert 向Trie树中插入敏感词
func (t *Trie) Insert(word string) {
t.lock.Lock()
defer t.lock.Unlock()
node := t.root
for _, char := range word {
if _, ok := node.children[char]; !ok {
node.children[char] = &TrieNode{children: make(map[rune]*TrieNode)}
}
node = node.children[char]
}
node.isEnd = true
}
// Delete 从Trie树中删除敏感词
func (t *Trie) Delete(word string) {
t.lock.Lock()
defer t.lock.Unlock()
node := t.root
nodes := make([]*TrieNode, 0)
chars := make([]rune, 0)
for _, char := range word {
if _, ok := node.children[char]; !ok {
return // 如果没有找到,直接返回
}
nodes = append(nodes, node)
chars = append(chars, char)
node = node.children[char]
}
if !node.isEnd {
return // 查找的不是一个词的结尾,不需要删除
}
if len(node.children) == 0 {
for i := len(nodes) - 1; i >= 0; i-- {
parentNode := nodes[i]
delete(parentNode.children, chars[i])
if len(parentNode.children) > 0 || parentNode == t.root {
break
}
}
} else {
node.isEnd = false // 如果该节点还有子节点,仅取消isEnd
}
}
// Contains 检查Trie树中是否存在敏感词
func (t *Trie) Contains(word string) bool {
t.lock.RLock()
defer t.lock.RUnlock()
node := t.root
for _, char := range word {
if _, ok := node.children[char]; !ok {
return false
}
node = node.children[char]
}
return node.isEnd
}
// Filter 过滤文本中的敏感词,并用替换符替换
func (t *Trie) Filter(text string, replaceRune rune) string {
t.lock.RLock()
defer t.lock.RUnlock()
result := []rune(text)
for i := 0; i < len(result); i++ {
node := t.root
start := i
for j := i; j < len(result); j++ {
char := result[j]
if _, ok := node.children[char]; !ok {
break
}
node = node.children[char]
if node.isEnd {
// 发现敏感词,替换
for k := start; k <= j; k++ {
result[k] = replaceRune
}
i = j
break
}
}
}
return string(result)
}
// LoadFromFile 从文件中加载敏感词
func (t *Trie) LoadFromFile(filePath string) error {
file, err := os.Open(filePath)
if err != nil {
return err
}
defer file.Close()
reader := bufio.NewReader(file)
for {
line, err := reader.ReadString('\n')
if err != nil {
if err == io.EOF {
break
}
return err
}
line = strings.TrimSpace(line)
if line == "" {
continue
}
t.Insert(line)
}
return nil
}
func main() {
trie := NewTrie()
// 1. 从文件加载敏感词
err := trie.LoadFromFile("sensitive_words.txt")
if err != nil {
log.Println("加载敏感词文件失败:", err)
// 如果加载失败,使用默认敏感词
trie.Insert("fuck")
trie.Insert("shit")
}
// 2. 添加/删除敏感词
trie.Insert("尼玛")
trie.Delete("shit")
// 3. 文本过滤
text1 := "这是一段测试文本,包含fuck和尼玛,还有一些正常内容。"
filteredText1 := trie.Filter(text1, '*')
fmt.Println("过滤前:", text1)
fmt.Println("过滤后:", filteredText1)
text2 := "你好,世界,没有敏感词!"
filteredText2 := trie.Filter(text2, '*')
fmt.Println("过滤前:", text2)
fmt.Println("过滤后:", filteredText2)
// 4. 测试 Contains
fmt.Println("Contains 'fuck':", trie.Contains("fuck")) // true
fmt.Println("Contains 'shit':", trie.Contains("shit")) // false
fmt.Println("Contains '尼玛':", trie.Contains("尼玛")) // true
}
说明:
- 创建
sensitive_words.txt
文件: 将敏感词一行一个放入此文件中。 - 编译并运行代码:
go run main.go
- 测试结果: 查看控制台输出,可以看到敏感词被替换为了
*
。
代码解析:
TrieNode
和Trie
: 定义了 Trie 树的数据结构。Insert(word string)
: 向 Trie 树插入一个敏感词。Delete(word string)
: 从Trie树中删除敏感词。Contains(word string)
: 检查 Trie 树是否包含指定敏感词。Filter(text string, replaceRune rune)
: 过滤文本中的敏感词,并用replaceRune
替换。LoadFromFile(filePath string)
: 从指定文件路径加载敏感词,一行一个。main()
函数: 演示了如何使用这个敏感词过滤服务。
优点:
- 高效: 使用 Trie 树进行查找,时间复杂度接近 O(n),n为敏感词长度,相比于直接匹配,效率更高。
- 可扩展: 可以很容易地添加新的敏感词。
- 线程安全: 使用
sync.RWMutex
实现了并发安全的读写操作。
进一步优化:
- 忽略大小写和空格: 可以对输入的文本和敏感词进行预处理,转换为小写并去除空格。
- 支持多种替换方式: 可以根据需求选择不同的替换符或替换规则。
- 使用高性能的第三方库: 可以使用高性能的 Trie 树库,例如
github.com/emirpasic/gods
。 - 配置化: 将配置(如文件路径,替换符)放入配置文件中。
- API 服务: 将此服务封装成 API 供其他应用调用。
注意:
- 这个示例是一个简单的敏感词过滤实现,在实际应用中可能需要考虑更多的复杂场景,如:
- 同音字、形近字: 可以考虑使用模糊匹配或更复杂的 NLP 技术来处理。
- 组合敏感词: 需要考虑不同组合方式的敏感词。
- 上下文: 有些词在特定上下文中才是敏感的。
- 为了安全和完整性,敏感词列表需要定期更新和维护。
你可以根据自己的需求进行修改和扩展,希望这个例子能够帮助你更好的理解和完善 敏感词过滤服务!