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 }