@@ -0,0 +1,170 @@
|
||||
package vidser
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"91porn-server/common/log"
|
||||
"91porn-server/models/v/sensitivewordmod"
|
||||
"91porn-server/models/v/vidmod"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
// CheckShelvingFields 直接对一组文本做敏感词命中检测;用于单帖编辑场景,
|
||||
// 调用方负责把请求字段与 DB 现值合并后传入。
|
||||
// 命中规则与 CheckShelvingByIDs 一致:Title / Content / RichText 任一命中即视为违规。
|
||||
func CheckShelvingFields(id primitive.ObjectID, title, content, richText string) error {
|
||||
terms := loadEnabledSensitiveTerms()
|
||||
if len(terms) == 0 {
|
||||
return nil
|
||||
}
|
||||
titleHits := findSensitiveHits(title, terms)
|
||||
contentHits := findSensitiveHits(content, terms)
|
||||
richHits := findSensitiveHits(richText, terms)
|
||||
if len(titleHits) == 0 && len(contentHits) == 0 && len(richHits) == 0 {
|
||||
return nil
|
||||
}
|
||||
return errors.New(formatHitLine(id, title, titleHits, contentHits, richHits))
|
||||
}
|
||||
|
||||
// CheckShelvingByIDs 上架前敏感词命中检测。
|
||||
//
|
||||
// 与 skd/service/contentreviewser VIDEO 任务的扫描规则保持一致:
|
||||
// 对每条视频的 Title / Content / RichText 三个字段做 substring 命中检测,
|
||||
// 任一字段命中启用状态的敏感词词条即视为违规,不予上架。
|
||||
//
|
||||
// 返回值:
|
||||
// - nil —— 全部通过或敏感词库为空
|
||||
// - non-nil err —— 至少一条命中,err.Error() 已按视频聚合
|
||||
//
|
||||
// 多条命中时,每条视频独占一行,行内字段按"标题命中 / 内容命中 / 富文本命中"顺序拼接。
|
||||
func CheckShelvingByIDs(ids []primitive.ObjectID) error {
|
||||
if len(ids) == 0 {
|
||||
return nil
|
||||
}
|
||||
terms := loadEnabledSensitiveTerms()
|
||||
if len(terms) == 0 {
|
||||
return nil
|
||||
}
|
||||
videos, err := vidmod.GetVideoListByIDsNoStatus(ids)
|
||||
if err != nil {
|
||||
log.Error("vidser.CheckShelvingByIDs query videos fail", log.Any("ids", ids), log.E(err))
|
||||
return errors.New("查询视频信息失败")
|
||||
}
|
||||
lines := make([]string, 0)
|
||||
for _, v := range videos {
|
||||
titleHits := findSensitiveHits(v.Title, terms)
|
||||
contentHits := findSensitiveHits(v.Content, terms)
|
||||
richHits := findSensitiveHits(v.RichText, terms)
|
||||
if len(titleHits) == 0 && len(contentHits) == 0 && len(richHits) == 0 {
|
||||
continue
|
||||
}
|
||||
lines = append(lines, formatHitLine(v.ID, v.Title, titleHits, contentHits, richHits))
|
||||
}
|
||||
if len(lines) == 0 {
|
||||
return nil
|
||||
}
|
||||
return errors.New(strings.Join(lines, "\n"))
|
||||
}
|
||||
|
||||
// LoadEnabledSensitiveTerms 启用状态敏感词词条加载(代理 sensitivewordmod.LoadEnabledTerms)
|
||||
// 保留此处签名以兼容现有 laosijiser / tianyuctrl 等调用方
|
||||
func LoadEnabledSensitiveTerms() []string {
|
||||
return sensitivewordmod.LoadEnabledTerms()
|
||||
}
|
||||
|
||||
// loadEnabledSensitiveTerms 内部短名别名
|
||||
func loadEnabledSensitiveTerms() []string {
|
||||
return sensitivewordmod.LoadEnabledTerms()
|
||||
}
|
||||
|
||||
// SensitiveHit 单条命中结果,用于批量同步接口(老司机/天宇媒资库)返回给前端
|
||||
type SensitiveHit struct {
|
||||
SourceID string `json:"sourceId"` // 来源 id(如 laosiji 资源 id / tianyu fileId)
|
||||
Title string `json:"title"` // 标题
|
||||
Detail string `json:"detail"` // "标题命中 XX 内容命中 YY 富文本命中 ZZ"
|
||||
}
|
||||
|
||||
// CheckTextHits 对一组文本做敏感词命中检测,命中返回 *SensitiveHit,未命中返回 nil。
|
||||
// 与 skd 内容审查任务的匹配规则保持一致:任一字段命中即视为命中。
|
||||
// 调用方在入库前调用,命中后应将记录的 status 强制为"未上架"语义的值。
|
||||
//
|
||||
// 词条由外部传入,便于批量场景一次加载、多次匹配;传 nil 或空切片视为词库为空,直接返回 nil。
|
||||
func CheckTextHits(terms []string, sourceID, title, content, richText string) *SensitiveHit {
|
||||
if len(terms) == 0 {
|
||||
return nil
|
||||
}
|
||||
titleHits := findSensitiveHits(title, terms)
|
||||
contentHits := findSensitiveHits(content, terms)
|
||||
richHits := findSensitiveHits(richText, terms)
|
||||
if len(titleHits) == 0 && len(contentHits) == 0 && len(richHits) == 0 {
|
||||
return nil
|
||||
}
|
||||
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 &SensitiveHit{
|
||||
SourceID: sourceID,
|
||||
Title: title,
|
||||
Detail: strings.Join(parts, " "),
|
||||
}
|
||||
}
|
||||
|
||||
// SensitiveForcedOfflineTip 写库后给管理员的统一提示文案
|
||||
const SensitiveForcedOfflineTip = "由于部分数据内容命中敏感词,状态强制为下架"
|
||||
|
||||
// findSensitiveHits substring 命中检测的内部别名,代理 sensitivewordmod.MatchHits
|
||||
func findSensitiveHits(input string, terms []string) []string {
|
||||
return sensitivewordmod.MatchHits(input, terms)
|
||||
}
|
||||
|
||||
// shouldCheckSensitiveOnUpdate 判断单帖编辑 UpdateVidInfo 是否需要做敏感词校验
|
||||
//
|
||||
// 1. status 被切到 上架(CheckPass=1) / 上架并免费(Free=3) —— 上架转换
|
||||
// 2. 当前已是上架态(1/3) 且本次更新涉及 Title/Content/RichText —— 已上架内容改动
|
||||
func shouldCheckSensitiveOnUpdate(vidInfo vidmod.VideoModel, req vidmod.EditReq) bool {
|
||||
if req.Status != nil && (*req.Status == vidmod.CheckPass || *req.Status == vidmod.Free) {
|
||||
return true
|
||||
}
|
||||
isCurrentlyShelved := vidInfo.Status == vidmod.CheckPass || vidInfo.Status == vidmod.Free
|
||||
touchesText := req.Title != nil || req.Content != nil || req.RichText != nil
|
||||
return isCurrentlyShelved && touchesText
|
||||
}
|
||||
|
||||
// parseObjectIDs 把十六进制 id 字符串数组转 ObjectID,非法 id 静默丢弃
|
||||
func parseObjectIDs(ids []string) []primitive.ObjectID {
|
||||
out := make([]primitive.ObjectID, 0, len(ids))
|
||||
for _, s := range ids {
|
||||
oid, err := primitive.ObjectIDFromHex(s)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
out = append(out, oid)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// formatHitLine 按需求格式拼接单条命中描述:
|
||||
// "id:xxxx 对象标题 XXXXXX。 标题命中 XX 内容命中 XX 富文本命中 XX,不予上架成功"
|
||||
func formatHitLine(id primitive.ObjectID, title string, 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 fmt.Sprintf("id:%s 对象标题 %s。 %s,不予上架成功", id.Hex(), title, strings.Join(parts, " "))
|
||||
}
|
||||
Reference in New Issue
Block a user