| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291 |
- <?php
- namespace App\Module\OpenAPI\Commands;
- use App\Module\OpenAPI\Models\OpenApiLog;
- use App\Module\OpenAPI\Models\OpenApiStats;
- use Illuminate\Console\Command;
- use Carbon\Carbon;
- /**
- * 生成OpenAPI统计数据命令
- */
- class GenerateStatsCommand extends Command
- {
- /**
- * 命令签名
- *
- * @var string
- */
- protected $signature = 'openapi:generate-stats
- {--date= : 指定统计日期 (Y-m-d 格式)}
- {--hour= : 指定统计小时 (0-23)}
- {--force : 强制重新生成已存在的统计数据}';
- /**
- * 命令描述
- *
- * @var string
- */
- protected $description = '生成OpenAPI统计数据';
- /**
- * 执行命令
- *
- * @return int
- */
- public function handle(): int
- {
- $date = $this->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;
- }
- }
|