Защищенный таймер для опросов: реализация бизнес-логики
При разработке веб-приложений, особенно систем опросов с ограничением по времени, возникает задача создания защищенного таймера. Клиентское время легко подделывается, поэтому полагаться на локальную дату пользователя - рискованно. В этой статье мы разберём, как реализовать бизнес-логику таймера, который устойчив к манипуляциям и синхронизирован между всеми устройствами.
Почему локальный таймер - плохое решение?
Если вы храните оставшееся время в 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) });
});Клиент получает число секунд и обновляет интерфейс. Никакой логики вычислений на фронтенде - только отображение.