selectRaw("REGEXP_REPLACE(SUBSTRING_INDEX(url, '?', 1), '^https?://[^/]+', '') as endpoint") ->addSelect('method') ->selectRaw('COUNT(*) as call_count') ->selectRaw('MAX(created_at) as last_called_at') ->selectRaw('AVG(duration_ms) as avg_duration_ms') ->selectRaw('SUM(CASE WHEN response_status >= 200 AND response_status < 300 THEN 1 ELSE 0 END) as success_count') ->selectRaw('SUM(CASE WHEN response_status >= 400 THEN 1 ELSE 0 END) as error_count') ->groupByRaw("REGEXP_REPLACE(SUBSTRING_INDEX(url, '?', 1), '^https?://[^/]+', ''), method") ->orderByDesc('call_count') ->get(); } /** * 전체 API 목록과 사용 현황 비교 * * @return array ['used' => [...], 'unused' => [...], 'stats' => [...]] */ public function getApiUsageComparison(): array { // Swagger에서 전체 API 목록 조회 $allEndpoints = $this->parser->getEndpoints(); // 사용된 API 통계 $usageStats = $this->getUsageStats()->keyBy(function ($item) { return $item->endpoint.'|'.$item->method; }); // 폐기 후보 목록 $deprecations = ApiDeprecation::active() ->get() ->keyBy(function ($item) { return $item->endpoint.'|'.$item->method; }); $used = []; $unused = []; foreach ($allEndpoints as $endpoint) { $key = $endpoint['path'].'|'.$endpoint['method']; $stats = $usageStats->get($key); $deprecation = $deprecations->get($key); $item = [ 'endpoint' => $endpoint['path'], 'method' => $endpoint['method'], 'summary' => $endpoint['summary'] ?? '', 'tags' => $endpoint['tags'] ?? [], 'call_count' => $stats->call_count ?? 0, 'last_called_at' => $stats->last_called_at ?? null, 'avg_duration_ms' => $stats->avg_duration_ms ?? null, 'success_count' => $stats->success_count ?? 0, 'error_count' => $stats->error_count ?? 0, 'deprecation' => $deprecation, ]; if ($stats) { $used[] = $item; } else { $unused[] = $item; } } // 사용된 API는 호출 횟수 순으로 정렬 usort($used, fn ($a, $b) => $b['call_count'] <=> $a['call_count']); return [ 'used' => $used, 'unused' => $unused, 'summary' => [ 'total' => count($allEndpoints), 'used_count' => count($used), 'unused_count' => count($unused), 'deprecation_count' => $deprecations->count(), ], ]; } /** * 미사용 API 목록 조회 */ public function getUnusedApis(): Collection { $comparison = $this->getApiUsageComparison(); return collect($comparison['unused']); } /** * 폐기 후보 추가 */ public function addDeprecationCandidate( string $endpoint, string $method, ?string $reason = null, ?int $userId = null ): ApiDeprecation { return ApiDeprecation::updateOrCreate( ['endpoint' => $endpoint, 'method' => strtoupper($method)], [ 'status' => ApiDeprecation::STATUS_CANDIDATE, 'reason' => $reason, 'created_by' => $userId, ] ); } /** * 폐기 후보 일괄 추가 (미사용 API 전체) */ public function addAllUnusedAsCandidate(?int $userId = null): int { $unused = $this->getUnusedApis(); $count = 0; foreach ($unused as $api) { // 이미 등록되지 않은 것만 추가 $exists = ApiDeprecation::where('endpoint', $api['endpoint']) ->where('method', $api['method']) ->exists(); if (! $exists) { $this->addDeprecationCandidate( $api['endpoint'], $api['method'], '미사용 API (자동 등록)', $userId ); $count++; } } return $count; } /** * 폐기 상태 변경 */ public function updateDeprecationStatus( int $id, string $status, ?string $reason = null, ?\DateTime $scheduledDate = null ): ApiDeprecation { $deprecation = ApiDeprecation::findOrFail($id); $data = ['status' => $status]; if ($reason !== null) { $data['reason'] = $reason; } if ($scheduledDate !== null) { $data['scheduled_date'] = $scheduledDate; } if ($status === ApiDeprecation::STATUS_DEPRECATED) { $data['deprecated_date'] = now(); } $deprecation->update($data); return $deprecation->fresh(); } /** * 폐기 후보 삭제 */ public function removeDeprecation(int $id): void { ApiDeprecation::where('id', $id)->delete(); } /** * 폐기 후보 목록 조회 */ public function getDeprecations(?string $status = null): Collection { $query = ApiDeprecation::with('creator') ->orderByDesc('created_at'); if ($status) { $query->where('status', $status); } return $query->get(); } /** * 인기 API 조회 (상위 N개) */ public function getPopularApis(int $limit = 20): Collection { return $this->getUsageStats()->take($limit); } /** * 최근 사용되지 않은 API (N일 이상) */ public function getStaleApis(int $days = 30): Collection { $cutoffDate = now()->subDays($days); // 최근 호출된 API (DB facade 사용 - Eloquent accessor 충돌 방지) $recentlyUsed = DB::table('api_request_logs') ->selectRaw("REGEXP_REPLACE(SUBSTRING_INDEX(url, '?', 1), '^https?://[^/]+', '') as endpoint") ->addSelect('method') ->where('created_at', '>=', $cutoffDate) ->distinct() ->get() ->map(fn ($item) => $item->endpoint.'|'.$item->method) ->toArray(); // 전체 사용 통계에서 최근 사용된 것 제외 return $this->getUsageStats() ->filter(function ($item) use ($recentlyUsed) { $key = $item->endpoint.'|'.$item->method; return ! in_array($key, $recentlyUsed); }); } /** * 일별 API 호출 추이 */ public function getDailyTrend(int $days = 30): Collection { return DB::table('api_request_logs') ->select(DB::raw('DATE(created_at) as date')) ->selectRaw('COUNT(*) as call_count') ->selectRaw("COUNT(DISTINCT CONCAT(REGEXP_REPLACE(SUBSTRING_INDEX(url, '?', 1), '^https?://[^/]+', ''), '|', method)) as unique_endpoints") ->where('created_at', '>=', now()->subDays($days)) ->groupBy('date') ->orderBy('date') ->get(); } }