修复全量审查问题;设置接口PATCH化;回传指纹加固
CI / test (push) Successful in 32s

This commit is contained in:
2026-07-22 16:51:23 +08:00
parent 0614ef22af
commit f51fb6c722
66 changed files with 3997 additions and 687 deletions
+53 -13
View File
@@ -112,26 +112,66 @@ func (s *SettingService) Security(ctx context.Context) (SecuritySettings, error)
return out, nil
}
// UpdateSecurity 校验并保存安全设置,随后刷新内存快照立即生效。
func (s *SettingService) UpdateSecurity(ctx context.Context, in SecuritySettings) error {
if err := validateSecurity(&in); err != nil {
// SecurityPatch 是安全设置的部分更新;nil 字段沿用现值,只落库出现的字段,
// 并发编辑不同字段因此互不回滚(2026-07-22 审查 #18)。
type SecurityPatch struct {
LoginFailLimit *int `json:"loginFailLimit"`
LoginLockMinutes *int `json:"loginLockMinutes"`
IPRateRPS *int `json:"ipRateRps"`
IPRateBurst *int `json:"ipRateBurst"`
RealIPHeader *string `json:"realIpHeader"`
AppURL *string `json:"appUrl"`
}
// merge 把非 nil 字段覆盖到 dst,返回被触碰字段的存储键集合。
func (p SecurityPatch) merge(dst *SecuritySettings) map[string]bool {
touched := map[string]bool{}
fields := []struct {
key string
set func()
on bool
}{
{settingSecLoginFailLimit, func() { dst.LoginFailLimit = *p.LoginFailLimit }, p.LoginFailLimit != nil},
{settingSecLoginLockMin, func() { dst.LoginLockMinutes = *p.LoginLockMinutes }, p.LoginLockMinutes != nil},
{settingSecIPRateRPS, func() { dst.IPRateRPS = *p.IPRateRPS }, p.IPRateRPS != nil},
{settingSecIPRateBurst, func() { dst.IPRateBurst = *p.IPRateBurst }, p.IPRateBurst != nil},
{settingSecRealIPHeader, func() { dst.RealIPHeader = *p.RealIPHeader }, p.RealIPHeader != nil},
{settingSecAppURL, func() { dst.AppURL = *p.AppURL }, p.AppURL != nil},
}
for _, f := range fields {
if f.on {
f.set()
touched[f.key] = true
}
}
return touched
}
// UpdateSecurity 把补丁合并到现值上整体校验,只落库出现的字段,再重读刷新快照。
func (s *SettingService) UpdateSecurity(ctx context.Context, p SecurityPatch) error {
cur, err := s.Security(ctx)
if err != nil {
return err
}
touched := p.merge(&cur)
if err := validateSecurity(&cur); err != nil {
return err
}
kv := map[string]string{
settingSecLoginFailLimit: strconv.Itoa(in.LoginFailLimit),
settingSecLoginLockMin: strconv.Itoa(in.LoginLockMinutes),
settingSecIPRateRPS: strconv.Itoa(in.IPRateRPS),
settingSecIPRateBurst: strconv.Itoa(in.IPRateBurst),
settingSecRealIPHeader: in.RealIPHeader,
settingSecAppURL: in.AppURL,
settingSecLoginFailLimit: strconv.Itoa(cur.LoginFailLimit),
settingSecLoginLockMin: strconv.Itoa(cur.LoginLockMinutes),
settingSecIPRateRPS: strconv.Itoa(cur.IPRateRPS),
settingSecIPRateBurst: strconv.Itoa(cur.IPRateBurst),
settingSecRealIPHeader: cur.RealIPHeader,
settingSecAppURL: cur.AppURL,
}
for key, value := range kv {
if err := s.set(ctx, key, value); err != nil {
for key := range touched {
if err := s.set(ctx, key, kv[key]); err != nil {
return err
}
}
s.security.Store(in)
return nil
// 重读而非直接 Store 合并值:并发补丁各写各键,重读保证快照收敛到库内最终值
return s.ReloadSecurity(ctx)
}
// ReloadSecurity 从库加载安全设置到内存快照;进程启动时调用一次。