fix(security-policy): 修复策略保存时租户行更新时间与事件投递阻塞问题

调整策略保存时的租户表更新逻辑,显式赋值updated_at避免自动填充干扰
修改策略变更事件投递逻辑,失败时仅告警审计不阻塞主流程
新增租户无策略时加载模板初始化配置的逻辑,提升默认策略灵活性
This commit is contained in:
Austin
2026-09-22 19:08:53 +08:00
parent dc3c45cd53
commit 671ac2e1f7
@@ -179,9 +179,10 @@ func (s *sSysImSecurityPolicy) Save(ctx context.Context, in *sysin.ImSecurityPol
} }
} }
if tenantID > 0 { 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). 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 return cfgErr
} }
} }
@@ -191,9 +192,11 @@ func (s *sSysImSecurityPolicy) Save(ctx context.Context, in *sysin.ImSecurityPol
return 0, gerror.Wrap(err, "保存安全策略失败") 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}) _ = 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 端消费后失效策略缓存并回源拉取新配置 // 保存即生效:投递策略变更事件,IM 端消费后失效策略缓存并回源拉取新配置。
if err = publishImPolicyEvent(ctx, tenantID, tenantCode, "security.policy.updated", "security_policy", version); err != nil { // 事件已持久化到 im_admin_outbox,立即投递失败仅告警+审计、由 outbox 重试续投,不阻塞已生效的保存结果
return id, err 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 return id, nil
} }
@@ -305,10 +308,31 @@ func (s *sSysImSecurityPolicy) SaveLoginLockoutConfig(ctx context.Context, in *s
return gerror.Wrap(err, "保存登录失败策略配置失败") 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}) _ = 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 { if saveErr != nil {
return saveErr return saveErr
} }