redis = Cache::store('redis')->handler(); } catch (\Exception $e) { // Redis连接失败,使用文件缓存 $this->redis = Cache::store('file')->handler(); Log::error('Redis连接失败,已切换到文件缓存:' . $e->getMessage()); } } /** * 记录访问并更新统计数据 */ public function recordVisit(string $page = 'home', string $userId = null): array { try { $date = date('Y-m-d'); $hour = date('H'); $userId = $userId ?? Request::ip(); // 使用管道提高性能 $pipe = $this->redis->multi(); // 总访问量(PV) $pipe->incr($this->prefix.'total_visits'); // 每日访问量 $pipe->incr($this->prefix.'daily:'.$date); // 页面统计 $pipe->zIncrBy($this->prefix.'page_views', 1, $page); // UV统计(使用HyperLogLog节省内存) $pipe->pfAdd($this->prefix.'uv:'.$date, [$userId]); // 时段统计 $pipe->hIncrBy($this->prefix.'hourly:'.$date, $hour, 1); // 执行所有命令 $result = $pipe->exec(); // 使用Redis锁控制更新频率,每5分钟更新一次数据库 $lockKey = $this->prefix.'update_lock:'.$date; if (!$this->redis->exists($lockKey)) { $this->redis->setex($lockKey, 300, 1); // 5分钟锁 $this->updateDailyStats($date, [ 'total_visits' => $result[0], 'daily_visits' => $result[1], 'unique_visitors' => $this->getUniqueVisitors($date) ]); } return [ 'total' => $result[0], 'daily' => $result[1], 'page' => $result[2], 'uv' => $result[3], 'hourly'=> $result[4] ]; } catch (\Exception $e) { Log::error('访问统计失败:' . $e->getMessage()); return [ 'total' => 0, 'daily' => 0, 'page' => 0, 'uv' => 0, 'hourly'=> 0 ]; } } /** * 更新每日统计数据 */ protected function updateDailyStats(string $date, array $stats) { try { // 获取其他统计数据 $otherStats = [ 'total_users' => Db::name('users')->count(), 'new_users' => Db::name('users')->whereDay('create_time', $date)->count(), 'total_articles' => Db::name('articles')->where('delete_time', null)->count(), 'daily_articles' => Db::name('articles')->whereDay('create_time', $date)->count(), 'article_views' => Db::name('articles')->whereDay('update_time', $date)->sum('views'), 'total_resources' => Db::name('resources')->where('delete_time', null)->count(), 'daily_resources' => Db::name('resources')->whereDay('create_time', $date)->count(), 'resource_downloads' => Db::name('resources')->whereDay('update_time', $date)->sum('downloads') ]; // 只在调试模式下记录日志 if (config('app.debug')) { Log::info('统计数据:' . json_encode($otherStats, JSON_UNESCAPED_UNICODE)); } // 合并统计数据 $stats = array_merge($stats, $otherStats); // 检查记录是否存在 $exists = Db::name('daily_stats')->where('date', $date)->find(); if ($exists) { // 更新已存在的记录 Db::name('daily_stats')->where('date', $date)->update([ 'total_users' => $stats['total_users'], 'new_users' => $stats['new_users'], 'total_visits' => $stats['total_visits'], 'daily_visits' => $stats['daily_visits'], 'unique_visitors' => $stats['unique_visitors'], 'total_articles' => $stats['total_articles'], 'daily_articles' => $stats['daily_articles'], 'article_views' => $stats['article_views'], 'total_resources' => $stats['total_resources'], 'daily_resources' => $stats['daily_resources'], 'resource_downloads' => $stats['resource_downloads'] ]); } else { // 插入新记录 Db::name('daily_stats')->insert([ 'date' => $date, 'total_users' => $stats['total_users'], 'new_users' => $stats['new_users'], 'total_visits' => $stats['total_visits'], 'daily_visits' => $stats['daily_visits'], 'unique_visitors' => $stats['unique_visitors'], 'total_articles' => $stats['total_articles'], 'daily_articles' => $stats['daily_articles'], 'article_views' => $stats['article_views'], 'total_resources' => $stats['total_resources'], 'daily_resources' => $stats['daily_resources'], 'resource_downloads' => $stats['resource_downloads'] ]); } } catch (\Exception $e) { Log::error('更新统计数据失败:' . $e->getMessage()); } } /** * 获取总访问量 */ public function getTotalVisits(): int { try { return (int)$this->redis->get($this->prefix.'total_visits'); } catch (\Exception $e) { Log::error('获取总访问量失败:' . $e->getMessage()); return 0; } } /** * 获取当日访问量 */ public function getDailyVisits(string $date = null): int { try { $date = $date ?? date('Y-m-d'); return (int)$this->redis->get($this->prefix.'daily:'.$date); } catch (\Exception $e) { Log::error('获取当日访问量失败:' . $e->getMessage()); return 0; } } /** * 获取独立访客数(UV) */ public function getUniqueVisitors(string $date = null): int { try { $date = $date ?? date('Y-m-d'); return $this->redis->pfCount($this->prefix.'uv:'.$date); } catch (\Exception $e) { Log::error('获取独立访客数失败:' . $e->getMessage()); return 0; } } }