423 lines
12 KiB
Go
423 lines
12 KiB
Go
// Copyright © 2023 OpenIM open source community. All rights reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package chat
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"git.imall.cloud/openim/chat/pkg/common/db/table/chat"
|
|
"github.com/openimsdk/tools/db/mongoutil"
|
|
"github.com/openimsdk/tools/db/pagination"
|
|
"github.com/openimsdk/tools/errs"
|
|
"go.mongodb.org/mongo-driver/bson"
|
|
"go.mongodb.org/mongo-driver/mongo"
|
|
"go.mongodb.org/mongo-driver/mongo/options"
|
|
)
|
|
|
|
func NewSensitiveWord(db *mongo.Database) (chat.SensitiveWordInterface, error) {
|
|
coll := db.Collection("sensitive_words")
|
|
_, err := coll.Indexes().CreateMany(context.Background(), []mongo.IndexModel{
|
|
{
|
|
Keys: bson.D{
|
|
{Key: "word", Value: 1},
|
|
},
|
|
Options: options.Index().SetUnique(true),
|
|
},
|
|
{
|
|
Keys: bson.D{
|
|
{Key: "status", Value: 1},
|
|
},
|
|
},
|
|
})
|
|
if err != nil {
|
|
return nil, errs.Wrap(err)
|
|
}
|
|
|
|
// 创建配置集合
|
|
configColl := db.Collection("sensitive_word_configs")
|
|
_, err = configColl.Indexes().CreateOne(context.Background(), mongo.IndexModel{
|
|
Keys: bson.D{
|
|
{Key: "enable_filter", Value: 1},
|
|
},
|
|
})
|
|
if err != nil {
|
|
return nil, errs.Wrap(err)
|
|
}
|
|
|
|
// 检查并初始化默认配置
|
|
ctx := context.Background()
|
|
count, err := configColl.CountDocuments(ctx, bson.M{})
|
|
if err != nil {
|
|
return nil, errs.Wrap(err)
|
|
}
|
|
|
|
if count == 0 {
|
|
// 创建默认配置
|
|
defaultConfig := &chat.SensitiveWordConfig{
|
|
ID: "default",
|
|
EnableFilter: true,
|
|
FilterMode: 1,
|
|
ReplaceChar: "***",
|
|
WhitelistUsers: []string{},
|
|
WhitelistGroups: []string{},
|
|
LogEnabled: true,
|
|
AutoApprove: false,
|
|
UpdateTime: time.Now(),
|
|
}
|
|
|
|
_, err = configColl.InsertOne(ctx, defaultConfig)
|
|
if err != nil {
|
|
return nil, errs.Wrap(err)
|
|
}
|
|
}
|
|
|
|
return &SensitiveWord{coll: coll, configColl: configColl}, nil
|
|
}
|
|
|
|
type SensitiveWord struct {
|
|
coll *mongo.Collection
|
|
configColl *mongo.Collection
|
|
}
|
|
|
|
// ==================== 敏感词管理 ====================
|
|
|
|
func (o *SensitiveWord) CreateSensitiveWord(ctx context.Context, word *chat.SensitiveWord) error {
|
|
word.CreateTime = time.Now()
|
|
word.UpdateTime = time.Now()
|
|
return mongoutil.InsertMany(ctx, o.coll, []*chat.SensitiveWord{word})
|
|
}
|
|
|
|
func (o *SensitiveWord) UpdateSensitiveWord(ctx context.Context, id string, data map[string]any) error {
|
|
data["update_time"] = time.Now()
|
|
return mongoutil.UpdateOne(ctx, o.coll, bson.M{"_id": id}, bson.M{"$set": data}, false)
|
|
}
|
|
|
|
func (o *SensitiveWord) DeleteSensitiveWord(ctx context.Context, ids []string) error {
|
|
return mongoutil.DeleteMany(ctx, o.coll, bson.M{"_id": bson.M{"$in": ids}})
|
|
}
|
|
|
|
func (o *SensitiveWord) GetSensitiveWord(ctx context.Context, id string) (*chat.SensitiveWord, error) {
|
|
return mongoutil.FindOne[*chat.SensitiveWord](ctx, o.coll, bson.M{"_id": id})
|
|
}
|
|
|
|
func (o *SensitiveWord) SearchSensitiveWords(ctx context.Context, keyword string, action int32, status int32, pagination pagination.Pagination) (int64, []*chat.SensitiveWord, error) {
|
|
filter := bson.M{}
|
|
if keyword != "" {
|
|
filter["word"] = bson.M{"$regex": keyword, "$options": "i"}
|
|
}
|
|
if action > 0 {
|
|
filter["action"] = action
|
|
}
|
|
if status > 0 {
|
|
filter["status"] = status
|
|
}
|
|
|
|
return mongoutil.FindPage[*chat.SensitiveWord](ctx, o.coll, filter, pagination)
|
|
}
|
|
|
|
func (o *SensitiveWord) GetAllSensitiveWords(ctx context.Context) ([]*chat.SensitiveWord, error) {
|
|
return mongoutil.Find[*chat.SensitiveWord](ctx, o.coll, bson.M{})
|
|
}
|
|
|
|
func (o *SensitiveWord) GetEnabledSensitiveWords(ctx context.Context) ([]*chat.SensitiveWord, error) {
|
|
return mongoutil.Find[*chat.SensitiveWord](ctx, o.coll, bson.M{"status": chat.SensitiveStatusEnabled})
|
|
}
|
|
|
|
// ==================== 敏感词检测和过滤 ====================
|
|
|
|
func (o *SensitiveWord) CheckSensitiveWords(ctx context.Context, content string) ([]*chat.SensitiveWord, error) {
|
|
words, err := o.GetEnabledSensitiveWords(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var matchedWords []*chat.SensitiveWord
|
|
contentLower := strings.ToLower(content)
|
|
|
|
for _, word := range words {
|
|
if strings.Contains(contentLower, strings.ToLower(word.Word)) {
|
|
matchedWords = append(matchedWords, word)
|
|
}
|
|
}
|
|
|
|
return matchedWords, nil
|
|
}
|
|
|
|
func (o *SensitiveWord) FilterContent(ctx context.Context, content string) (string, []*chat.SensitiveWord, error) {
|
|
words, err := o.CheckSensitiveWords(ctx, content)
|
|
if err != nil {
|
|
return content, nil, err
|
|
}
|
|
|
|
if len(words) == 0 {
|
|
return content, words, nil
|
|
}
|
|
|
|
filteredContent := content
|
|
for _, word := range words {
|
|
replaceChar := "***"
|
|
filteredContent = strings.ReplaceAll(filteredContent, word.Word, replaceChar)
|
|
}
|
|
|
|
return filteredContent, words, nil
|
|
}
|
|
|
|
// ==================== 敏感词日志管理 ====================
|
|
|
|
func (o *SensitiveWord) CreateSensitiveWordLog(ctx context.Context, log *chat.SensitiveWordLog) error {
|
|
log.CreateTime = time.Now()
|
|
return mongoutil.InsertMany(ctx, o.coll.Database().Collection("sensitive_word_logs"), []*chat.SensitiveWordLog{log})
|
|
}
|
|
|
|
func (o *SensitiveWord) GetSensitiveWordLogs(ctx context.Context, userID, groupID string, pagination pagination.Pagination) (int64, []*chat.SensitiveWordLog, error) {
|
|
filter := bson.M{}
|
|
if userID != "" {
|
|
filter["user_id"] = userID
|
|
}
|
|
if groupID != "" {
|
|
filter["group_id"] = groupID
|
|
}
|
|
|
|
coll := o.coll.Database().Collection("sensitive_word_logs")
|
|
return mongoutil.FindPage[*chat.SensitiveWordLog](ctx, coll, filter, pagination)
|
|
}
|
|
|
|
func (o *SensitiveWord) DeleteSensitiveWordLogs(ctx context.Context, ids []string) error {
|
|
coll := o.coll.Database().Collection("sensitive_word_logs")
|
|
return mongoutil.DeleteMany(ctx, coll, bson.M{"_id": bson.M{"$in": ids}})
|
|
}
|
|
|
|
// ==================== 敏感词分组管理 ====================
|
|
|
|
func (o *SensitiveWord) CreateSensitiveWordGroup(ctx context.Context, group *chat.SensitiveWordGroup) error {
|
|
group.CreateTime = time.Now()
|
|
group.UpdateTime = time.Now()
|
|
return mongoutil.InsertMany(ctx, o.coll.Database().Collection("sensitive_word_groups"), []*chat.SensitiveWordGroup{group})
|
|
}
|
|
|
|
func (o *SensitiveWord) UpdateSensitiveWordGroup(ctx context.Context, id string, data map[string]any) error {
|
|
data["update_time"] = time.Now()
|
|
coll := o.coll.Database().Collection("sensitive_word_groups")
|
|
return mongoutil.UpdateOne(ctx, coll, bson.M{"_id": id}, bson.M{"$set": data}, false)
|
|
}
|
|
|
|
func (o *SensitiveWord) DeleteSensitiveWordGroup(ctx context.Context, ids []string) error {
|
|
coll := o.coll.Database().Collection("sensitive_word_groups")
|
|
return mongoutil.DeleteMany(ctx, coll, bson.M{"_id": bson.M{"$in": ids}})
|
|
}
|
|
|
|
func (o *SensitiveWord) GetSensitiveWordGroup(ctx context.Context, id string) (*chat.SensitiveWordGroup, error) {
|
|
coll := o.coll.Database().Collection("sensitive_word_groups")
|
|
return mongoutil.FindOne[*chat.SensitiveWordGroup](ctx, coll, bson.M{"_id": id})
|
|
}
|
|
|
|
func (o *SensitiveWord) GetAllSensitiveWordGroups(ctx context.Context) ([]*chat.SensitiveWordGroup, error) {
|
|
coll := o.coll.Database().Collection("sensitive_word_groups")
|
|
return mongoutil.Find[*chat.SensitiveWordGroup](ctx, coll, bson.M{})
|
|
}
|
|
|
|
// ==================== 敏感词配置管理 ====================
|
|
|
|
func (o *SensitiveWord) GetSensitiveWordConfig(ctx context.Context) (*chat.SensitiveWordConfig, error) {
|
|
// 查找配置(默认配置已在初始化时创建)
|
|
config, err := mongoutil.FindOne[*chat.SensitiveWordConfig](ctx, o.configColl, bson.M{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return config, nil
|
|
}
|
|
|
|
func (o *SensitiveWord) UpdateSensitiveWordConfig(ctx context.Context, config *chat.SensitiveWordConfig) error {
|
|
config.UpdateTime = time.Now()
|
|
if config.ID == "" {
|
|
config.ID = "default"
|
|
}
|
|
filter := bson.M{"_id": config.ID}
|
|
fmt.Println("UpdateSensitiveWordConfig", "_________55", config, o.configColl.Name())
|
|
err := mongoutil.UpdateOne(ctx, o.configColl, filter, bson.M{"$set": config}, true)
|
|
fmt.Println("UpdateSensitiveWordConfig", "_________44", config)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (o *SensitiveWord) IsFilterEnabled(ctx context.Context) (bool, error) {
|
|
config, err := o.GetSensitiveWordConfig(ctx)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return config.EnableFilter, nil
|
|
}
|
|
|
|
func (o *SensitiveWord) GetFilterMode(ctx context.Context) (int32, error) {
|
|
// 简化实现,返回默认值
|
|
return 1, nil
|
|
}
|
|
|
|
func (o *SensitiveWord) GetReplaceChar(ctx context.Context) (string, error) {
|
|
config, err := o.GetSensitiveWordConfig(ctx)
|
|
if err != nil {
|
|
return "***", err
|
|
}
|
|
if config.ReplaceChar == "" {
|
|
return "***", nil
|
|
}
|
|
return config.ReplaceChar, nil
|
|
}
|
|
|
|
func (o *SensitiveWord) IsUserInWhitelist(ctx context.Context, userID string) (bool, error) {
|
|
config, err := o.GetSensitiveWordConfig(ctx)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
for _, id := range config.WhitelistUsers {
|
|
if id == userID {
|
|
return true, nil
|
|
}
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
func (o *SensitiveWord) IsGroupInWhitelist(ctx context.Context, groupID string) (bool, error) {
|
|
config, err := o.GetSensitiveWordConfig(ctx)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
for _, id := range config.WhitelistGroups {
|
|
if id == groupID {
|
|
return true, nil
|
|
}
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
// ==================== 批量操作 ====================
|
|
|
|
func (o *SensitiveWord) BatchCreateSensitiveWords(ctx context.Context, words []*chat.SensitiveWord) error {
|
|
now := time.Now()
|
|
for _, word := range words {
|
|
word.CreateTime = now
|
|
word.UpdateTime = now
|
|
}
|
|
return mongoutil.InsertMany(ctx, o.coll, words)
|
|
}
|
|
|
|
func (o *SensitiveWord) BatchUpdateSensitiveWords(ctx context.Context, updates map[string]map[string]any) error {
|
|
for id, data := range updates {
|
|
data["update_time"] = time.Now()
|
|
err := mongoutil.UpdateOne(ctx, o.coll, bson.M{"_id": id}, bson.M{"$set": data}, false)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (o *SensitiveWord) BatchDeleteSensitiveWords(ctx context.Context, ids []string) error {
|
|
return mongoutil.DeleteMany(ctx, o.coll, bson.M{"_id": bson.M{"$in": ids}})
|
|
}
|
|
|
|
// ==================== 统计信息 ====================
|
|
|
|
func (o *SensitiveWord) GetSensitiveWordStats(ctx context.Context) (map[string]int64, error) {
|
|
stats := make(map[string]int64)
|
|
|
|
// 总数
|
|
total, err := mongoutil.Count(ctx, o.coll, bson.M{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
stats["total"] = total
|
|
|
|
// 启用数量
|
|
enabled, err := mongoutil.Count(ctx, o.coll, bson.M{"status": chat.SensitiveStatusEnabled})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
stats["enabled"] = enabled
|
|
|
|
// 禁用数量
|
|
disabled, err := mongoutil.Count(ctx, o.coll, bson.M{"status": chat.SensitiveStatusDisabled})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
stats["disabled"] = disabled
|
|
|
|
// 替换模式数量
|
|
replace, err := mongoutil.Count(ctx, o.coll, bson.M{"action": chat.SensitiveActionReplace})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
stats["replace"] = replace
|
|
|
|
// 拦截模式数量
|
|
block, err := mongoutil.Count(ctx, o.coll, bson.M{"action": chat.SensitiveActionBlock})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
stats["block"] = block
|
|
|
|
return stats, nil
|
|
}
|
|
|
|
func (o *SensitiveWord) GetSensitiveWordLogStats(ctx context.Context, startTime, endTime time.Time) (map[string]int64, error) {
|
|
stats := make(map[string]int64)
|
|
coll := o.coll.Database().Collection("sensitive_word_logs")
|
|
|
|
filter := bson.M{
|
|
"create_time": bson.M{
|
|
"$gte": startTime,
|
|
"$lte": endTime,
|
|
},
|
|
}
|
|
|
|
// 总数
|
|
total, err := mongoutil.Count(ctx, coll, filter)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
stats["total"] = total
|
|
|
|
// 替换数量
|
|
replaceFilter := bson.M{}
|
|
for k, v := range filter {
|
|
replaceFilter[k] = v
|
|
}
|
|
replaceFilter["action"] = chat.SensitiveActionReplace
|
|
replace, err := mongoutil.Count(ctx, coll, replaceFilter)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
stats["replace"] = replace
|
|
|
|
// 拦截数量
|
|
blockFilter := bson.M{}
|
|
for k, v := range filter {
|
|
blockFilter[k] = v
|
|
}
|
|
blockFilter["action"] = chat.SensitiveActionBlock
|
|
block, err := mongoutil.Count(ctx, coll, blockFilter)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
stats["block"] = block
|
|
|
|
return stats, nil
|
|
}
|