Printomato

Printomato ფოტობეჭდვის კიოსკებს მართავს სავაჭრო ცენტრებში, ფესტივალებზე და სასტუმროების ლობიებში. ჩვენ ავაგეთ მისი ოპერატორის მხარე: სესიით ავთენტიფიცირებული REST API არსებული მოწყობილობების API-ის გვერდით და React კონსოლი, რომელიც მთელ ქსელს რეალურ დროში აჩვენებს — რომელი კიოსკია ონლაინ, რომელს გაუთავდა ქაღალდი, რა დაიბეჭდა დღეს და რომელი გადახდები დასრულდა.

წელი: 2024 — 2026 · კატეგორია: კიოსკების ქსელის მართვა

ჩვენი როლი

Full-stack, ორი დეველოპერის პროექტზე. ჩვენს პასუხისმგებლობაში იყო გადახდების სტეკი, ადმინ-API, React კონსოლი და კონტეინერიზებული დეპლოი.

პრობლემა

Printomato ფოტობეჭდვის კიოსკებს მართავს — სავაჭრო ცენტრების ატრიუმებში, ფესტივალის სტენდებზე, სასტუმროების ლობიებში. თითოეული კიოსკი ბრენდირებულ ფოტოებს იმ კამპანიისთვის ბეჭდავს, რომელიც ამ ადგილას აქტიურია, და ადრე თუ გვიან თითოეულს ქაღალდი უთავდება, კამერასთან კავშირი ეკარგება ან ყველაზე უხერხულ მომენტში ქსელიდან ითიშება.

ოპერაციული მუშაობა სერვერზე რენდერებულ Django გვერდებს ეყრდნობოდა: ერთი კამპანიებისთვის, ერთი მოწყობილობებისთვის, ერთი ფოტოებისთვის. ისინი მუშაობდა, მაგრამ ყოველთვის მხოლოდ იმას აჩვენებდა, რაც ბოლოს განახლების ღილაკზე დაჭერისას შეესაბამებოდა სინამდვილეს. კითხვაზე, რომელი კიოსკებია ახლა გათიშული, პასუხი ოთხი ჩანართის გადატვირთვასა და მათგან ყველაზე ძველის დაჯერებას ნიშნავდა. შემდეგ კიოსკებმა ბარათით გადახდების მიღება დაიწყეს, რამაც თვალყურის სადევნებელი კიდევ ერთი ცოცხალი პროცესი დაამატა — და არსად იყო კარგი ადგილი, საიდანაც ამის გაკეთება შეიძლებოდა.

მიდგომა

გადაწერა მხოლოდ ოპერატორის მხარეს შეეხო და არა კიოსკების ქსელს. კიოსკები სრულიად ხელუხლებელი დარჩა — ისინი კვლავ იმავე ფოტოს ენდფოინთზე აგზავნიან POST მოთხოვნას, კვლავ იმავე WebSocket-ს ხსნიან და გადახდის ენდფოინთებს კვლავ იმავე საერთო საიდუმლოთი მიმართავენ. პირველის გვერდით, `/api/admin/`-ზე, მეორე, სესიით ავთენტიფიცირებული API დაემატა, ხოლო მის გამოსაყენებლად `/app/`-ზე React-ის ერთგვერდიანი კონსოლი განთავსდა. ძველი სერვერზე რენდერებული გვერდები მთელი ამ ხნის განმავლობაში თავდაპირველ URL-ებზე მუშაობდა, ამიტომ გადართვისთვის განკუთვნილი დაძაბული შაბათ-კვირა არასოდეს დამდგარა.

ყველაფერი, რისიც ოპერატორს უნდა სჯეროდეს, სერვერზე ერთი ანოტირებული queryset-იდან ითვლება — ქაღალდის პროცენტი, დღევანდელი ბეჭდვები, კამპანიის პროგრესი, აქვს თუ არა მოწყობილობას ღია გაფრთხილებები — და ბრაუზერში არასოდეს იწყობა ხელახლა. კლიენტის მხარეს ყოველ ენდფოინთს ზუსტად ერთი TanStack Query ჰუკი აქვს, და ამავე ჰუკების ფაილში ინახება ქეშის გასაღებები და ინვალიდაციის წესებიც. მოწყობილობის რედაქტირება კიოსკების ბადესაც და მართვის პანელსაც ანახლებს, რადგან ეს წესი ერთხელ არის დაწერილი და არა რვა კომპონენტში გამეორებული.

მთავარი მომენტები

  • კიოსკები WebSocket-ს ღიად ინახავენ — მიერთებისას მოწყობილობა ონლაინად ინიშნება და მისი სტატუსი ყველა ღია კონსოლს ეგზავნება, ამიტომ კიოსკების ბადეს განახლების ღილაკი არასოდეს სჭირდება.
  • ოცდაშვიდი სესიით ავთენტიფიცირებული მარშრუტი მოწყობილობების API-ის გვერდით დაემატა და არა მასში ჩაშენდა: კიოსკები და თავდაპირველი სერვერზე რენდერებული გვერდები მთელი ამ ხნის განმავლობაში ხელუხლებლად მუშაობდა.
  • ქაღალდის პროცენტი, დღევანდელი ბეჭდვები, კამპანიის პროგრესი და ღია გაფრთხილებების მდგომარეობა სერვერზე ითვლება — პროცენტები serializer-ში, რაოდენობები queryset-ის ანოტაციებად — ამიტომ ერთი და იგივე რიცხვის სხვადასხვა მნიშვნელობა ორ ეკრანზე ვერასდროს გამოჩნდება.
  • KEEPZ ბარათით გადახდები: საერთო საიდუმლოთი დაცული ენდფოინთი თითოეული მოწყობილობისთვის ცალკე ტოკენს ქმნის, ნაკადს კიოსკზე მისივე სოკეტით უშვებს და ყოველ სესიას დასრულებისთანავე აღრიცხავს.
  • 96 Django ტესტი, რომელთაგან 73 ადმინ-API-ის კონტრაქტს აფიქსირებს — ფილტრები, პაგინაციის საზღვრები, ვალიდაციის შეტყობინებები, უფლებები.
  • ერთი `docker compose up` მთელ სტეკს — Postgres, Redis, Daphne, nginx — ისე აამუშავებს, რომ შედეგად მიიღება საწყისი მონაცემებით შევსებული კონსოლი, რომელშიც შესვლა შეიძლება.

არქიტექტურა

  • კიოსკი: თითოეული მოწყობილობა საკუთარ არხზე WebSocket-ს ღიად ინახავს. მიერთება მას ონლაინად მონიშნავს და მის სტატუსს ქსელის ჯგუფს უგზავნის; სოკეტის გაწყვეტა ამ სტატუსს ასუფთავებს. ბრძანებები — ხელახალი ბეჭდვა, გადატვირთვა, გადახდის დაწყება — იმავე კავშირით ბრუნდება უკან.
  • Django 5 + Channels: ერთი ASGI აპლიკაცია ემსახურება REST API-ს და არხების სამ მარშრუტს: მოწყობილობის სოკეტს, ქსელის არხს, რომელსაც კონსოლი გამოიწერს, და შეტყობინებების არხს, რომელიც გაფრთხილებების ტოსტებს მართავს. არხის ფენა Redis-ია. კონტეინერიზებული კონსოლი HTTP-სა და WebSocket-ებისთვის ერთ Daphne-ს იყენებს; თავდაპირველი compose კი gunicorn-ს WSGI აპლიკაციაზე ტოვებს, /ws/-ისთვის ცალკე Daphne-სთან ერთად.
  • დომენი: ხუთი დომენური მოდელი — Campaign, Device, Photo, Notification, PaymentSession — და მორგებული მომხმარებლის მოდელი. მენეჯერის მეთოდები ონლაინ მოწყობილობების რაოდენობას, ბეჭდვების ჯამებსა და კამპანიის მდგომარეობას queryset-ზე ანოტაციებად ამატებს, ამიტომ სიის ხედი თითოეული კიოსკისთვის ცალკე მოთხოვნად არ იყოფა.
  • ადმინ-API: ოცდაშვიდი სესიით ავთენტიფიცირებული მარშრუტი `/api/admin/`-ის ქვეშ: ხუთი viewset, ასევე ავთენტიფიკაციის, მართვის პანელისა და ოფციების ენდფოინთები, მოთხოვნის ფილტრაციით, გვერდის ზომით შეზღუდული პაგინაციის კონვერტით და drf-spectacular-ით გენერირებული OpenAPI სქემით.
  • კონსოლი: React 18 TypeScript-ზე strict რეჟიმში, Vite 6, Tailwind v4 CSS-first თემის ბლოკით და კონფიგურაციის ფაილის გარეშე, TanStack Query v5 სერვერის მდგომარეობისთვის, React Router v6 `/app` basename-ით. თერთმეტი მარშრუტი, მართვის პანელის გარდა დანარჩენზე კოდის დაყოფით, პირველ ჩატვირთვაზე 113 KB gzip-ით.
  • გადახდები: საჯარო გადახდის სერვისი საერთო საიდუმლოს სათაურით ავთენტიფიცირდება, რომ მოწყობილობის ტოკენი შეინახოს, კიოსკზე ნაკადი გაუშვას და შედეგად მიღებული სესიის ჩანაწერი შექმნას ან განაახლოს. კონსოლი ამ რეესტრს უკან კითხულობს შეჯამებით, რომელიც ნებისმიერ მიმდინარე ფილტრზე ითვლება — შემოსავალი, წარმატების მაჩვენებელი, უარყოფილი გადახდები.
  • მედია: Pillow შენახვისას მინიატურას აგენერირებს. ფაილები დეველოპმენტში ლოკალურ დისკზე ხვდება, პროდაქშენში კი S3-თავსებად bucket-ში, ერთი და იმავე საცავის ინტერფეისის მეშვეობით. მონიშნული ფოტოები ერთ ZIP-ად ჩამოიტვირთება.
  • დეპლოი: ცალკე compose ფაილი კონსოლს Node-ის ეტაპზე აგებს, სტატიკურ ფაილებს Python-ის ეტაპზე აგროვებს და წინ nginx-ს აყენებს, შემდეგ მიგრაციებს უშვებს, ბაზას საწყისი მონაცემებით ავსებს და მოთხოვნებს ემსახურება — ერთი ბრძანება რეპოზიტორიის კლონირებიდან კონსოლამდე, რომელშიც უკვე შეიძლება შესვლა.

ციფრებში

Django ტესტები: 96 · ადმინ-API-ის მარშრუტები: 27 · კონსოლის პროდაქშენ ბილდი, gzip-ით: 113 KB · WebSocket არხები რეალურ დროში: 3

შედეგები

  • კიოსკების ქსელის მდგომარეობა რეალურ დროში ახლდება. კავშირი რომ გაუწყდება, კიოსკი ყველა ღია კონსოლზე მყისიერად ნაცრისფერდება, აღდგენისას კი ისევ მყისიერად აქტიურდება — განახლების ღილაკის დაჭერის გარეშე.
  • ერთი ადგილი, სადაც ყველაფერი ჩანს. ქაღალდი, ბეჭდვები, კამპანიები, გაფრთხილებები და გადახდის სესიები ერთ მართვის პანელს იზიარებს და არა სხვადასხვა დროს განახლებულ ოთხ ბრაუზერის ჩანართს.
  • ადგილზე არაფრის შეცვლა არ დასჭირდა. მოწყობილობების API და თავდაპირველი სერვერზე რენდერებული გვერდები მთელი გადაწერის განმავლობაში მუშაობდა — ახალი კონსოლი დამატება იყო და არა მიგრაცია.
  • API-ის ქცევას საკუთარი 73 ტესტი აფიქსირებს: ფილტრების სემანტიკა, პაგინაციის კონვერტი, ვალიდაციის ზუსტი შეტყობინებები, რომლებსაც ფორმები ველებთან აჩვენებს, და ვის რისი გამოძახების უფლება აქვს.
  • სტატუსების პალიტრა ფერთა შემმოწმებელი ინსტრუმენტით არის გადამოწმებული და არა თვალით შერჩეული — ის პანელის ზედაპირზე WCAG AA-ს აკმაყოფილებს და დეიტერანოპიის დროსაც ფერთა მკაფიო გარჩევადობას ინარჩუნებს, ხოლო ყოველ სტატუსს ხატულა და ტექსტური წარწერა ახლავს, ამიტომ ფერი არასოდეს არის ერთადერთი სიგნალი.
  • მთელი სტეკი ერთი ბრძანებით ეშვება — სწორედ ამიტომაა შესაძლებელი მისი ინტერაქტიული დემოს არსებობა.

რა გამოვიყენეთ

  • ბექენდი: Python 3.12, Django 5.0, Django REST Framework 3.15, drf-spectacular 0.27, Channels 4.1 + channels-redis 4.2, Daphne 4.1, gunicorn 23, Pillow 10, django-crispy-forms 2.3
  • გადახდის სერვისი (ცალკე): FastAPI 0.115, uvicorn 0.30, SQLAlchemy 2.0, httpx 0.27, cryptography 43 (AES-256-CBC + RSA გასაღების შეფუთვა), KEEPZ ინტეგრატორის API, Python 3.11
  • მონაცემები და საცავი: PostgreSQL 15, SQLite (ნაგულისხმევი ძრავა), Redis 7, psycopg2, django-storages 1.14 + boto3 (DigitalOcean Spaces)
  • კონსოლი: React 18.3, TypeScript 5.7 (strict), Vite 6, Tailwind CSS 4 (კონფიგურაცია CSS-ში, @theme), TanStack Query 5.62, React Router 6.28, i18next 24, lucide-react
  • ინფრასტრუქტურა: Docker + Compose, nginx 1.27-alpine, კონსოლის სამეტაპიანი იმიჯი, GitHub Actions CI

დემო

დემოში რეალური კონსოლის ფრონტენდი მუშაობს: იგივე React კომპონენტები, იგივე TanStack Query ქეში, იგივე სტილების ფაილი, რომელიც Django-სთან ერთად მიეწოდება. ის მხოლოდ Django-ს არ უშვებს. ქვედა დონეზე fetch ფენა ბრაუზერშივე მომუშავე სიმულირებული API-ით არის ჩანაცვლებული, რომელიც საწყის მონაცემებს გასცემს და თქვენს ცვლილებებს მეხსიერებაში არსებულ საცავში წერს — არც სერვერი, არც ბაზა, არც რაიმე სახის ბექენდი. მესამე მხარისკენ არცერთი მოთხოვნა არ იგზავნება — შრიფტებიც ბანდლშია, დანარჩენ ყველაფერთან ერთად. ყოველი ეკრანი ინტერაქტიულია და ყოველი ცვლილება სესიის ბოლომდე რჩება; გვერდის გადატვირთვით საწყის, ხელუხლებელ მონაცემებს დაიბრუნებთ. ქსელის ტელემეტრია სოკეტით კი არ მოდის, არამედ ტაიმერით არის სიმულირებული, ამიტომ მოწყობილობები მართლა ითიშება და ბეჭდვები მართლა ჩნდება თქვენს თვალწინ.