web: Abruf-Protokoll der RSS-Feeds — Reader-Dienst, gemeldete Abonnenten, Admin-Bericht, Panel (Roadmap 3.1)
- feed_fetches: jeder Feed-Abruf mit Art, Schlüssel, Portal, Sprache, erkanntem Reader (config/feeds.php, FeedReaderIdentifier), vom Dienst gemeldeter Abonnentenzahl und gekürztem User-Agent — keine IP. Schreiben per defer() nach der Antwort, auch bei Cache-Treffer. - Aufbewahrung 395 Tage über press-releases:purge-events; FEED_FETCHES_RECORDED_SINCE. - Admin ▸ Reports ▸ Indexierung & Abrufe: Block „Abrufe der RSS-Feeds" (Summen je Feed-Art, je Reader-Dienst, meistabgerufene Feeds; FeedFetchReport, Verdichtung in PHP für MySQL und SQLite). - Panel „Auslieferung": Abrufe je Feed in 30 Tagen; RSS-Seite nennt die Zählung ohne IP. - Tests: FeedFetchLogTest (5), IndexStatusCheckTest erweitert. - dev/2026-09-01: Nachtrag Protokoll Teil 11, Roadmap 3.1; 0.7 gilt auch für Crawler- und Feed-Log. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
23811ad4d2
commit
44d0bb6f29
18 changed files with 794 additions and 13 deletions
|
|
@ -5,6 +5,7 @@ namespace App\Http\Controllers\Web;
|
|||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Category;
|
||||
use App\Models\Company;
|
||||
use App\Models\FeedFetch;
|
||||
use App\Models\PressRelease;
|
||||
use App\Scopes\PortalScope;
|
||||
use App\Services\Company\MergedNewsroomLookup;
|
||||
|
|
@ -20,6 +21,8 @@ use Illuminate\Http\Response;
|
|||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
use function Illuminate\Support\defer;
|
||||
|
||||
/**
|
||||
* RSS-Feeds (Roadmap 3.1): je Portal (`/{edition}/feed`), je Branche
|
||||
* (`/{edition}/category/{slug}/feed`, inklusive Unterkategorien wie die
|
||||
|
|
@ -46,7 +49,7 @@ class FeedController extends Controller
|
|||
$context = WebPortalContext::fromRequest();
|
||||
$base = rtrim($request->getSchemeAndHttpHost(), '/');
|
||||
|
||||
return $this->respond($context, ['all'], function () use ($context, $base): string {
|
||||
return $this->respond($context, [FeedFetch::FEED_PORTAL], function () use ($context, $base): string {
|
||||
$releases = $this->releases($context)->get($this->columns());
|
||||
$brand = (array) ($context->domainConfig['brand'] ?? []);
|
||||
|
||||
|
|
@ -65,7 +68,7 @@ class FeedController extends Controller
|
|||
$base = rtrim($request->getSchemeAndHttpHost(), '/');
|
||||
$language = $context->language;
|
||||
|
||||
return $this->respond($context, ['category', $slug], function () use ($context, $base, $slug, $language): string {
|
||||
return $this->respond($context, [FeedFetch::FEED_CATEGORY, $slug], function () use ($context, $base, $slug, $language): string {
|
||||
$category = Category::query()
|
||||
->whereIn('portal', $context->portalValues)
|
||||
->where('is_active', true)
|
||||
|
|
@ -132,7 +135,7 @@ class FeedController extends Controller
|
|||
abort(404);
|
||||
}
|
||||
|
||||
return $this->respond($context, ['newsroom', $slug], function () use ($context, $base, $company, $language): string {
|
||||
return $this->respond($context, [FeedFetch::FEED_NEWSROOM, $slug], function () use ($context, $base, $company, $language): string {
|
||||
$releases = $this->releases($context)->where('company_id', $company->id)->get($this->columns());
|
||||
|
||||
if ($releases->isEmpty()) {
|
||||
|
|
@ -153,7 +156,11 @@ class FeedController extends Controller
|
|||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $keySegments
|
||||
* Antwort aus dem Cache (oder frisch gebaut) plus Abruf-Protokoll: jeder
|
||||
* Feed-Abruf wird nach der Antwort ohne IP festgehalten (feed_fetches),
|
||||
* auch bei warmem Cache — der Abruf ist die Messgröße, nicht der Aufbau.
|
||||
*
|
||||
* @param array{0: string, 1?: string} $keySegments Feed-Art und Schlüssel (Slug)
|
||||
* @param Closure(): string $build
|
||||
*/
|
||||
private function respond(WebPortalContext $context, array $keySegments, Closure $build): Response
|
||||
|
|
@ -163,6 +170,11 @@ class FeedController extends Controller
|
|||
$build,
|
||||
);
|
||||
|
||||
[$feed, $portal, $language, $userAgent] = [$keySegments[0], $context->portal->value, $context->language, request()->userAgent()];
|
||||
$feedKey = $keySegments[1] ?? null;
|
||||
|
||||
defer(static fn () => FeedFetch::recordDeferred($feed, $feedKey, $portal, $language, $userAgent));
|
||||
|
||||
return response($xml, 200, ['Content-Type' => 'application/rss+xml; charset=UTF-8']);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue