package auth import ( "context" "time" "hyapp/pkg/appcode" "hyapp/pkg/xerr" authdomain "hyapp/services/user-service/internal/domain/auth" ) const ( defaultMaxAccountsPerDevice = int32(1) registerRiskConfigCacheTTL = 5 * time.Minute ) // GetRegisterRiskConfig 返回当前 App 的注册风控配置。 // Redis 只缓存 5 分钟以降低登录注册热路径回源;缓存异常不阻断读取,避免缓存故障改变注册策略。 func (s *Service) GetRegisterRiskConfig(ctx context.Context) (authdomain.RegisterRiskConfig, error) { appCode := appcode.FromContext(ctx) if s.registerRiskConfigCache != nil { config, ok, err := s.registerRiskConfigCache.GetRegisterRiskConfig(ctx, appCode) if err == nil && ok { return normalizeRegisterRiskConfig(config), nil } } config, err := s.loadRegisterRiskConfig(ctx, appCode) if err != nil { return authdomain.RegisterRiskConfig{}, err } if s.registerRiskConfigCache != nil { // 读路径写缓存是加速动作;失败时仍返回 MySQL 事实,后续请求会继续尝试刷新缓存。 _ = s.registerRiskConfigCache.SetRegisterRiskConfig(ctx, config, registerRiskConfigCacheTTL) } return config, nil } // UpdateRegisterRiskConfig 保存注册风控配置并立即刷新 Redis。 // MySQL 是持久事实,Redis 是注册热路径缓存;更新后同步刷新同一个 key,避免运营保存后继续命中旧值。 func (s *Service) UpdateRegisterRiskConfig(ctx context.Context, maxAccountsPerDevice int32, operatorAdminID int64) (authdomain.RegisterRiskConfig, error) { if maxAccountsPerDevice < 1 { return authdomain.RegisterRiskConfig{}, xerr.New(xerr.InvalidArgument, "max_accounts_per_device must be greater than 0") } if s.authRepository == nil { return authdomain.RegisterRiskConfig{}, xerr.New(xerr.Unavailable, "auth repository is not configured") } nowMs := s.now().UTC().UnixMilli() config, err := s.authRepository.UpsertRegisterRiskConfig(ctx, authdomain.RegisterRiskConfig{ AppCode: appcode.FromContext(ctx), MaxAccountsPerDevice: maxAccountsPerDevice, UpdatedByAdminID: operatorAdminID, CreatedAtMs: nowMs, UpdatedAtMs: nowMs, }) if err != nil { return authdomain.RegisterRiskConfig{}, err } config = normalizeRegisterRiskConfig(config) if s.registerRiskConfigCache != nil { if err := s.registerRiskConfigCache.SetRegisterRiskConfig(ctx, config, registerRiskConfigCacheTTL); err != nil { // 保存成功但缓存未刷新时返回错误,提示后台重试;否则注册链路可能在 5 分钟内继续读取旧配置。 return authdomain.RegisterRiskConfig{}, err } } return config, nil } func (s *Service) loadRegisterRiskConfig(ctx context.Context, appCode string) (authdomain.RegisterRiskConfig, error) { if s.authRepository == nil { return defaultRegisterRiskConfig(appCode), nil } config, err := s.authRepository.GetRegisterRiskConfig(ctx, appCode) if xerr.IsCode(err, xerr.NotFound) { return defaultRegisterRiskConfig(appCode), nil } if err != nil { return authdomain.RegisterRiskConfig{}, err } return normalizeRegisterRiskConfig(config), nil } func defaultRegisterRiskConfig(appCode string) authdomain.RegisterRiskConfig { return authdomain.RegisterRiskConfig{ AppCode: appcode.Normalize(appCode), MaxAccountsPerDevice: defaultMaxAccountsPerDevice, } } func normalizeRegisterRiskConfig(config authdomain.RegisterRiskConfig) authdomain.RegisterRiskConfig { config.AppCode = appcode.Normalize(config.AppCode) if config.MaxAccountsPerDevice < 1 { config.MaxAccountsPerDevice = defaultMaxAccountsPerDevice } return config }