package contentreviewser import ( "sort" "strings" "91porn-server/models/v/contentreviewmod" ) const ( markPrefix = "" markSuffix = "" ) // 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 用 ... 包裹命中的词 // 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 全部替换为 term, // 但跳过已经包在 ... 内的片段(避免嵌套) 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) }