@@ -0,0 +1,114 @@
|
||||
package contentreviewser
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"91porn-server/models/v/contentreviewmod"
|
||||
)
|
||||
|
||||
const (
|
||||
markPrefix = "<mark>"
|
||||
markSuffix = "</mark>"
|
||||
)
|
||||
|
||||
// allTerms 把快照展开为一组待匹配的词
|
||||
func allTerms(snaps []contentreviewmod.SensitiveWordSnap) []string {
|
||||
seen := make(map[string]struct{})
|
||||
out := make([]string, 0, len(snaps))
|
||||
for _, s := range snaps {
|
||||
if s.Word == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[s.Word]; ok {
|
||||
continue
|
||||
}
|
||||
seen[s.Word] = struct{}{}
|
||||
out = append(out, s.Word)
|
||||
}
|
||||
// 长词优先,避免短词先替换破坏长词的匹配
|
||||
sort.Slice(out, func(i, j int) bool {
|
||||
return len(out[i]) > len(out[j])
|
||||
})
|
||||
return out
|
||||
}
|
||||
|
||||
// findHits 返回 input 中命中的词(去重,保持长→短顺序)
|
||||
func findHits(input string, terms []string) []string {
|
||||
if input == "" || len(terms) == 0 {
|
||||
return nil
|
||||
}
|
||||
hits := make([]string, 0)
|
||||
seen := make(map[string]struct{})
|
||||
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
|
||||
}
|
||||
|
||||
// highlight 用 <mark>...</mark> 包裹命中的词
|
||||
// hits 已按长度降序;为避免再次包裹已 mark 的内容,分段替换
|
||||
func highlight(input string, hits []string) string {
|
||||
if input == "" || len(hits) == 0 {
|
||||
return input
|
||||
}
|
||||
result := input
|
||||
for _, h := range hits {
|
||||
if h == "" {
|
||||
continue
|
||||
}
|
||||
result = replaceNotInMark(result, h)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// replaceNotInMark 在 input 中把 term 全部替换为 <mark>term</mark>,
|
||||
// 但跳过已经包在 <mark>...</mark> 内的片段(避免嵌套)
|
||||
func replaceNotInMark(input, term string) string {
|
||||
if term == "" {
|
||||
return input
|
||||
}
|
||||
var b strings.Builder
|
||||
i := 0
|
||||
for i < len(input) {
|
||||
// 已经在 mark 块里,整段原样输出
|
||||
if strings.HasPrefix(input[i:], markPrefix) {
|
||||
end := strings.Index(input[i:], markSuffix)
|
||||
if end < 0 {
|
||||
b.WriteString(input[i:])
|
||||
return b.String()
|
||||
}
|
||||
b.WriteString(input[i : i+end+len(markSuffix)])
|
||||
i += end + len(markSuffix)
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(input[i:], term) {
|
||||
b.WriteString(markPrefix)
|
||||
b.WriteString(term)
|
||||
b.WriteString(markSuffix)
|
||||
i += len(term)
|
||||
continue
|
||||
}
|
||||
b.WriteByte(input[i])
|
||||
i++
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// matchAndHighlight 在 input 上一步到位:返回命中词列表 + 高亮后的文本
|
||||
func matchAndHighlight(input string, terms []string) ([]string, string) {
|
||||
hits := findHits(input, terms)
|
||||
if len(hits) == 0 {
|
||||
return nil, input
|
||||
}
|
||||
return hits, highlight(input, hits)
|
||||
}
|
||||
Reference in New Issue
Block a user