Powrót do strony głównej

Projekt walidacji biletów QR dla kin

Artykuł omawia projekt systemu walidacji biletów QR kin. Od podpisu offline do idempotentnych API z transakcjami PostgreSQL. Omówione problemy timeoutów, blokowania HoL i kompromisy.

Jak zaprojektować niezawodną walidację biletów w kinie
Advertisement 728x90

Projekt systemu walidacji biletów QR: od trybu offline do idempotentności

Kontroler skanuje kod QR biletu, system sprawdza go w bazie i oznacza jako użyty. Główny wymóg: jeden bilet — jedno wejście. Proste zadanie na pierwszy rzut oka komplikują awarie sieci, kolejki i wielu kontrolerów. Prześledźmy ewolucję rozwiązań od walidacji offline do niezawodnego przetwarzania serwerowego.

Tryb offline wykorzystuje podpis cyfrowy (ECC lub RSA). QR zawiera dane biletu i podpis, kontroler weryfikuje kluczem publicznym bez sieci. Problem: brak synchronizacji między kontrolerami. Zrzut ekranu biletu przechodzi przez każdą bramkę niezależnie.

Stan serwera i aktualizacje atomowe

Jedno repozytorium rozwiązuje problem synchronizacji. Każde skanowanie to zapytanie do serwera z atomową aktualizacją statusu.

Google AdInline article slot

Przykład w PostgreSQL:

UPDATE tickets 
SET status = 'wasted', used_at = NOW()
WHERE id = :ticket_id AND status = 'active'
RETURNING *;

Jeśli zwrócony zostanie wiersz — bilet został wykorzystany, wejście dozwolone. Brak wiersza — bilet już użyty lub nieistniejący.

Klasyczny problem: utrata odpowiedzi z serwera. Kontroler w strefie słabego sygnału wysyła zapytanie, serwer aktualizuje bazę, ale odpowiedź nie dociera. Limit czasu, ponowne skanowanie — serwer odmawia, klient nie wchodzi mimo opłaconego biletu.

Google AdInline article slot

Idempotentność na poziomie API

Rozwiązanie — zapytania idempotentne z unikalnym kluczem (UUID), generowanym przy otwarciu sesji skanowania. Klucz rejestruje próbę, umożliwia ponowienia bez duplikacji.

Logika w transakcji (read committed):

begin ;

-- 1. Rejestrujemy próbę
INSERT INTO processed_requests (ticket_id, idempotency_key)
VALUES (:ticket_id, :idempotency_key)
ON CONFLICT (ticket_id, idempotency_key) 
DO NOTHING RETURNING *;

-- 2. Jeśli rows_affected == 0: rollback, zwróć SUCCESS

-- 3. Wykorzystujemy bilet
UPDATE tickets 
SET status = 'wasted', used_at = NOW()
WHERE id = :ticket_id AND status = 'active'
RETURNING *;

-- 4. Jeśli rows_affected == 0: rollback, zwróć ERROR

commit;
-- 5. Zwróć SUCCESS

Transakcja zapewnia atomowość: albo klucz + status zaktualizowane, albo nic. Unikalny indeks na (ticket_id, idempotency_key) zapobiega wyścigom — drugie zapytanie czeka lub przechodzi do DO NOTHING.

Google AdInline article slot

Ponowienie w tej samej sesji zwraca sukces, nawet jeśli pierwsza odpowiedź zginęła.

Head-of-Line Blocking i utrata kontekstu

Rzeczywisty problem: kontroler pod presją kolejki zamyka sesję bez odpowiedzi. Nowe skanowanie generuje świeży idempotency_key. Serwer widzi użyty bilet z obcym kluczem — odmowa.

Kontroler nie odróżni prawowitego klienta od oszusta ze zrzutem ekranu. HoL-blokowanie wymaga ostatecznej odpowiedzi przed kolejnym biletem.

Kompromis: zwracać timestamp ostatniego użycia. Kontroler ocenia: "2 minuty temu — ta sama osoba". Ryzyko: grupa ze wspólnym QR przechodzi kolejno.

Kluczowe wyzwania:

  • Limity czasu sieci bez utraty stanu
  • Synchronizacja wielu kontrolerów
  • Czynnik ludzki w kolejkach
  • Równowaga niezawodności i UX

Co jest ważne

  • Atomowy UPDATE z warunkiem status='active' zapobiega podwójnemu wykorzystaniu
  • Idempotentność przez UUID + unikalny indeks gwarantuje ponowienia bez duplikatów
  • Transakcje (read committed) zapewniają sekwencyjną widoczność zmian
  • HoL-blokowanie nieuniknione dla 100% niezawodności bez biometrii
  • Kompromis timestamp przenosi decyzję na operatora

Perspektywy ulepszeń

Statyczne QR się dezaktualizują. Dynamiczne tokeny lub challenge-response z jednorazowymi OTP rozwiązują problem. Kontroler żąda nonce od serwera, klient podpisuje — serwer weryfikuje i oznacza atomowo.

Biometria (face ID) lub NFC eliminują zrzuty ekranu, ale wymagają sprzętu. QUIC minimalizuje utratę pakietów, ale nie rozwiązuje utraty kontekstu.

Skalowanie: Redis do cache idempotency_keys, sharding po ticket_id. Monitorowanie: metryki failed_scans po timestamp, A/B-testy kompromisów.

— Editorial Team

Advertisement 728x90

Czytaj dalej