userId = $userId; $this->ip = $ip; } /** * 获取过去 N 天的日期 key 列表(当天在最前) * * @param int $days * @return array */ private function getRecentDateKeys($days = 3) { $keys = []; for ($i = 0; $i < $days; $i++) { $keys[] = self::LAST_DETECTED_PREFIX . date('Ymd', strtotime("-{$i} days")); } return $keys; } /** * Execute the job. */ public function handle() { $userId = $this->userId; $ip = $this->ip; if (empty($userId) || empty($ip)) { return; } // 1. 检查过去 3 天的每日 Hash 中是否已检测过同一 IP // 只要任意一天记录相同 IP → 跳过(IP 未变) $recentKeys = $this->getRecentDateKeys(self::DETECTION_WINDOW_DAYS); $todayKey = $recentKeys[0]; $ipUnchanged = false; foreach ($recentKeys as $key) { $storedIp = Redis::hget($key, $userId); if ($storedIp === $ip) { $ipUnchanged = true; break; } } if ($ipUnchanged) { // IP 在检测窗口内未变,跳过 return; } // 2. 执行风险检测 $service = new IpRiskService(); $result = $service->detect($ip); // 3. 根据检测结果更新风险标记 Hash if ($result['is_risky']) { $value = $ip . '|' . $result['reason']; Redis::hset(self::REDIS_HASH_KEY, $userId, $value); Log::info("IpRiskDetection: user flagged", [ 'user_id' => $userId, 'ip' => $ip, 'reason' => $result['reason'], ]); } // 4. 记录本次检测结果到今天的 Hash,并确保 3 天过期 $isNew = !Redis::exists($todayKey); Redis::hset($todayKey, $userId, $ip); if ($isNew) { Redis::expire($todayKey, 86400 * self::DETECTION_WINDOW_DAYS); } } }