feat: 完成鸽达达后台安全与审计改造
This commit is contained in:
@@ -0,0 +1,266 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user