169 lines
3.5 KiB
Go
169 lines
3.5 KiB
Go
package logic
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"time"
|
|
|
|
modentity "spark-server/internal/module/moderation/entity"
|
|
"spark-server/internal/pkg/sensitive"
|
|
)
|
|
|
|
// MemoryRepo is an in-process word list for logic tests (no MySQL).
|
|
type MemoryRepo struct {
|
|
mu sync.Mutex
|
|
words map[int64]*modentity.Word
|
|
nextID int64
|
|
m *sensitive.Matcher
|
|
hits []HitRecord
|
|
}
|
|
|
|
func NewMemoryRepo(words ...*modentity.Word) *MemoryRepo {
|
|
r := &MemoryRepo{words: map[int64]*modentity.Word{}, nextID: 1}
|
|
for _, w := range words {
|
|
if w == nil {
|
|
continue
|
|
}
|
|
if w.ID == 0 {
|
|
w.ID = r.nextID
|
|
r.nextID++
|
|
}
|
|
cp := *w
|
|
r.words[cp.ID] = &cp
|
|
}
|
|
r.rebuild()
|
|
return r
|
|
}
|
|
|
|
func (r *MemoryRepo) Ready() bool { return r != nil }
|
|
|
|
func (r *MemoryRepo) ReloadMatcher(ctx context.Context) error {
|
|
_ = ctx
|
|
r.rebuild()
|
|
return nil
|
|
}
|
|
|
|
func (r *MemoryRepo) rebuild() {
|
|
entries := make([]sensitive.Entry, 0, len(r.words))
|
|
for _, w := range r.words {
|
|
if w == nil || !w.Enabled || w.Word == "" {
|
|
continue
|
|
}
|
|
entries = append(entries, sensitive.Entry{ID: w.ID, Word: w.Word})
|
|
}
|
|
r.m = sensitive.Build(entries)
|
|
}
|
|
|
|
func (r *MemoryRepo) ListWords(ctx context.Context, _ string, _ int, _ *bool, _ int, _ int) ([]modentity.Word, int64, error) {
|
|
_ = ctx
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
var all []modentity.Word
|
|
for _, w := range r.words {
|
|
if w == nil {
|
|
continue
|
|
}
|
|
all = append(all, *w)
|
|
}
|
|
return all, int64(len(all)), nil
|
|
}
|
|
|
|
func (r *MemoryRepo) CreateWord(ctx context.Context, w *modentity.Word) error {
|
|
_ = ctx
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
if w.ID == 0 {
|
|
w.ID = r.nextID
|
|
r.nextID++
|
|
}
|
|
cp := *w
|
|
r.words[cp.ID] = &cp
|
|
r.rebuild()
|
|
return nil
|
|
}
|
|
|
|
func (r *MemoryRepo) UpdateWord(ctx context.Context, id int64, patch WordPatch) error {
|
|
_ = ctx
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
w := r.words[id]
|
|
if w == nil {
|
|
return ErrNotReady
|
|
}
|
|
cp := *w
|
|
if patch != nil {
|
|
if err := patch(&cp); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
r.words[id] = &cp
|
|
r.rebuild()
|
|
return nil
|
|
}
|
|
|
|
func (r *MemoryRepo) SoftDeleteWord(ctx context.Context, id int64) error {
|
|
return r.UpdateWord(ctx, id, func(w *modentity.Word) error {
|
|
w.Enabled = false
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func (r *MemoryRepo) InsertHits(ctx context.Context, hits []HitRecord) error {
|
|
_ = ctx
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
r.hits = append(r.hits, hits...)
|
|
return nil
|
|
}
|
|
|
|
func (r *MemoryRepo) ListHits(ctx context.Context, _ int64, _ int, _, _ *time.Time, _ int, _ int) ([]HitRecord, int64, error) {
|
|
_ = ctx
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
return r.hits, int64(len(r.hits)), nil
|
|
}
|
|
|
|
func (r *MemoryRepo) LookupWord(id int64) *modentity.Word {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
w := r.words[id]
|
|
if w == nil {
|
|
return nil
|
|
}
|
|
cp := *w
|
|
return &cp
|
|
}
|
|
|
|
func (r *MemoryRepo) MatcherFind(text string) ([]MatchDetail, string, bool) {
|
|
idx := sensitive.FoldAndIndex(text)
|
|
if r.m == nil || len(idx.Norm) == 0 {
|
|
return nil, text, true
|
|
}
|
|
raw := r.m.FindAll(idx.Norm)
|
|
if len(raw) == 0 {
|
|
return nil, text, true
|
|
}
|
|
intervals, ok := sensitive.IntervalsFromMatches(&idx, raw)
|
|
masked := text
|
|
if ok {
|
|
masked = sensitive.Mask(idx.OrigBytes, intervals, "***")
|
|
}
|
|
details := make([]MatchDetail, 0, len(raw))
|
|
for _, rm := range raw {
|
|
w := r.LookupWord(rm.EntryID)
|
|
if w == nil {
|
|
continue
|
|
}
|
|
from, to := 0, 0
|
|
if ok && rm.Start >= 0 && rm.End <= len(idx.OrigFrom) {
|
|
from = idx.OrigFrom[rm.Start]
|
|
to = idx.OrigTo[rm.End-1]
|
|
}
|
|
details = append(details, MatchDetail{
|
|
WordID: w.ID, Category: w.Category, Action: w.Action,
|
|
Excerpt: sensitive.Excerpt(idx.OrigBytes, from, to, 128), From: from, To: to,
|
|
})
|
|
}
|
|
return details, masked, ok
|
|
}
|