65 lines
2.3 KiB
PHP
65 lines
2.3 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Models\PageVisit;
|
|
use App\Support\UserAgentParser;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Http\Response;
|
|
use Illuminate\Support\Facades\Cache;
|
|
|
|
class PageHitController extends Controller
|
|
{
|
|
private const GIF_1X1 = "GIF89a\x01\x00\x01\x00\x80\x00\x00\xff\xff\xff\x00\x00\x00!\xf9\x04\x01\x00\x00\x00\x00,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;";
|
|
|
|
public function __invoke(Request $request): Response
|
|
{
|
|
$channelId = strtolower(trim((string) $request->query('c', $request->input('c', ''))));
|
|
$clientUid = trim((string) $request->query('u', $request->input('u', '')));
|
|
$path = trim((string) $request->query('p', $request->input('p', '')));
|
|
|
|
if (! preg_match('/^[0-9a-f]{32}$/', $channelId)) {
|
|
return $this->pixel();
|
|
}
|
|
if (! preg_match('/^[0-9a-fA-F-]{8,64}$/', $clientUid) && ! preg_match('/^tmp_[0-9a-f]+$/i', $clientUid)) {
|
|
return $this->pixel();
|
|
}
|
|
if (strlen($path) > 255) {
|
|
$path = substr($path, 0, 255);
|
|
}
|
|
|
|
$debounceKey = 'page_hit:'.$channelId.':'.$clientUid;
|
|
if (! Cache::add($debounceKey, 1, now()->addSeconds(8))) {
|
|
return $this->pixel();
|
|
}
|
|
|
|
$ua = substr((string) $request->userAgent(), 0, 512);
|
|
$parsed = UserAgentParser::parse($ua);
|
|
|
|
PageVisit::query()->create([
|
|
'channel_id' => $channelId,
|
|
'client_uid' => substr($clientUid, 0, 64),
|
|
'user_agent' => $ua !== '' ? $ua : null,
|
|
'os' => $parsed['os'],
|
|
'os_version' => $parsed['os_version'] !== '' ? $parsed['os_version'] : null,
|
|
'browser' => $parsed['browser'],
|
|
'browser_version' => $parsed['browser_version'] !== '' ? $parsed['browser_version'] : null,
|
|
'ip' => $request->ip(),
|
|
'path' => $path !== '' ? $path : null,
|
|
'created_at' => now(),
|
|
]);
|
|
|
|
return $this->pixel();
|
|
}
|
|
|
|
private function pixel(): Response
|
|
{
|
|
return response(self::GIF_1X1, 200, [
|
|
'Content-Type' => 'image/gif',
|
|
'Content-Length' => (string) strlen(self::GIF_1X1),
|
|
'Cache-Control' => 'no-store, no-cache, must-revalidate, max-age=0',
|
|
'Pragma' => 'no-cache',
|
|
]);
|
|
}
|
|
}
|