12-05-2026 Frontend dev
This commit is contained in:
parent
405df0a122
commit
5b8bdf4182
779 changed files with 480564 additions and 6241 deletions
184
app/Services/Api/LegacyApiAccessLogAnalyzer.php
Normal file
184
app/Services/Api/LegacyApiAccessLogAnalyzer.php
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services\Api;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class LegacyApiAccessLogAnalyzer
|
||||
{
|
||||
/**
|
||||
* @param list<string> $paths
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function analyze(array $paths, int $top = 20): array
|
||||
{
|
||||
$report = [
|
||||
'generated_at' => now()->toIso8601String(),
|
||||
'input_paths' => $paths,
|
||||
'missing_paths' => [],
|
||||
'summary' => [
|
||||
'files' => 0,
|
||||
'total_lines' => 0,
|
||||
'matched_requests' => 0,
|
||||
'legacy_key_requests' => 0,
|
||||
'unique_client_ips' => 0,
|
||||
'unique_api_key_fingerprints' => 0,
|
||||
],
|
||||
'endpoints' => [],
|
||||
'client_ips' => [],
|
||||
'api_key_fingerprints' => [],
|
||||
'status_codes' => [],
|
||||
'sample_requests' => [],
|
||||
];
|
||||
|
||||
foreach ($this->expandPaths($paths) as $path) {
|
||||
if (! is_readable($path)) {
|
||||
$report['missing_paths'][] = $path;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$report['summary']['files']++;
|
||||
$file = new \SplFileObject($path);
|
||||
|
||||
while (! $file->eof()) {
|
||||
$line = trim((string) $file->fgets());
|
||||
|
||||
if ($line === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$report['summary']['total_lines']++;
|
||||
$request = $this->parseLine($line);
|
||||
|
||||
if ($request === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$endpoint = $this->legacyEndpoint($request['path']);
|
||||
|
||||
if ($endpoint === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->recordMatch($report, $request, $endpoint);
|
||||
}
|
||||
}
|
||||
|
||||
$report['summary']['unique_client_ips'] = count($report['client_ips']);
|
||||
$report['summary']['unique_api_key_fingerprints'] = count($report['api_key_fingerprints']);
|
||||
|
||||
$this->sortAndLimit($report, $top);
|
||||
|
||||
return $report;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $paths
|
||||
* @return list<string>
|
||||
*/
|
||||
private function expandPaths(array $paths): array
|
||||
{
|
||||
$expanded = [];
|
||||
|
||||
foreach ($paths as $path) {
|
||||
$matches = Str::contains($path, ['*', '?', '['])
|
||||
? glob($path) ?: []
|
||||
: [$path];
|
||||
|
||||
foreach ($matches as $match) {
|
||||
$expanded[] = $match;
|
||||
}
|
||||
}
|
||||
|
||||
return array_values(array_unique($expanded));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{client_ip: string, method: string, target: string, path: string, query: string, status: string|null}|null
|
||||
*/
|
||||
private function parseLine(string $line): ?array
|
||||
{
|
||||
if (! preg_match('/^(?P<client_ip>\S+).*"(?P<method>GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)\s+(?P<target>\S+)[^"]*"\s+(?P<status>\d{3}|-)?/', $line, $matches)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$target = $matches['target'];
|
||||
$parsedUrl = parse_url($target);
|
||||
$path = Arr::get($parsedUrl, 'path', Str::before($target, '?'));
|
||||
$query = Arr::get($parsedUrl, 'query', Str::contains($target, '?') ? Str::after($target, '?') : '');
|
||||
|
||||
return [
|
||||
'client_ip' => $matches['client_ip'],
|
||||
'method' => $matches['method'],
|
||||
'target' => $target,
|
||||
'path' => $path,
|
||||
'query' => $query,
|
||||
'status' => $matches['status'] !== '-' ? ($matches['status'] ?? null) : null,
|
||||
];
|
||||
}
|
||||
|
||||
private function legacyEndpoint(string $path): ?string
|
||||
{
|
||||
$normalizedPath = Str::of($path)
|
||||
->replace('/index.php', '')
|
||||
->trim('/')
|
||||
->toString();
|
||||
|
||||
if (! preg_match('~(?:^|/)(pressrelease|pressreleaseimage|company|category|newsletter)/([a-zA-Z0-9_-]+)~', $normalizedPath, $matches)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return "{$matches[1]}/{$matches[2]}";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $report
|
||||
* @param array{client_ip: string, method: string, target: string, path: string, query: string, status: string|null} $request
|
||||
*/
|
||||
private function recordMatch(array &$report, array $request, string $endpoint): void
|
||||
{
|
||||
$report['summary']['matched_requests']++;
|
||||
$report['endpoints'][$endpoint] = ($report['endpoints'][$endpoint] ?? 0) + 1;
|
||||
$report['client_ips'][$request['client_ip']] = ($report['client_ips'][$request['client_ip']] ?? 0) + 1;
|
||||
|
||||
if ($request['status'] !== null) {
|
||||
$report['status_codes'][$request['status']] = ($report['status_codes'][$request['status']] ?? 0) + 1;
|
||||
}
|
||||
|
||||
parse_str($request['query'], $query);
|
||||
|
||||
if (filled($query['api_key'] ?? null)) {
|
||||
$report['summary']['legacy_key_requests']++;
|
||||
$fingerprint = hash('sha256', (string) $query['api_key']);
|
||||
$report['api_key_fingerprints'][$fingerprint] = ($report['api_key_fingerprints'][$fingerprint] ?? 0) + 1;
|
||||
}
|
||||
|
||||
if (count($report['sample_requests']) < 10) {
|
||||
$report['sample_requests'][] = [
|
||||
'method' => $request['method'],
|
||||
'target' => $this->maskApiKey($request['target']),
|
||||
'endpoint' => $endpoint,
|
||||
'client_ip' => $request['client_ip'],
|
||||
'status' => $request['status'],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
private function maskApiKey(string $target): string
|
||||
{
|
||||
return preg_replace('/([?&]api_key=)[^&]+/', '$1***', $target) ?? $target;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $report
|
||||
*/
|
||||
private function sortAndLimit(array &$report, int $top): void
|
||||
{
|
||||
foreach (['endpoints', 'client_ips', 'api_key_fingerprints', 'status_codes'] as $key) {
|
||||
arsort($report[$key]);
|
||||
$report[$key] = array_slice($report[$key], 0, $top, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue