package auth import ( "context" "crypto/sha256" "encoding/hex" "log/slog" "strings" "hyapp/pkg/appcode" "hyapp/pkg/idgen" "hyapp/pkg/logx" authdomain "hyapp/services/user-service/internal/domain/auth" ) const ( riskBlockReasonIPAsync = "ip_async" ) func (s *Service) enqueueLoginIPRiskJob(ctx context.Context, meta Meta, token authdomain.Token, channel string, platform string, language string, timezone string) { if s.authRepository == nil || strings.TrimSpace(meta.ClientIP) == "" || token.SessionID == "" || token.UserID <= 0 { // 没有可复核 IP 或 session 时不创建任务;登录主链路不因此失败。 return } nowMs := s.now().UnixMilli() clientIP := strings.TrimSpace(meta.ClientIP) job := authdomain.LoginIPRiskJob{ AppCode: appcode.Normalize(token.AppCode), JobID: idgen.New("iprisk"), SessionID: token.SessionID, UserID: token.UserID, ClientIP: clientIP, ClientIPHash: HashLoginRiskIP(clientIP), Channel: normalizeChannel(channel), Platform: normalizePlatform(firstNonEmpty(platform, meta.Platform)), Language: strings.TrimSpace(firstNonEmpty(language, meta.Language)), Timezone: strings.TrimSpace(firstNonEmpty(timezone, meta.Timezone)), RequestID: meta.RequestID, EdgeCountryCode: normalizeCountryCode(meta.CountryByIP), Status: authdomain.LoginIPRiskStatusPending, CreatedAtMs: nowMs, UpdatedAtMs: nowMs, } if job.Channel == "" { job.Channel = "account" } if err := s.authRepository.CreateLoginIPRiskJob(ctx, job); err != nil { // 风控任务失败按文档 fail-open;日志只输出 IP hash,明文 IP 已在业务表审计。 logx.Error(logx.With(ctx, slog.String("request_id", meta.RequestID), slog.String("app_code", job.AppCode)), "login_ip_risk_job_create_failed", err, slog.String("component", "user_auth"), slog.String("client_ip_hash", job.ClientIPHash), slog.String("session_id", job.SessionID)) return } logx.Info(logx.With(ctx, slog.String("request_id", meta.RequestID), slog.String("app_code", job.AppCode)), "login_ip_risk_job_created", slog.String("component", "user_auth"), slog.String("job_id", job.JobID), slog.String("session_id", job.SessionID), slog.Int64("user_id", job.UserID), slog.String("client_ip_hash", job.ClientIPHash), slog.String("channel", job.Channel), slog.String("platform", job.Platform)) } // HashLoginRiskIP 生成登录风险 Redis key 和 DB 索引用的稳定 IP 摘要。 func HashLoginRiskIP(ip string) string { sum := sha256.Sum256([]byte(strings.TrimSpace(ip))) return hex.EncodeToString(sum[:]) }