From 671ac2e1f7291709047e021d80989cc44ed983c1 Mon Sep 17 00:00:00 2001 From: Austin Date: Tue, 22 Sep 2026 19:08:53 +0800 Subject: [PATCH] =?UTF-8?q?fix(security-policy):=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E7=AD=96=E7=95=A5=E4=BF=9D=E5=AD=98=E6=97=B6=E7=A7=9F=E6=88=B7?= =?UTF-8?q?=E8=A1=8C=E6=9B=B4=E6=96=B0=E6=97=B6=E9=97=B4=E4=B8=8E=E4=BA=8B?= =?UTF-8?q?=E4=BB=B6=E6=8A=95=E9=80=92=E9=98=BB=E5=A1=9E=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 调整策略保存时的租户表更新逻辑,显式赋值updated_at避免自动填充干扰 修改策略变更事件投递逻辑,失败时仅告警审计不阻塞主流程 新增租户无策略时加载模板初始化配置的逻辑,提升默认策略灵活性 --- .../internal/logic/sys/im_security_policy.go | 40 +++++++++++++++---- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/server/internal/logic/sys/im_security_policy.go b/server/internal/logic/sys/im_security_policy.go index 3ddef3f..6842b9c 100644 --- a/server/internal/logic/sys/im_security_policy.go +++ b/server/internal/logic/sys/im_security_policy.go @@ -179,9 +179,10 @@ func (s *sSysImSecurityPolicy) Save(ctx context.Context, in *sysin.ImSecurityPol } } if tenantID > 0 { - // 上游已将商户配置并入 im_tenant 表,此处同步冗余开关与策略版本(商户行必然存在,无需补建) + // 上游已将商户配置并入 im_tenant 表,此处同步冗余开关与策略版本。 + // updated_at 显式自赋值:抑制 gf 自动填充与 ON UPDATE CURRENT_TIMESTAMP,策略保存不 touch 商户行的更新人/时间 if _, cfgErr := tx.Model(dao.ImTenant.Table()).Safe().Ctx(ctx).Where(tc.Id, tenantID). - Data(g.Map{tc.SecurityPolicyVersion: version, tc.GlobalFriendEnabled: in.GlobalFriendEnabled, tc.MessageCurfewEnabled: in.MessageCurfewEnabled, tc.UpdatedAt: now, tc.UpdatedBy: operator}).Update(); cfgErr != nil { + Data(g.Map{tc.SecurityPolicyVersion: version, tc.GlobalFriendEnabled: in.GlobalFriendEnabled, tc.MessageCurfewEnabled: in.MessageCurfewEnabled, tc.UpdatedAt: gdb.Raw("updated_at")}).Update(); cfgErr != nil { return cfgErr } } @@ -191,9 +192,11 @@ func (s *sSysImSecurityPolicy) Save(ctx context.Context, in *sysin.ImSecurityPol return 0, gerror.Wrap(err, "保存安全策略失败") } _ = writeImAdminAudit(ctx, tenantID, "im_security_policy_save", "im_security_policy", id, "success", g.Map{"version": version, "global_friend_enabled": in.GlobalFriendEnabled, "message_curfew_enabled": in.MessageCurfewEnabled}) - // 保存即生效:投递策略变更事件,IM 端消费后失效策略缓存并回源拉取新配置 - if err = publishImPolicyEvent(ctx, tenantID, tenantCode, "security.policy.updated", "security_policy", version); err != nil { - return id, err + // 保存即生效:投递策略变更事件,IM 端消费后失效策略缓存并回源拉取新配置。 + // 事件已持久化到 im_admin_outbox,立即投递失败仅告警+审计、由 outbox 重试续投,不阻塞已生效的保存结果 + if pubErr := publishImPolicyEvent(ctx, tenantID, tenantCode, "security.policy.updated", "security_policy", version); pubErr != nil { + g.Log().Warningf(ctx, "IM策略事件投递失败(outbox将重试): tenant=%d version=%d err=%v", tenantID, version, pubErr) + _ = writeImAdminAudit(ctx, tenantID, "im_policy_event_publish", "im_security_policy", id, "failed", g.Map{"error": pubErr.Error(), "version": version}) } return id, nil } @@ -305,10 +308,31 @@ func (s *sSysImSecurityPolicy) SaveLoginLockoutConfig(ctx context.Context, in *s return gerror.Wrap(err, "保存登录失败策略配置失败") } _ = writeImAdminAudit(ctx, tenantID, "im_security_policy_login_lockout_save", "im_security_policy", policyID, "success", g.Map{"enabled": in.Enabled, "password_max_attempts": in.PasswordMaxAttempts, "lock_minutes": in.LockMinutes, "window_minutes": in.WindowMinutes}) - return publishImPolicyEvent(ctx, tenantID, tenantCode, "security.policy.updated", "security_policy", version) + if pubErr := publishImPolicyEvent(ctx, tenantID, tenantCode, "security.policy.updated", "security_policy", version); pubErr != nil { + g.Log().Warningf(ctx, "IM策略事件投递失败(outbox将重试): tenant=%d version=%d err=%v", tenantID, version, pubErr) + _ = writeImAdminAudit(ctx, tenantID, "im_policy_event_publish", "im_security_policy", policyID, "failed", g.Map{"error": pubErr.Error(), "version": version}) + } + return nil } - // 租户尚无策略:保存默认策略,保存即生效并投递策略变更事件 - id, saveErr := s.Save(ctx, &sysin.ImSecurityPolicySaveInp{TenantId: tenantID, PolicyName: "默认安全策略", LoginRateLimit: gjson.New(value), GlobalFriendEnabled: 1, MessageCurfewEnabled: 0}) + // 租户尚无策略:按模板复制初始策略(模板未初始化时退回内置默认值),仅覆盖登录失败配置,保存即生效并投递策略变更事件 + saveInp := &sysin.ImSecurityPolicySaveInp{TenantId: tenantID, PolicyName: "默认安全策略", LoginRateLimit: gjson.New(value), GlobalFriendEnabled: 1, MessageCurfewEnabled: 0} + tpl, tplErr := loadPolicyTemplateRow(imtenant.RegistryDB().Model(imPolicyTemplateTable).Safe().Ctx(ctx)) + if tplErr != nil { + return gerror.Wrap(tplErr, "读取安全策略模板失败") + } + if tpl != nil { + saveInp.PolicyName = tpl["policy_name"].String() + saveInp.PasswordPolicy = gjson.New(tpl["password_policy"].String()) + saveInp.SensitiveWordPolicy = gjson.New(tpl["sensitive_word_policy"].String()) + saveInp.FileReviewPolicy = gjson.New(tpl["file_review_policy"].String()) + saveInp.RealnamePolicy = gjson.New(tpl["realname_policy"].String()) + saveInp.RiskChallengePolicy = gjson.New(tpl["risk_challenge_policy"].String()) + saveInp.GlobalFriendEnabled = tpl["global_friend_enabled"].Int() + saveInp.FriendDisabledPolicy = gjson.New(tpl["friend_disabled_policy"].String()) + saveInp.MessageCurfewEnabled = tpl["message_curfew_enabled"].Int() + saveInp.CurfewRules = templateCurfewRules(tpl) + } + id, saveErr := s.Save(ctx, saveInp) if saveErr != nil { return saveErr }