Initial commit

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-15 13:57:10 +08:00
co-authored by Claude Opus 5
commit 8679200f41
1897 changed files with 257900 additions and 0 deletions
+165
View File
@@ -0,0 +1,165 @@
package sensitivewordmod
import (
"fmt"
"time"
"91porn-server/common/db"
"91porn-server/common/log"
"91porn-server/models"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
var mdb *db.MongoDB
const table = models.SensitiveWord
func coll(t *db.MongoTool) *db.MongoTool {
if t == nil {
return mdb.Coll(table)
}
return t.Coll(table)
}
func Init() {
mdb = db.Init(table)
initIndex()
}
func initIndex() {
many := []mongo.IndexModel{
{
Keys: bson.D{{Key: "word", Value: 1}},
Options: options.Index().SetUnique(true),
},
{
Keys: bson.D{{Key: "category", Value: 1}},
},
{
Keys: bson.D{{Key: "status", Value: 1}},
},
}
if _, err := coll(nil).CreateIndex(many); err != nil {
panic(fmt.Sprintf("%s model set index err ==>[%+v]", table, err))
}
}
// Add 新增单条
func Add(w *SensitiveWord) error {
now := time.Now()
w.CreatedAt = now
w.UpdatedAt = now
w.Status = StatusEnabled
if _, err := coll(nil).InsertOne(w); err != nil {
log.Error(fmt.Sprintf("[sensitivewordmod] Add fail: %+v", err))
return err
}
return nil
}
// UpsertByWord 按主词 upsert(导入时使用)
func UpsertByWord(w *SensitiveWord) error {
now := time.Now()
filter := bson.M{"word": w.Word}
update := bson.M{
"$set": bson.M{
"category": w.Category,
"updatedAt": now,
},
"$setOnInsert": bson.M{
"status": StatusEnabled,
"createdAt": now,
},
}
if _, err := coll(nil).UpsertOne(filter, update); err != nil {
log.Error(fmt.Sprintf("[sensitivewordmod] UpsertByWord fail: %+v", err), log.Any("word", w.Word))
return err
}
return nil
}
// Update 更新单条
func Update(id primitive.ObjectID, fields bson.M) error {
fields["updatedAt"] = time.Now()
cond := bson.M{"_id": id}
if _, err := coll(nil).UpdateOne(cond, bson.M{"$set": fields}); err != nil {
log.Error(fmt.Sprintf("[sensitivewordmod] Update fail: %+v", err))
return err
}
return nil
}
// Delete 批量删除
func Delete(ids []primitive.ObjectID) (int64, error) {
cond := bson.M{"_id": bson.M{"$in": ids}}
result, err := coll(nil).DeleteMany(cond)
if err != nil {
log.Error(fmt.Sprintf("[sensitivewordmod] Delete fail: %+v", err))
return 0, err
}
return result.DeletedCount, nil
}
// List 分页查询
func List(req *ListReq) ([]*SensitiveWord, int64, error) {
cond := buildCond(req)
total, err := coll(nil).Count(cond)
if err != nil {
return nil, 0, err
}
page := req.Page
size := req.Size
if page < 1 {
page = 1
}
if size < 1 {
size = 20
}
skip := (page - 1) * size
opts := options.FindOptions{}
opts.SetSort(bson.D{{Key: "createdAt", Value: -1}}).
SetSkip(skip).
SetLimit(size)
var list []*SensitiveWord
if err = coll(nil).Find(&list, cond, &opts); err != nil {
log.Error(fmt.Sprintf("[sensitivewordmod] List find fail: %+v", err))
return nil, total, err
}
return list, total, nil
}
// FindAll 查询全部(导出时使用)
func FindAll(req *ListReq) ([]*SensitiveWord, error) {
cond := buildCond(req)
opts := options.FindOptions{}
opts.SetSort(bson.D{{Key: "category", Value: 1}, {Key: "createdAt", Value: 1}})
var list []*SensitiveWord
if err := coll(nil).Find(&list, cond, &opts); err != nil {
log.Error(fmt.Sprintf("[sensitivewordmod] FindAll fail: %+v", err))
return nil, err
}
return list, nil
}
func buildCond(req *ListReq) bson.M {
cond := bson.M{}
if req.Category != "" {
cond["category"] = req.Category
}
if req.Status != nil {
cond["status"] = *req.Status
}
if req.Keyword != "" {
cond["word"] = bson.M{"$regex": req.Keyword, "$options": "i"}
}
return cond
}
+64
View File
@@ -0,0 +1,64 @@
package sensitivewordmod
import (
"strings"
"91porn-server/common/log"
)
// LoadEnabledTerms 加载启用状态的敏感词词条。
// DB 失败时降级返回空切片,调用方应视为"词库为空,不命中",避免阻塞业务流程。
func LoadEnabledTerms() []string {
enabled := StatusEnabled
list, err := FindAll(&ListReq{Status: &enabled})
if err != nil {
log.Error("sensitivewordmod LoadEnabledTerms fail", log.E(err))
return nil
}
out := make([]string, 0, len(list))
for _, w := range list {
if w.Word != "" {
out = append(out, w.Word)
}
}
return out
}
// MatchHits 对单个文本做 substring 命中检测,返回去重后的命中词。
// 与 skd/service/contentreviewser/matcher.findHits 行为一致。
func MatchHits(input string, terms []string) []string {
if input == "" || len(terms) == 0 {
return nil
}
seen := make(map[string]struct{})
hits := make([]string, 0)
for _, t := range terms {
if t == "" {
continue
}
if _, ok := seen[t]; ok {
continue
}
if strings.Contains(input, t) {
seen[t] = struct{}{}
hits = append(hits, t)
}
}
return hits
}
// FormatHitDetail 把三类字段的命中拼成 "标题命中 XX 内容命中 YY 富文本命中 ZZ"。
// 任一字段未命中则该段省略;全部为空时返回空串。
func FormatHitDetail(titleHits, contentHits, richHits []string) string {
parts := make([]string, 0, 3)
if len(titleHits) > 0 {
parts = append(parts, "标题命中 "+strings.Join(titleHits, "、"))
}
if len(contentHits) > 0 {
parts = append(parts, "内容命中 "+strings.Join(contentHits, "、"))
}
if len(richHits) > 0 {
parts = append(parts, "富文本命中 "+strings.Join(richHits, "、"))
}
return strings.Join(parts, " ")
}
+51
View File
@@ -0,0 +1,51 @@
package sensitivewordmod
import (
"time"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// Status 启用状态
const (
StatusEnabled = 1
StatusDisabled = 0
)
// SensitiveWord 敏感词
type SensitiveWord struct {
ID primitive.ObjectID `json:"id" bson:"_id,omitempty"`
Category string `json:"category" bson:"category"` // 一级分类
Word string `json:"word" bson:"word"` // 词条
Status int `json:"status" bson:"status"` // 1-启用 0-禁用
CreatedAt time.Time `json:"createdAt" bson:"createdAt"`
UpdatedAt time.Time `json:"updatedAt" bson:"updatedAt"`
}
// ListReq 查询请求
type ListReq struct {
Page int64 `json:"page" form:"page"`
Size int64 `json:"size" form:"size"`
Category string `json:"category" form:"category"`
Keyword string `json:"keyword" form:"keyword"` // 按词条模糊搜索
Status *int `json:"status" form:"status"`
}
// AddReq 新增请求
type AddReq struct {
Category string `json:"category" binding:"required"`
Word string `json:"word" binding:"required"`
}
// UpdateReq 编辑请求
type UpdateReq struct {
ID string `json:"id" binding:"required"`
Category string `json:"category"`
Word string `json:"word"`
Status *int `json:"status"`
}
// DeleteReq 删除请求
type DeleteReq struct {
IDs []string `json:"ids" binding:"required"`
}