package sys import ( "context" "crypto/sha256" "encoding/hex" "strings" "hotgo/internal/consts" "hotgo/internal/dao" "hotgo/internal/library/contexts" "hotgo/internal/library/imtenant" "hotgo/internal/model/input/sysin" "hotgo/internal/service" "github.com/gogf/gf/v2/database/gdb" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/os/gtime" ) type sSysImSensitiveWord struct{} func NewSysImSensitiveWord() *sSysImSensitiveWord { return &sSysImSensitiveWord{} } func init() { service.RegisterSysImSensitiveWord(NewSysImSensitiveWord()) } func normalizedSensitiveWord(value string) (string, string) { normalized := strings.ToLower(strings.TrimSpace(value)) sum := sha256.Sum256([]byte(normalized)) return normalized, hex.EncodeToString(sum[:]) } func (s *sSysImSensitiveWord) List(ctx context.Context, in *sysin.ImSensitiveWordListInp) (list []*sysin.ImSensitiveWordListModel, total int, err error) { if err = in.Filter(ctx); err != nil { return } tenantID, _, err := resolveImPolicyTenant(ctx, in.TenantId) if err != nil { return nil, 0, err } c := dao.ImSensitiveWords.Columns() gc := dao.ImSensitiveWordGroups.Columns() model := imtenant.RegistryDB().Model(dao.ImSensitiveWords.Table()+" w").Safe().Ctx(ctx).LeftJoin(dao.ImSensitiveWordGroups.Table()+" g", "g."+gc.Id+"=w."+c.GroupId).Fields("w."+c.Id, "w."+c.TenantId, "w."+c.GroupId, "g."+gc.GroupName, "w."+c.Word, "w."+c.MatchType, "CAST(w."+c.SceneJson+" AS CHAR) scene_json", "w."+c.Action, "w."+c.Replacement, "w."+c.RiskLevel, "w."+c.Priority, "w."+c.Status, "w."+c.Remark, "w."+c.CreatedAt, "w."+c.UpdatedAt).Where("w."+c.TenantId, tenantID).WhereNull("w." + c.DeletedAt) if in.GroupId > 0 { model = model.Where("w."+c.GroupId, in.GroupId) } if in.Word != "" { model = model.WhereLike("w."+c.Word, "%"+in.Word+"%") } if in.MatchType != "" { model = model.Where("w."+c.MatchType, in.MatchType) } if in.Action != "" { model = model.Where("w."+c.Action, in.Action) } if in.RiskLevel != "" { model = model.Where("w."+c.RiskLevel, in.RiskLevel) } if in.Status != 0 { model = model.Where("w."+c.Status, in.Status) } err = model.Page(in.Page, in.PerPage).OrderDesc("w."+c.Priority).OrderDesc("w."+c.Id).ScanAndCount(&list, &total, false) if err != nil { err = gerror.Wrap(err, "获取敏感词列表失败") } return } func (s *sSysImSensitiveWord) Edit(ctx context.Context, in *sysin.ImSensitiveWordEditInp) error { if err := in.Filter(ctx); err != nil { return err } tenantID, tenantCode, err := resolveImPolicyTenant(ctx, in.TenantId) if err != nil { return err } if err = validateSensitiveWordGroup(ctx, tenantID, in.GroupId); err != nil { return err } _, hash := normalizedSensitiveWord(in.Word) c := dao.ImSensitiveWords.Columns() db := imtenant.RegistryDB() now := gtime.Now() data := g.Map{c.TenantId: tenantID, c.GroupId: in.GroupId, c.Word: in.Word, c.WordHash: hash, c.MatchType: in.MatchType, c.SceneJson: policyJSON(in.SceneJson, "[]"), c.Action: in.Action, c.Replacement: in.Replacement, c.RiskLevel: in.RiskLevel, c.Priority: in.Priority, c.Status: in.Status, c.Remark: in.Remark, c.UpdatedAt: now, c.UpdatedBy: contexts.GetUserId(ctx)} targetID := in.Id if in.Id > 0 { var row gdb.Record if row, err = db.Model(dao.ImSensitiveWords.Table()).Safe().Ctx(ctx).Where(c.Id, in.Id).WhereNull(c.DeletedAt).One(); err != nil { return err } if row.IsEmpty() || row[c.TenantId].Int64() != tenantID { return gerror.New("敏感词不存在") } _, err = db.Model(dao.ImSensitiveWords.Table()).Safe().Ctx(ctx).Where(c.Id, in.Id).Data(data).Update() } else { data[c.CreatedAt] = now data[c.CreatedBy] = contexts.GetUserId(ctx) result, insertErr := db.Model(dao.ImSensitiveWords.Table()).Safe().Ctx(ctx).Data(data).Insert() err = insertErr if err == nil { targetID, err = result.LastInsertId() } } if err != nil { return gerror.Wrap(err, "保存敏感词失败,请检查是否重复") } _ = writeImAdminAudit(ctx, tenantID, "im_sensitive_word_edit", "im_sensitive_word", targetID, "success", g.Map{"group_id": in.GroupId, "action": in.Action}) return publishImPolicyEvent(ctx, tenantID, tenantCode, "sensitive_words.updated", "sensitive_words", uint(targetID)) } func (s *sSysImSensitiveWord) Delete(ctx context.Context, in *sysin.ImSensitiveWordDeleteInp) error { if err := in.Filter(ctx); err != nil { return err } return s.updateWordState(ctx, in.Id, consts.StatusDisable, true, "im_sensitive_word_delete") } func (s *sSysImSensitiveWord) Status(ctx context.Context, in *sysin.ImSensitiveWordStatusInp) error { if err := in.Filter(ctx); err != nil { return err } return s.updateWordState(ctx, in.Id, in.Status, false, "im_sensitive_word_status") } func (s *sSysImSensitiveWord) updateWordState(ctx context.Context, id int64, status int, deleted bool, action string) error { c := dao.ImSensitiveWords.Columns() db := imtenant.RegistryDB() var row gdb.Record var err error if row, err = db.Model(dao.ImSensitiveWords.Table()).Safe().Ctx(ctx).Where(c.Id, id).WhereNull(c.DeletedAt).One(); err != nil { return err } if row.IsEmpty() { return gerror.New("敏感词不存在") } tenantID, tenantCode, err := resolveImPolicyTenant(ctx, row[c.TenantId].Int64()) if err != nil { return err } data := g.Map{c.Status: status, c.UpdatedAt: gtime.Now(), c.UpdatedBy: contexts.GetUserId(ctx)} if deleted { data[c.DeletedAt] = gtime.Now() data[c.DeletedBy] = contexts.GetUserId(ctx) } if _, err = db.Model(dao.ImSensitiveWords.Table()).Safe().Ctx(ctx).Where(c.Id, id).Data(data).Update(); err != nil { return gerror.Wrap(err, "更新敏感词失败") } _ = writeImAdminAudit(ctx, tenantID, action, "im_sensitive_word", id, "success", g.Map{"status": status}) return publishImPolicyEvent(ctx, tenantID, tenantCode, "sensitive_words.updated", "sensitive_words", uint(id)) } func (s *sSysImSensitiveWord) BatchImport(ctx context.Context, in *sysin.ImSensitiveWordBatchImportInp) (res *sysin.ImSensitiveWordBatchImportModel, err error) { if err = in.Filter(ctx); err != nil { return } tenantID, tenantCode, err := resolveImPolicyTenant(ctx, in.TenantId) if err != nil { return nil, err } if err = validateSensitiveWordGroup(ctx, tenantID, in.GroupId); err != nil { return nil, err } res = &sysin.ImSensitiveWordBatchImportModel{} db := imtenant.RegistryDB() c := dao.ImSensitiveWords.Columns() seen := map[string]struct{}{} err = db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { for _, raw := range in.Words { word, hash := normalizedSensitiveWord(raw) if word == "" { res.Skipped++ continue } if _, ok := seen[hash]; ok { res.Skipped++ continue } seen[hash] = struct{}{} count, countErr := tx.Model(dao.ImSensitiveWords.Table()).Safe().Ctx(ctx).Where(c.TenantId, tenantID).Where(c.GroupId, in.GroupId).Where(c.WordHash, hash).WhereNull(c.DeletedAt).Count() if countErr != nil { return countErr } if count > 0 { res.Skipped++ continue } now := gtime.Now() _, insertErr := tx.Model(dao.ImSensitiveWords.Table()).Safe().Ctx(ctx).Data(g.Map{c.TenantId: tenantID, c.GroupId: in.GroupId, c.Word: strings.TrimSpace(raw), c.WordHash: hash, c.MatchType: in.MatchType, c.SceneJson: policyJSON(in.SceneJson, "[]"), c.Action: in.Action, c.Replacement: in.Replacement, c.RiskLevel: in.RiskLevel, c.Status: consts.StatusEnabled, c.CreatedAt: now, c.CreatedBy: contexts.GetUserId(ctx), c.UpdatedAt: now, c.UpdatedBy: contexts.GetUserId(ctx)}).Insert() if insertErr != nil { return insertErr } res.Inserted++ } return nil }) if err != nil { return nil, gerror.Wrap(err, "批量导入敏感词失败") } _ = writeImAdminAudit(ctx, tenantID, "im_sensitive_word_batch_import", "im_sensitive_word", 0, "success", g.Map{"inserted": res.Inserted, "skipped": res.Skipped}) err = publishImPolicyEvent(ctx, tenantID, tenantCode, "sensitive_words.updated", "sensitive_words", uint(res.Inserted)) return } func (s *sSysImSensitiveWord) GroupList(ctx context.Context, in *sysin.ImSensitiveWordGroupListInp) (list []*sysin.ImSensitiveWordGroupModel, err error) { if err = in.Filter(ctx); err != nil { return } tenantID, _, err := resolveImPolicyTenant(ctx, in.TenantId) if err != nil { return nil, err } c := dao.ImSensitiveWordGroups.Columns() err = imtenant.RegistryDB().Model(dao.ImSensitiveWordGroups.Table()).Safe().Ctx(ctx).Fields(c.Id, c.TenantId, c.GroupCode, c.GroupName, c.Priority, c.Status, c.Remark, c.CreatedAt).Where(c.TenantId, tenantID).WhereNull(c.DeletedAt).OrderDesc(c.Priority).OrderAsc(c.GroupName).Scan(&list) return } func (s *sSysImSensitiveWord) GroupEdit(ctx context.Context, in *sysin.ImSensitiveWordGroupEditInp) error { if err := in.Filter(ctx); err != nil { return err } tenantID, tenantCode, err := resolveImPolicyTenant(ctx, in.TenantId) if err != nil { return err } c := dao.ImSensitiveWordGroups.Columns() db := imtenant.RegistryDB() now := gtime.Now() data := g.Map{c.TenantId: tenantID, c.GroupCode: in.GroupCode, c.GroupName: in.GroupName, c.Priority: in.Priority, c.Status: in.Status, c.Remark: in.Remark, c.UpdatedAt: now, c.UpdatedBy: contexts.GetUserId(ctx)} targetID := in.Id if in.Id > 0 { var row gdb.Record if row, err = db.Model(dao.ImSensitiveWordGroups.Table()).Safe().Ctx(ctx).Where(c.Id, in.Id).WhereNull(c.DeletedAt).One(); err != nil { return err } if row.IsEmpty() || row[c.TenantId].Int64() != tenantID { return gerror.New("敏感词分组不存在") } if row[c.GroupCode].String() != in.GroupCode { return gerror.New("分组编码创建后不能修改") } _, err = db.Model(dao.ImSensitiveWordGroups.Table()).Safe().Ctx(ctx).Where(c.Id, in.Id).Data(data).Update() } else { data[c.CreatedAt] = now data[c.CreatedBy] = contexts.GetUserId(ctx) result, insertErr := db.Model(dao.ImSensitiveWordGroups.Table()).Safe().Ctx(ctx).Data(data).Insert() err = insertErr if err == nil { targetID, err = result.LastInsertId() } } if err != nil { return gerror.Wrap(err, "保存敏感词分组失败") } _ = writeImAdminAudit(ctx, tenantID, "im_sensitive_word_group_edit", "im_sensitive_word_group", targetID, "success", nil) return publishImPolicyEvent(ctx, tenantID, tenantCode, "sensitive_words.updated", "sensitive_words", uint(targetID)) } func validateSensitiveWordGroup(ctx context.Context, tenantID, groupID int64) error { c := dao.ImSensitiveWordGroups.Columns() count, err := imtenant.RegistryDB().Model(dao.ImSensitiveWordGroups.Table()).Safe().Ctx(ctx).Where(c.Id, groupID).Where(c.TenantId, tenantID).WhereNull(c.DeletedAt).Count() if err != nil { return gerror.Wrap(err, "检查敏感词分组失败") } if count == 0 { return gerror.New("敏感词分组不存在") } return nil }