Практические решения для обработки кликов по URL на PHP

Раздел: Разработка на PHP

Основные подходы к обработке кликов

Как реализовать универсальный трекер кликов с мгновенным перенаправлением и логированием?

Основное и наиболее надёжное решение - использование PHP-скрипта в качестве посредника. Все ссылки на сайте заменяются на адрес вида /track.php?url=.... При переходе скрипт фиксирует событие (в файл, базу данных или сторонний сервис) и сразу выполняет редирект. Такой подход работает без JavaScript и не зависит от браузера пользователя.

Пример простого скрипта track.php:


<?php
$url = $_GET['url'] ?? null;
if (!$url) {
    header('HTTP/1.1 400 Bad Request');
    exit('URL not provided');
}

// Логирование в текстовый файл (добавление строки)
$logEntry = date('Y-m-d H:i:s') . ' | ' . $_SERVER['REMOTE_ADDR'] . ' | ' . $url . "\n";
file_put_contents('clicks.log', $logEntry, FILE_APPEND | LOCK_EX);

// Перенаправление
header('Location: ' . $url, true, 302);
exit;
?>
  
Результат: при переходе на http://site.com/track.php?url=https://example.com в файл clicks.log добавляется запись, а пользователь перенаправляется на https://example.com.
  

Типичные проблемы: открытая редиректы (можно перенаправить на любой сайт) - риск для безопасности. Рекомендуется проверять url через белый список доменов или как минимум фильтровать протокол (разрешать только http/https). Также возможна потеря меток UTM при кодировании - используйте urlencode() при формировании ссылок.

Как использовать базу данных для более глубокой аналитики кликов?

Вместо текстового лога можно сохранять данные в MySQL или SQLite. Это позволяет выполнять сложные запросы - количество кликов по дням, источники (реферер), браузеры и т.д. Пример структуры таблицы:


CREATE TABLE clicks (
    id INT AUTO_INCREMENT PRIMARY KEY,
    url VARCHAR(2048) NOT NULL,
    ip VARCHAR(45) NOT NULL,
    user_agent TEXT,
    referer TEXT,
    clicked_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
  

Вставка данных через PDO:


$stmt = $pdo->prepare('INSERT INTO clicks (url, ip, user_agent, referer) VALUES (:url, :ip, :ua, :ref)');
$stmt->execute([
    ':url' => $url,
    ':ip' => $_SERVER['REMOTE_ADDR'],
    ':ua' => $_SERVER['HTTP_USER_AGENT'] ?? '',
    ':ref' => $_SERVER['HTTP_REFERER'] ?? ''
]);
  

Проблемы: при большом количестве кликов база может стать узким местом. Рекомендуется использовать очереди (RabbitMQ) или асинхронную запись. Также нельзя полагаться на HTTP_REFERER - он может отсутствовать или быть подделан.

Как отслеживать клики без изменения ссылок с помощью JavaScript?

Если изменить все URL на сайте невозможно, можно добавить слушатель события click на все ссылки и отправить асинхронный запрос к PHP скрипту до перехода. Это решение требует JavaScript и не работает при выключенном JS.


// Пример с использованием fetch
 document.querySelectorAll('a').forEach(function(link) {
    link.addEventListener('click', function(e) {
        e.preventDefault();
        var url = this.href;
        fetch('/log_click.php?url=' + encodeURIComponent(url), { method: 'GET', keepalive: true })
            .then(function() {
                window.location.href = url;
            });
    });
 });
  

PHP-скрипт log_click.php может быть таким же, как в базовом решении, но без редиректа (он уже выполняется на стороне браузера).

Ошибки: при быстром уходе со страницы запрос может не успеть завершиться. Используйте параметр keepalive: true или отправляйте через navigator.sendBeacon(). Также возможны блокировки CORS при кросс-доменных ссылках.

Как обрабатывать клики по URL через .htaccess и PHP?

С помощью mod_rewrite можно перенаправить все запросы на определённый PHP-скрипт, который в свою очередь обработает URL из строки запроса. Это позволяет скрыть существование скрипта-посредника и сохранить оригинальный вид ссылок (например, /go/example.com).


RewriteEngine On
RewriteRule ^go/(.+)$ track.php?url=$1 [L,QSA]
  

В track.php принимаем $_GET['url'] и обрабатываем как обычно. Для безопасности декодируем URL (urldecode) и проверяем.

Проблемы: если в URL содержатся слэши, правило может не сработать. Используйте RewriteRule ^go/(.*)$ и кодируйте слэши при передаче. Также нужно аккуратно настраивать исключения для статических файлов.

Как отслеживать клики с помощью изображения-пикселя (tracking pixel)?

Если необходимо отследить открытие письма или посещение страницы, можно использовать невидимое изображение размером 1x1, src которого ведёт на PHP-скрипт. Сам скрипт ничего не выводит, кроме пустого GIF, и логирует запрос.


// pixel.php
<?php
// Логирование (можно записать, что был запрос с таким-то IP)
file_put_contents('pixel.log', date('Y-m-d H:i:s') . "\n", FILE_APPEND);

// Возвращаем 1x1 прозрачный GIF
header('Content-Type: image/gif');
echo base64_decode('R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7');
?>
  

В HTML: <img src="pixel.php" width="1" height="1" alt="" />

Недостатки: пиксель может быть заблокирован почтовыми клиентами или расширениями, блокирующими трекинг. Не передаётся информация о URL, на котором размещён пиксель (можно добавить параметры в src: pixel.php?page=home).

Как добавить защиту от CSRF и повторного использования ссылок?

Для предотвращения подделки запросов можно генерировать одноразовый токен, который добавляется в URL вместе с целевым адресом. Токен проверяется в скрипте и после первого использования удаляется из базы. Пример генерации:
$token = bin2hex(random_bytes(16));
Сохраните токен вместе с URL и временем истечения. При обработке проверьте существование и срок действия.

Ошибки: при кэшировании страницы токен может стать недействительным. Используйте JavaScript для динамической подстановки токена или короткий срок жизни (несколько минут).

Расширенные примеры и пояснения

1. Комплексный трекер с фильтрацией и геолокацией

Используем базу данных (SQLite), определяем страну по IP (через сторонний API) и блокируем нежелательные домены.

Пример

<?php
$allowedDomains = ['example.com', 'mysite.org'];
$url = $_GET['url'] ?? '';
$parsed = parse_url($url);
$host = $parsed['host'] ?? '';

if (!in_array($host, $allowedDomains)) {
    header('HTTP/1.1 403 Forbidden');
    exit('Domain not allowed');
}

// Получение страны (через http://ip-api.com/json/)
$ip = $_SERVER['REMOTE_ADDR'];
$geo = json_decode(file_get_contents("http://ip-api.com/json/{$ip}"));
$country = $geo->country ?? 'Unknown';

// Запись в SQLite
$db = new SQLite3('clicks.db');
$stmt = $db->prepare('INSERT INTO clicks (url, ip, country, clicked_at) VALUES (:url, :ip, :country, datetime("now"))');
$stmt->bindValue(':url', $url, SQLITE3_TEXT);
$stmt->bindValue(':ip', $ip, SQLITE3_TEXT);
$stmt->bindValue(':country', $country, SQLITE3_TEXT);
$stmt->execute();

header('Location: ' . $url, true, 302);
?>
Результат: при переходе по /track.php?url=https://example.com выполняется проверка домена, запрос к API геолокации (может замедлить редирект), запись в SQLite, затем редирект. Если домен не разрешён - ошибка 403.

2. Асинхронная отправка через navigator.sendBeacon (без потери данных при закрытии страницы)

Пример

// JavaScript (вставляется на все страницы)
document.addEventListener('click', function(e) {
    var target = e.target.closest('a');
    if (target && target.href) {
        e.preventDefault();
        var data = new FormData();
        data.append('url', target.href);
        data.append('page', window.location.href);
        navigator.sendBeacon('/beacon.php', data);
        window.location.href = target.href;
    }
});
Пример

// beacon.php (PHP)
<?php
$url = $_POST['url'] ?? '';
$page = $_POST['page'] ?? '';
$line = date('Y-m-d H:i:s') . " | $url | $page | " . $_SERVER['REMOTE_ADDR'] . "\n";
file_put_contents('beacon.log', $line, FILE_APPEND);
http_response_code(204); // No Content
?>
Результат: при клике на любую ссылку данные отправляются асинхронно через sendBeacon (гарантированная доставка даже при закрытии вкладки), затем происходит навигация. В лог записывается время, целевой URL, страница, с которой произошёл клик, и IP.

3. Использование очереди Redis для высоконагруженных проектов

Вместо записи непосредственно в базу данных, PHP-скрипт помещает задачу в очередь Redis, а отдельный worker асинхронно обрабатывает и сохраняет в MySQL.

Пример

// track.php (ставит задачу)
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$job = json_encode([
    'url' => $_GET['url'],
    'ip' => $_SERVER['REMOTE_ADDR'],
    'time' => time()
]);
$redis->lPush('click_queue', $job);
header('Location: ' . $_GET['url']);
Пример

// worker.php (запускается как демон)
while (true) {
    $job = $redis->brPop('click_queue', 5);
    if ($job) {
        $data = json_decode($job[1], true);
        // сохраняем в MySQL
        $stmt = $pdo->prepare('INSERT INTO clicks (url, ip, clicked_at) VALUES (?, ?, FROM_UNIXTIME(?))');
        $stmt->execute([$data['url'], $data['ip'], $data['time']]);
    }
}
Результат: скрипт-перенаправитель почти мгновенно возвращает редирект, а запись в БД выполняется асинхронно, что снижает нагрузку на PHP-FPM и предотвращает задержки.

4. Динамическая генерация ссылок с подписью (HMAC) для предотвращения подмены

Пример

// Генерация ссылки на стороне шаблона:
$secret = 'my_secret_key';
$targetUrl = 'https://partner.com/offer';
$expires = time() + 3600; // 1 час
$signature = hash_hmac('sha256', $targetUrl . $expires, $secret);
$trackLink = '/go?url=' . urlencode($targetUrl) . '&exp=' . $expires . '&sig=' . $signature;
Пример

// track.php - проверка подписи
$url = $_GET['url'] ?? '';
$expires = (int)($_GET['exp'] ?? 0);
$sig = $_GET['sig'] ?? '';

$expectedSig = hash_hmac('sha256', $url . $expires, $secret);
if (!hash_equals($expectedSig, $sig) || time() > $expires) {
    header('HTTP/1.1 403 Forbidden');
    exit('Invalid or expired link');
}

// Логирование и редирект
Результат: только ссылки, сгенерированные на сервере с правильным секретом, будут работать. Срок действия ограничен, подделать подпись злоумышленнику практически невозможно.

Обработка кликов по URL в PHP - comments

En
Click php url (php)