191 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			191 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | ||
| namespace app\service;
 | ||
| 
 | ||
| use think\facade\Cache;
 | ||
| use think\facade\Request;
 | ||
| use think\facade\Db;
 | ||
| use think\facade\Log;
 | ||
| 
 | ||
| class VisitStatsService
 | ||
| {
 | ||
|     // Redis实例
 | ||
|     protected $redis;
 | ||
|     
 | ||
|     // 键名前缀
 | ||
|     protected $prefix = 'stats:';
 | ||
|     
 | ||
|     public function __construct()
 | ||
|     {
 | ||
|         try {
 | ||
|             // 获取Redis处理器
 | ||
|             $this->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();
 | ||
|             
 | ||
|             // 更新数据库统计
 | ||
|             $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(), // 移除 delete_time 条件
 | ||
|                 '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')
 | ||
|             ];
 | ||
|             
 | ||
|             // 记录日志,方便调试
 | ||
|             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;
 | ||
|         }
 | ||
|     }
 | ||
| }
 | ||
|     
 | 
