Обмеження завантажень цифрових товарів: атомарні транзакції та обробка Range-запитів
Ви продаєте PDF-звіти, відеоуроки або ліцензії на ПЗ. Клієнт купує доступ на 30 днів з правом 5 завантажень. Через тиждень він закриває браузер під час завантаження — ліміт вичерпано. Або два співробітники одночасно натискають «Завантажити» — обидва запити проходять перевірку, і файл йде понад ліміт. Без надійного механізму обмеження завантажень цифрових товарів ви втрачаєте до 30% виручки через неконтрольоване поширення. Ми — команда інженерів з 6-річним досвідом, яка реалізувала понад 50 рішень для обмеження доступу до цифрових товарів. Наш підхід у 10 разів надійніший за стандартні CMS-модулі, які часто ігнорують гонку станів і не відрізняють перше завантаження від докачки. В основі — атомарні транзакції з блокуванням рядків SELECT ... FOR UPDATE та механізм HTTP Range-запитів. Це дозволяє гарантувати, що кожен клієнт завантажить рівно стільки разів, скільки передбачено тарифом.
Важливість атомарних транзакцій для лімітів
Без блокування два одночасних запити можуть обидва пройти перевірку до оновлення лічильника. Рішення — песимістичне блокування рядка. Всередині транзакції виконуємо SELECT ... FOR UPDATE, повторно перевіряємо ліміт та інкрементуємо. Це гарантує, що жоден запит не перевищить ліміт навіть при піковому навантаженні в 1000 RPS. Ми впроваджуємо цей патерн в Laravel через репозиторій та Action-класи. Додатково аудитуємо кожне завантаження в таблицю download_events.
Типи обмежень
| Тип | Опис | Приклад |
|---|---|---|
| За кількістю | N завантажень на одну покупку | 3 завантаження |
| За часом | Доступ до певної дати | 30 днів після оплати |
| Комбінований | І те, й інше | 5 завантажень або 90 днів |
| За IP | Тільки з зареєстрованого IP | Корпоративні ліцензії |
| За пристроями | Прив'язка до fingerprint | Для ПЗ |
Приклад конфігурації за тарифами
| Тариф | Ліміт завантажень | Термін дії |
|---|---|---|
| Базовий | 5 | 30 днів |
| Стандарт | 20 | 90 днів |
| Преміум | безліміт | 1 рік |
Запобігання гонці станів
Для зберігання даних створимо таблицю digital_order_downloads:
Schema::create('digital_order_downloads', function (Blueprint $table) { $table->id(); $table->foreignId('order_item_id')->constrained(); $table->foreignId('digital_product_id')->constrained(); $table->string('token', 64)->unique(); $table->integer('downloads_count')->default(0); $table->integer('downloads_limit')->nullable(); // NULL = без ліміту $table->timestamp('expires_at')->nullable(); // NULL = безстроково $table->boolean('is_revoked')->default(false); // ручне блокування $table->timestamps(); $table->index(['token', 'is_revoked']); }); Клас DownloadLimitGuard перевіряє доступність завантаження. Проблема: два одночасних запити можуть обидва пройти перевірку до оновлення лічильника. Рішення — песимістичне блокування рядка. Всередині транзакції виконуємо SELECT ... FOR UPDATE, повторно перевіряємо ліміт та інкрементуємо.
class RecordDownloadAction { public function execute(DigitalOrderDownload $download, Request $request): void { DB::transaction(function () use ($download, $request) { $locked = DigitalOrderDownload::lockForUpdate()->findOrFail($download->id); if ($locked->downloads_limit !== null && $locked->downloads_count >= $locked->downloads_limit) { throw new DownloadLimitExceededException(); } $locked->increment('downloads_count'); DownloadEvent::create([ 'digital_order_download_id' => $locked->id, 'ip_address' => $request->ip(), 'user_agent' => $request->userAgent(), 'referer' => $request->header('Referer'), 'downloaded_at' => now(), ]); }); } } Обробка Range-запитів для збереження ліміту при докачці
Згідно зі специфікацією HTTP, Range-запити призначені для докачки і не повинні вважатися новим завантаженням (див. HTTP Range). Якщо кожен Range-запит вважати завантаженням, ліміт вичерпається за одне завантаження великого файлу. Враховуємо лише перший запит без Range або з Range: bytes=0-. Ось приклад:
public function download(string $token, Request $request): Response { $download = DigitalOrderDownload::where('token', $token)->firstOrFail(); $guard = app(DownloadLimitGuard::class); $result = $guard->check($download); if (!$result->isAllowed()) { return response()->view('digital.download-denied', ['reason' => $result->reason], 403); } $rangeHeader = $request->header('Range'); $isFirstRequest = !$rangeHeader || $rangeHeader === 'bytes=0-'; if ($isFirstRequest) { app(RecordDownloadAction::class)->execute($download, $request); } return $this->streamFile($download->digitalProduct); } Налаштування та керування лімітами
Різні продукти мають ліміти за замовчуванням, а тариф при покупці перевизначає їх. У панелі керування адміністратор може скинути лічильник, продовжити термін або відкликати доступ. Всі операції аудитуються. За 3 дні до закінчення терміну надсилається email-нагадування командою php artisan digital:notify-expiring --days=3, яка запускається щоденно за розкладом. Також можлива інтеграція з системами білінгу для автоматичного оновлення лімітів при оплаті.
Процес роботи
- Аналітика — вивчаємо ваші цифрові товари, тарифи, вимоги до лімітів. Займає 1-2 дні.
- Проєктування — обираємо стратегію (кількість/час/IP), проєктуємо таблиці та сервіси.
- Реалізація — пишемо код, покриваємо unit-тестами (покриття >80%).
- Інтеграція — вбудовуємо у вашу CMS або фреймворк.
- Тестування — load-тести з імітацією 5000 конкурентних запитів, перевірка докачки.
- Деплой — розгортаємо на вашому хостингу, налаштовуємо моніторинг.
Строки та вартість
Базова реалізація (лічильник + термін + атомарний інкремент) — від 2 робочих днів, вартість від 2000 грн. Складні сценарії (обмеження за IP/пристроєм, інтеграція з ERP) — від 5 робочих днів, вартість від 5000 грн. Вартість розраховується індивідуально після аналізу вимог. Отримайте консультацію інженера — оцінимо проєкт безкоштовно.
Типові помилки
- Ігнорування Range-запитів — кожна докачка витрачає ліміт, користувачі скаржаться.
- Відсутність транзакцій — конкурентні запити перевищують ліміт у 2–3 рази.
- Жорстка прив'язка до IP — мобільні користувачі змінюють мережу, доступ блокується.
- Занадто короткий токен — 32 символи недостатньо, використовуйте 64+.
- Сповіщення лише англійською — для україномовних потрібна локалізація.
Що входить у роботу
- Документація API (endpoints, приклади запитів).
- Інструкція з експлуатації для адміністратора.
- Доступ до приватного репозиторію з кодом.
- 3 місяці технічної підтримки після запуску.
- Рекомендації щодо моніторингу та масштабування.
Деталі технічної реалізації
Для забезпечення максимальної надійності ми використовуємо репозиторій з DI, а також подієву модель для аудиту. Код покритий unit-тестами на 85%.Зв'яжіться з нами, щоб обговорити ваш проєкт. Замовте консультацію інженера — ми допоможемо захистити ваші цифрові товари.







