option('date') ? Carbon::parse($this->option('date')) : Carbon::yesterday(); $hour = $this->option('hour'); $force = $this->option('force'); $this->info("开始生成统计数据..."); $this->info("统计日期: {$date->toDateString()}"); if ($hour !== null) { $this->info("统计小时: {$hour}"); $this->generateHourlyStats($date, (int)$hour, $force); } else { $this->info("生成全天统计数据"); $this->generateDailyStats($date, $force); } $this->info("统计数据生成完成!"); return 0; } /** * 生成每日统计数据 * * @param Carbon $date * @param bool $force * @return void */ protected function generateDailyStats(Carbon $date, bool $force): void { // 生成24小时的统计数据 for ($hour = 0; $hour < 24; $hour++) { $this->generateHourlyStats($date, $hour, $force); } // 生成全天汇总统计 $this->generateDailySummary($date, $force); } /** * 生成每小时统计数据 * * @param Carbon $date * @param int $hour * @param bool $force * @return void */ protected function generateHourlyStats(Carbon $date, int $hour, bool $force): void { $startTime = $date->copy()->hour($hour)->minute(0)->second(0); $endTime = $startTime->copy()->addHour(); $this->line("处理时间段: {$startTime->format('Y-m-d H:i:s')} - {$endTime->format('Y-m-d H:i:s')}"); // 获取该时间段的日志数据 $logs = OpenApiLog::whereBetween('created_at', [$startTime, $endTime])->get(); if ($logs->isEmpty()) { $this->line(" 无数据"); return; } // 按应用和接口分组统计 $groupedLogs = $logs->groupBy(function ($log) { return $log->app_id . '|' . $log->uri; }); foreach ($groupedLogs as $key => $groupLogs) { [$appId, $endpoint] = explode('|', $key, 2); // 检查是否已存在统计记录 $existingStats = OpenApiStats::where('app_id', $appId) ->where('date', $date->toDateString()) ->where('hour', $hour) ->where('endpoint', $endpoint) ->first(); if ($existingStats && !$force) { $this->line(" 跳过已存在的统计: {$appId} - {$endpoint}"); continue; } // 计算统计数据 $stats = $this->calculateStats($groupLogs); // 创建或更新统计记录 $data = [ 'app_id' => $appId, 'date' => $date->toDateString(), 'hour' => $hour, 'endpoint' => $endpoint, 'request_count' => $stats['request_count'], 'success_count' => $stats['success_count'], 'error_count' => $stats['error_count'], 'avg_response_time' => $stats['avg_response_time'], 'max_response_time' => $stats['max_response_time'], 'min_response_time' => $stats['min_response_time'], 'rate_limit_hits' => $stats['rate_limit_hits'], 'unique_ips' => $stats['unique_ips'], 'error_details' => $stats['error_details'], ]; if ($existingStats) { $existingStats->update($data); $this->line(" 更新统计: {$appId} - {$endpoint} ({$stats['request_count']} 请求)"); } else { OpenApiStats::create($data); $this->line(" 创建统计: {$appId} - {$endpoint} ({$stats['request_count']} 请求)"); } } } /** * 生成每日汇总统计 * * @param Carbon $date * @param bool $force * @return void */ protected function generateDailySummary(Carbon $date, bool $force): void { $this->line("生成每日汇总统计..."); // 获取该日期的所有小时统计数据 $hourlyStats = OpenApiStats::where('date', $date->toDateString()) ->whereNotNull('hour') ->get(); if ($hourlyStats->isEmpty()) { $this->line(" 无小时统计数据"); return; } // 按应用和接口分组 $groupedStats = $hourlyStats->groupBy(function ($stat) { return $stat->app_id . '|' . $stat->endpoint; }); foreach ($groupedStats as $key => $groupStats) { [$appId, $endpoint] = explode('|', $key, 2); // 检查是否已存在日汇总记录 $existingSummary = OpenApiStats::where('app_id', $appId) ->where('date', $date->toDateString()) ->whereNull('hour') ->where('endpoint', $endpoint) ->first(); if ($existingSummary && !$force) { $this->line(" 跳过已存在的日汇总: {$appId} - {$endpoint}"); continue; } // 计算汇总数据 $summary = [ 'app_id' => $appId, 'date' => $date->toDateString(), 'hour' => null, 'endpoint' => $endpoint, 'request_count' => $groupStats->sum('request_count'), 'success_count' => $groupStats->sum('success_count'), 'error_count' => $groupStats->sum('error_count'), 'avg_response_time' => $groupStats->avg('avg_response_time'), 'max_response_time' => $groupStats->max('max_response_time'), 'min_response_time' => $groupStats->min('min_response_time'), 'rate_limit_hits' => $groupStats->sum('rate_limit_hits'), 'unique_ips' => $groupStats->sum('unique_ips'), // 这里简化处理,实际应该去重 'error_details' => $this->mergeErrorDetails($groupStats), ]; if ($existingSummary) { $existingSummary->update($summary); $this->line(" 更新日汇总: {$appId} - {$endpoint} ({$summary['request_count']} 请求)"); } else { OpenApiStats::create($summary); $this->line(" 创建日汇总: {$appId} - {$endpoint} ({$summary['request_count']} 请求)"); } } } /** * 计算统计数据 * * @param \Illuminate\Support\Collection $logs * @return array */ protected function calculateStats($logs): array { $requestCount = $logs->count(); $successCount = $logs->where('response_code', '<', 400)->count(); $errorCount = $requestCount - $successCount; $rateLimitHits = $logs->where('rate_limit_hit', true)->count(); $uniqueIps = $logs->pluck('ip_address')->unique()->count(); $responseTimes = $logs->pluck('response_time')->filter(); $avgResponseTime = $responseTimes->avg() ?: 0; $maxResponseTime = $responseTimes->max() ?: 0; $minResponseTime = $responseTimes->min() ?: 0; // 统计错误详情 $errorDetails = []; $errorLogs = $logs->where('response_code', '>=', 400); foreach ($errorLogs as $log) { $code = $log->response_code; if (!isset($errorDetails[$code])) { $errorDetails[$code] = [ 'count' => 0, 'message' => $log->error_message ?: "HTTP {$code}", 'first_seen' => $log->created_at->toISOString(), ]; } $errorDetails[$code]['count']++; $errorDetails[$code]['last_seen'] = $log->created_at->toISOString(); } return [ 'request_count' => $requestCount, 'success_count' => $successCount, 'error_count' => $errorCount, 'avg_response_time' => round($avgResponseTime, 2), 'max_response_time' => $maxResponseTime, 'min_response_time' => $minResponseTime, 'rate_limit_hits' => $rateLimitHits, 'unique_ips' => $uniqueIps, 'error_details' => $errorDetails, ]; } /** * 合并错误详情 * * @param \Illuminate\Support\Collection $stats * @return array */ protected function mergeErrorDetails($stats): array { $mergedDetails = []; foreach ($stats as $stat) { $errorDetails = $stat->error_details ?? []; foreach ($errorDetails as $code => $details) { if (!isset($mergedDetails[$code])) { $mergedDetails[$code] = $details; } else { $mergedDetails[$code]['count'] += $details['count']; if (isset($details['last_seen'])) { $mergedDetails[$code]['last_seen'] = $details['last_seen']; } } } } return $mergedDetails; } }