Защищенный таймер для опросов: реализация бизнес-логики

    При разработке веб-приложений, особенно систем опросов с ограничением по времени, возникает задача создания защищенного таймера. Клиентское время легко подделывается, поэтому полагаться на локальную дату пользователя - рискованно. В этой статье мы разберём, как реализовать бизнес-логику таймера, который устойчив к манипуляциям и синхронизирован между всеми устройствами.

    Почему локальный таймер - плохое решение?

    Если вы храните оставшееся время в JavaScript или опираетесь на Date.now() на стороне клиента, пользователь может:

    • Перевести часы назад и продлить себе время;
    • Изменить системную дату на устройстве;
    • Использовать разные устройства с разным временем.

    Для опросов, конкурсов или экзаменов это недопустимо. Единственный надёжный способ - серверный таймер, который не зависит от клиентских данных.

    Как работает защищённый серверный таймер

    Основная идея: хранить на сервере момент старта таймера (в UTC) и фиксированную длительность. Оставшееся время вычисляется как разница между (время_старта + длительность) и текущим серверным временем. Клиент получает только отображение, но не может повлиять на отсчёт.

    Шаг 1. Запись старта в базе данных

    При начале опроса создаётся запись с полями:

    • user_id - идентификатор пользователя или сессии;
    • start_time - метка времени на сервере (UTC);
    • duration - длительность в секундах (например, 10800 для 3 часов).

    Пример SQL (PostgreSQL):

    CREATE TABLE poll_timer (
        user_id INT PRIMARY KEY,
        start_time TIMESTAMP WITH TIME ZONE NOT NULL,
        duration INT NOT NULL
    );

    Шаг 2. Вычисление оставшегося времени на сервере

    При каждом запросе (например, AJAX-проверка или загрузка страницы) сервер выполняет:

    remaining = (start_time + duration) - NOW()

    Если remaining <= 0, опрос принудительно завершается. Клиенту возвращается только remaining в секундах, никогда - start_time или duration.

    Шаг 3. Синхронизация между устройствами

    Когда пользователь заходит с другого устройства, сервер проверяет существующую запись в БД. Если таймер уже запущен (по user_id), оставшееся время вычисляется на основе того же start_time. Таким образом, время одинаково на ПК, телефоне или планшете.

    Дополнительные меры защиты

    Чтобы исключить любые попытки обхода, используйте:

    • HTTPS - шифрование трафика предотвращает перехват и подмену ответов сервера;
    • Токены сессии - привязка таймера к конкретной сессии или пользователю;
    • Ограничение частоты запросов - чтобы злоумышленник не мог «заморозить» таймер спамом;
    • Логирование - фиксируйте все попытки завершения опроса для аудита.

    Пример реализации на Node.js (Express)

    Упрощённый код серверного эндпоинта для получения оставшегося времени:

    app.get('/api/timer', async (req, res) => {
        const userId = req.session.userId;
        const timer = await db.query('SELECT start_time, duration FROM poll_timer WHERE user_id = $1', [userId]);
        if (!timer.rows.length) return res.json({ remaining: 0 });
        const remaining = Math.max(0, (timer.rows[0].start_time.getTime() + timer.rows[0].duration * 1000) - Date.now());
        res.json({ remaining: Math.floor(remaining / 1000) });
    });

    Клиент получает число секунд и обновляет интерфейс. Никакой логики вычислений на фронтенде - только отображение.

    Часто задаваемые вопросы (FAQ)

    Часто задаваемые вопросы