Executable
+291
@@ -0,0 +1,291 @@
|
||||
package filter
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// Filter 提供敏感词过滤接口
|
||||
type Filter interface {
|
||||
// Filter 文本过滤函数
|
||||
// excludes 表示排除指定的字符
|
||||
// 返回文本中出现的敏感词,如果敏感词不存在则返回nil
|
||||
// 如果出现异常,则返回error
|
||||
Filter(text string, excludes ...rune) ([]string, error)
|
||||
|
||||
// FilterResult 文本过滤函数
|
||||
// excludes 表示排除指定的字符
|
||||
// 返回文本中出现的敏感词及出现次数,如果敏感词不存在则返回nil
|
||||
// 如果出现异常,则返回error
|
||||
FilterResult(text string, excludes ...rune) (map[string]int, error)
|
||||
|
||||
// FilterReader 从可读流中过滤敏感词
|
||||
// excludes 表示排除指定的字符
|
||||
// 返回可读流中出现的敏感词,如果敏感词不存在则返回nil
|
||||
// 如果出现异常,则返回error
|
||||
FilterReader(reader io.Reader, excludes ...rune) ([]string, error)
|
||||
|
||||
// FilterReaderResult 从可读流中过滤敏感词
|
||||
// excludes 表示排除指定的字符
|
||||
// 返回可读流中出现的敏感词及出现次数,如果敏感词不存在则返回nil
|
||||
// 如果出现异常,则返回error
|
||||
FilterReaderResult(reader io.Reader, excludes ...rune) (map[string]int, error)
|
||||
|
||||
// Replace 使用字符替换文本中的敏感词
|
||||
// delim 替换的字符
|
||||
// 如果出现异常,则返回error
|
||||
Replace(text string, delim rune) (string, error)
|
||||
}
|
||||
|
||||
// NewNodeReaderFilter 创建节点过滤器,实现敏感词的过滤
|
||||
// 从可读流中读取敏感词数据(以指定的分隔符读取数据)
|
||||
func NewNodeReaderFilter(rd io.Reader, delim byte) Filter {
|
||||
nf := &nodeFilter{
|
||||
root: newNode(),
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
_, _ = io.Copy(buf, rd)
|
||||
buf.WriteByte(delim)
|
||||
for {
|
||||
line, err := buf.ReadString(delim)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
nf.addFilterWords(line)
|
||||
}
|
||||
buf.Reset()
|
||||
return nf
|
||||
}
|
||||
|
||||
// NewNodeChanFilter 创建节点过滤器,实现敏感词的过滤
|
||||
// 从通道中读取敏感词数据
|
||||
func NewNodeChanFilter(text <-chan string) Filter {
|
||||
nf := &nodeFilter{
|
||||
root: newNode(),
|
||||
}
|
||||
for v := range text {
|
||||
nf.addFilterWords(v)
|
||||
}
|
||||
return nf
|
||||
}
|
||||
|
||||
// NewNodeFilter 创建节点过滤器,实现敏感词的过滤
|
||||
// 从切片中读取敏感词数据
|
||||
func NewNodeFilter(text []string) Filter {
|
||||
nf := &nodeFilter{
|
||||
root: newNode(),
|
||||
}
|
||||
for i, l := 0, len(text); i < l; i++ {
|
||||
nf.addFilterWords(text[i])
|
||||
}
|
||||
return nf
|
||||
}
|
||||
|
||||
func newNode() *node {
|
||||
return &node{
|
||||
child: make(map[rune]*node),
|
||||
}
|
||||
}
|
||||
|
||||
type node struct {
|
||||
end bool
|
||||
child map[rune]*node
|
||||
}
|
||||
|
||||
type nodeFilter struct {
|
||||
root *node
|
||||
}
|
||||
|
||||
func (nf *nodeFilter) addFilterWords(text string) {
|
||||
n := nf.root
|
||||
uChars := []rune(text)
|
||||
for i, l := 0, len(uChars); i < l; i++ {
|
||||
if unicode.IsSpace(uChars[i]) {
|
||||
continue
|
||||
}
|
||||
if _, ok := n.child[uChars[i]]; !ok {
|
||||
n.child[uChars[i]] = newNode()
|
||||
}
|
||||
n = n.child[uChars[i]]
|
||||
}
|
||||
n.end = true
|
||||
}
|
||||
|
||||
func (nf *nodeFilter) Filter(text string, excludes ...rune) ([]string, error) {
|
||||
buf := bytes.NewBufferString(text)
|
||||
defer buf.Reset()
|
||||
return nf.FilterReader(buf, excludes...)
|
||||
}
|
||||
|
||||
func (nf *nodeFilter) FilterResult(text string, excludes ...rune) (map[string]int, error) {
|
||||
buf := bytes.NewBufferString(text)
|
||||
defer buf.Reset()
|
||||
return nf.FilterReaderResult(buf, excludes...)
|
||||
}
|
||||
|
||||
func (nf *nodeFilter) FilterReader(reader io.Reader, excludes ...rune) ([]string, error) {
|
||||
data, err := nf.FilterReaderResult(reader, excludes...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := make([]string, len(data))
|
||||
i := 0
|
||||
for k := range data {
|
||||
result[i] = k
|
||||
i++
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (nf *nodeFilter) FilterReaderResult(reader io.Reader, excludes ...rune) (map[string]int, error) {
|
||||
var uChars []rune
|
||||
data := make(map[string]int)
|
||||
bi := bufio.NewReader(reader)
|
||||
for {
|
||||
ur, _, err := bi.ReadRune()
|
||||
if err != nil {
|
||||
if err != io.EOF {
|
||||
return nil, err
|
||||
}
|
||||
break
|
||||
}
|
||||
if nf.checkExclude(ur, excludes...) {
|
||||
continue
|
||||
}
|
||||
if (unicode.IsSpace(ur) || unicode.IsPunct(ur)) && len(uChars) > 0 {
|
||||
nf.doFilter(uChars[:], data)
|
||||
uChars = nil
|
||||
continue
|
||||
}
|
||||
uChars = append(uChars, ur)
|
||||
}
|
||||
if len(uChars) > 0 {
|
||||
nf.doFilter(uChars, data)
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (nf *nodeFilter) Replace(text string, delimiter rune) (string, error) {
|
||||
newNF, err := CheckUpdate()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if newNF != nil {
|
||||
nf = newNF
|
||||
}
|
||||
uChars := []rune(text)
|
||||
indexes := nf.doIndexes(uChars)
|
||||
if len(indexes) == 0 {
|
||||
return text, nil
|
||||
}
|
||||
for i := 0; i < len(indexes); i++ {
|
||||
uChars[indexes[i]] = delimiter
|
||||
}
|
||||
return string(uChars), nil
|
||||
}
|
||||
|
||||
func (nf *nodeFilter) checkExclude(u rune, excludes ...rune) bool {
|
||||
if len(excludes) == 0 {
|
||||
return false
|
||||
}
|
||||
var exist bool
|
||||
for i, l := 0, len(excludes); i < l; i++ {
|
||||
if u == excludes[i] {
|
||||
exist = true
|
||||
break
|
||||
}
|
||||
}
|
||||
return exist
|
||||
}
|
||||
|
||||
func (nf *nodeFilter) doFilter(uChars []rune, data map[string]int) {
|
||||
var result []string
|
||||
ul := len(uChars)
|
||||
buf := new(bytes.Buffer)
|
||||
n := nf.root
|
||||
for i := 0; i < ul; i++ {
|
||||
if _, ok := n.child[uChars[i]]; !ok {
|
||||
continue
|
||||
}
|
||||
n = n.child[uChars[i]]
|
||||
buf.WriteRune(uChars[i])
|
||||
if n.end {
|
||||
result = append(result, buf.String())
|
||||
}
|
||||
for j := i + 1; j < ul; j++ {
|
||||
if _, ok := n.child[uChars[j]]; !ok {
|
||||
break
|
||||
}
|
||||
n = n.child[uChars[j]]
|
||||
buf.WriteRune(uChars[j])
|
||||
if n.end {
|
||||
result = append(result, buf.String())
|
||||
}
|
||||
}
|
||||
buf.Reset()
|
||||
n = nf.root
|
||||
}
|
||||
for i, l := 0, len(result); i < l; i++ {
|
||||
var c int
|
||||
if v, ok := data[result[i]]; ok {
|
||||
c = v
|
||||
}
|
||||
data[result[i]] = c + 1
|
||||
}
|
||||
}
|
||||
|
||||
func (nf *nodeFilter) doIndexes(uChars []rune) (indexes []int) {
|
||||
var (
|
||||
tIndexes []int
|
||||
ul = len(uChars)
|
||||
n = nf.root
|
||||
)
|
||||
for i := 0; i < ul; i++ {
|
||||
if _, ok := n.child[uChars[i]]; !ok {
|
||||
continue
|
||||
}
|
||||
n = n.child[uChars[i]]
|
||||
tIndexes = append(tIndexes, i)
|
||||
if n.end {
|
||||
indexes = nf.appendTo(indexes, tIndexes)
|
||||
tIndexes = nil
|
||||
}
|
||||
for j := i + 1; j < ul; j++ {
|
||||
if _, ok := n.child[uChars[j]]; !ok {
|
||||
break
|
||||
}
|
||||
n = n.child[uChars[j]]
|
||||
tIndexes = append(tIndexes, j)
|
||||
if n.end {
|
||||
indexes = nf.appendTo(indexes, tIndexes)
|
||||
}
|
||||
}
|
||||
if tIndexes != nil {
|
||||
tIndexes = nil
|
||||
}
|
||||
n = nf.root
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (nf *nodeFilter) appendTo(dst, src []int) []int {
|
||||
var t []int
|
||||
for i, il := 0, len(src); i < il; i++ {
|
||||
var exist bool
|
||||
for j, jl := 0, len(dst); j < jl; j++ {
|
||||
if src[i] == dst[j] {
|
||||
exist = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !exist {
|
||||
t = append(t, src[i])
|
||||
}
|
||||
}
|
||||
return append(dst, t...)
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
package filter
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"91porn-server/app/appg"
|
||||
"91porn-server/common/cache"
|
||||
"91porn-server/common/constant/redisconst"
|
||||
"91porn-server/common/log"
|
||||
"91porn-server/models/v/filtermod"
|
||||
"91porn-server/web/webg"
|
||||
)
|
||||
|
||||
var (
|
||||
WordCache = &cache.Cache{}
|
||||
WordFilter Filter
|
||||
TagFilter Filter
|
||||
|
||||
WordsFilterKey = "FILTER-WORDS"
|
||||
WordsReadStsKey = "isRead"
|
||||
|
||||
Read = "read"
|
||||
UnRead = "unread"
|
||||
|
||||
ReplaceMark = '*'
|
||||
)
|
||||
|
||||
// FilterStart 启动文本过滤器-(评论)
|
||||
func Start() {
|
||||
Init()
|
||||
InitTagFilter()
|
||||
}
|
||||
|
||||
func InitTagFilter() {
|
||||
log.Info("Init Tag Filter start...")
|
||||
TagFilter = NewNodeFilter(appg.Static.TagFilter)
|
||||
}
|
||||
|
||||
// Init Init
|
||||
func Init() {
|
||||
log.Info("Init WordsFilter start...")
|
||||
WordCache.New()
|
||||
// 从数据库获取过滤词
|
||||
words, err := GetFilterWordsFromDB()
|
||||
if err != nil {
|
||||
log.Error("WordFilter Init GetFilterWords error", log.E(err))
|
||||
os.Exit(1)
|
||||
}
|
||||
// 添加过滤词到缓存
|
||||
if err = WordCache.Add(WordsFilterKey, words, 0); err != nil {
|
||||
log.Error("WordFilter Init WordCache Add FILTER-WORD error", log.E(err), log.Any("words", words))
|
||||
os.Exit(1)
|
||||
}
|
||||
if len(words) > 0 {
|
||||
WordFilter = NewNodeFilter(words)
|
||||
// 添加过滤词到Redis
|
||||
if err = appg.Redis.Lpush(WordsFilterKey, words); err != nil {
|
||||
log.Error("WordFilter Init GetNewestFilterWord error", log.E(err), log.Any("words", words))
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
log.Info("Init WordsFilter end")
|
||||
}
|
||||
|
||||
// GetFilterWords 从Redis获取过滤词
|
||||
func GetFilterWordsFromDB() ([]string, error) {
|
||||
filterWords, err := filtermod.GetAllFilterWords()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
words := make([]string, len(filterWords))
|
||||
for i, v := range filterWords {
|
||||
words[i] = v.Word
|
||||
}
|
||||
return words, nil
|
||||
}
|
||||
|
||||
// GetFilterWords 从Redis获取过滤词
|
||||
func GetFilterWordsFromRedis() ([]string, error) {
|
||||
count, err := appg.Redis.LCount(WordsFilterKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return appg.Redis.LRange(WordsFilterKey, 0, count)
|
||||
}
|
||||
|
||||
// 检查是否有跟新
|
||||
func CheckUpdate() (*nodeFilter, error) {
|
||||
isRead, err := appg.Redis.Get(WordsReadStsKey)
|
||||
if err != nil {
|
||||
log.Error("CheckUpdate Redis Get error")
|
||||
return nil, err
|
||||
}
|
||||
if isRead != nil && *isRead == UnRead {
|
||||
words, err := GetFilterWordsFromRedis()
|
||||
if err != nil {
|
||||
log.Error("WordFilter CheckUpdate GetFilterWords error")
|
||||
return nil, err
|
||||
}
|
||||
WordCache.New()
|
||||
if err = WordCache.Add(WordsFilterKey, words, 0); err != nil {
|
||||
log.Error("WordFilter CheckUpdate WordCache Add error")
|
||||
return nil, err
|
||||
}
|
||||
WordFilter = NewNodeFilter(words)
|
||||
if err = appg.Redis.Set(WordsReadStsKey, Read, 0); err != nil {
|
||||
log.Error("WordFilter Redis Del error")
|
||||
return nil, err
|
||||
}
|
||||
var tmp interface{} = WordFilter
|
||||
var nf = tmp.(*nodeFilter)
|
||||
return nf, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// UpdateFilterWords web更新Redis
|
||||
func UpdateFilterWords() error {
|
||||
var (
|
||||
sensitiveWords []string
|
||||
)
|
||||
// 更新Redis
|
||||
if _, err := webg.Redis.Del(redisconst.SensitiveWordsCache); err != nil {
|
||||
log.Error("WordsFilter UpdateFilterWords Del error", log.E(err))
|
||||
return err
|
||||
}
|
||||
words, err := filtermod.GetAllFilterWords()
|
||||
if err != nil || len(words) <= 0 {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, w := range words {
|
||||
sensitiveWords = append(sensitiveWords, w.Word)
|
||||
}
|
||||
|
||||
if len(sensitiveWords) > 0 {
|
||||
// 放入缓存
|
||||
bytes, _ := json.Marshal(sensitiveWords)
|
||||
err := webg.Redis.Set(redisconst.SensitiveWordsCache, bytes, 10*time.Minute)
|
||||
if err != nil {
|
||||
log.Warn(fmt.Sprintf("redis set SensitiveWords err:%v", err))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user