QGate(キューゲート) は、飲食店(はま寿司・ファミレス型発券)やイベント会場、オフィス受付に対応した、暗号署名付きQRコード × リアルタイム順番待ち・入退場管理プラットフォームです。
WebSocket によるミリ秒単位の双方向同期、厳密な入退場ライフサイクル制御、2つの運用モード切り替え、および完全な Docker コンテナ化(自動HTTPS・スマホカメラ対応)を備えています。
- ⚡ 発券機モード (登録不要):
- 飲食店のタッチパネルのように、人数と席(テーブル/カウンター)を選ぶだけで即座に整理券QRを発行。
- リロードしても端末(LocalStorage)にチケット情報が保持されます。
- 🔒 会員予約モード (登録必須):
- メールアドレスとパスワードによる会員登録・ログイン。
- ログイン後はいつでもマイページでQRコードを確認可能。
- Gmail連携によるパスワード再設定(6桁コード送信)およびアカウント削除に対応。
QRコードの単なる読み取りではなく、不正防止のための厳密な状態遷移を強制します。
[発券 / 予約] ───> ISSUED (待機中 / あなたは今 4組目)
│
▼ (スタッフが手動または「自動呼出」で席番号を指定)
CALLED (お呼び出し中! 「テーブル席 12」へ案内)
│
▼ (受付でQRコードを1回目スキャン)
CHECKED_IN (入場・ご案内完了 / ユーザー画面が動的更新)
│
▼ (ユーザー画面の「退場する」ボタン押下 または 2時間経過)
CHECKED_OUT (退場完了 / DBからレコードを即時自動削除して容量解放)
- 会場・店舗の大画面ディスプレイ用モニター。
- 「お呼び出し中(NOW SERVING)」 と 「準備中・順番待ち(PREPARING)」 の2カラム構成。
- スタッフが呼び出しを行うと、リロード不要で即座に番号が反映され、チャイム音が鳴ります。
- 暗号署名付きQRコード: QRコード内部にHMAC-SHA256署名と有効期限を埋め込み、偽造・改ざん・使い回しを完全に防止。
- Middleware ルート保護: スタッフ認証(PINコード)がない場合、
/dashboardや/scannerへのアクセスをサーバー側で即座に遮断。
- 内蔵SSL証明書自動生成エンジンにより、追加設定なしで
https://起動。 - iOS Safari / Android Chrome の背面カメラ(アウトカメラ)を最優先起動し、オートフォーカスで瞬時にスキャン。
| レイヤー | 技術 |
|---|---|
| フロントエンド | Next.js 14 (App Router), React 18, TypeScript, Tailwind CSS |
| バックエンド | Node.js (HTTPS & WebSocket Server), Next.js Route Handlers |
| データベース | PostgreSQL 16, Prisma ORM 5.22 |
| キャッシュ / PubSub | Redis 7, ioredis |
| 通信プロトコル | WSS (WebSocket), HTTPS |
| メール送信 | Nodemailer (Gmail SMTP) |
| QRコード | html5-qrcode (Native BarcodeDetector GPU加速), qrcode.react |
| コンテナ | Docker, Docker Compose |
qgate/
├── app/
│ ├── api/v1/
│ │ ├── analytics/ # 混雑分析 & 待ち時間予測 API
│ │ ├── auth/ # スタッフ認証 & 顧客認証 (登録/ログイン/パスワードリセット)
│ │ ├── checkin/ # 入退場スキャン照合 & DB自動クリーンアップ API
│ │ ├── event/mode/ # 運用モード切替 API
│ │ ├── queue/ # 呼出 & キュー一覧取得 API
│ │ └── tickets/ # 発券・追跡・退場準備 API
│ ├── calling/ # 会場呼出ライブモニター画面
│ ├── dashboard/ # 管理者ダッシュボード & 呼出コントロール
│ ├── login/ # スタッフ専用ログイン画面
│ ├── scanner/ # 高性能QRスキャナー端末画面
│ ├── globals.css # 統一モダンミニマルスタイル
│ ├── layout.tsx # ナビゲーション & セッション制御
│ └── page.tsx # ユーザー向け発券・予約ポータル
├── lib/
│ ├── crypto-qr.ts # HMAC-SHA256 署名付きQR生成 & 検証
│ ├── mailer.ts # Gmail SMTP メール送信モジュール
│ ├── prisma.ts # Prisma クライアント
│ └── redis.ts # Redis キャッシュ & WebSocket 直結モジュール
├── prisma/
│ ├── schema.prisma # データベーススキーマ定義
│ └── seed.ts # 初期イベント・組織データ投入
├── public/sounds/ # チャイム・ビープ音声
├── docker-compose.yml # コンテナオーケストレーション
├── Dockerfile # アプリケーションコンテナ定義
├── entrypoint.sh # DB自動マイグレーション & 起動スクリプト
├── server.js # HTTPS & WebSocket 統合サーバー
└── package.json
プロジェクト直下に .env を作成します。
DATABASE_URL="postgresql://postgres:postgrespassword@postgres:5432/qgate?schema=public"
REDIS_URL="redis://redis:6379"
# Gmail 送信設定(パスワード再設定用)
GMAIL_USER="your-gmail-address@gmail.com"
GMAIL_APP_PASSWORD="16桁のGoogleアプリパスワード"docker compose up --build初回起動時に以下の処理が自動実行されます:
- PostgreSQL & Redis のヘルスチェック待機
- データベースの自動マイグレーション (
prisma db push) - 初期シードデータの投入 (
prisma/seed.ts) - 内蔵自己署名SSL証明書の動的生成
- HTTPS / WSS サーバーの起動
| 画面名 | URL | 用途 |
|---|---|---|
| ユーザーポータル | https://localhost:3000 |
整理券の発券、会員登録/ログイン、QR表示、退場ボタン |
| 会場呼出モニター | https://localhost:3000/calling |
大画面用。お呼び出し番号と順番待ちのリアルタイム表示 |
| スタッフ管理画面 | https://localhost:3000/dashboard |
モード切替、1クリック自動呼出、案内席の指定 |
| 受付スキャナー | https://localhost:3000/scanner |
スタッフ用端末。入場(1回目)と退場(2回目)の高速スキャン |
スマホ実機からアクセスする場合 (同一 Wi-Fi):
https://[PCのローカルIP]:3000(例:https://192.168.0.4:3000)
※ 初回アクセス時に「詳細設定」→「安全でないページに進む」を許可してください。
- スタッフ / 管理者 PIN:
1111または9999 - パスワード再設定テスト用共通コード:
123456
[Step 1: ユーザー側]
1. https://localhost:3000 で人数を選んで「整理券を発券する」を押す。
2. 画面に「A-001 / 順番待ち中」とQRコードが表示される。
[Step 2: 会場モニター側]
3. 別タブで https://localhost:3000/calling を開く。
4. 「準備中」に「A-001」が並んでいることを確認。
[Step 3: スタッフ呼出]
5. https://localhost:3000/dashboard で「テーブル 5」を指定し、「先頭のお客様を自動呼出」をクリック。
6. モニターからチャイムが鳴り「A-001 👉 テーブル 5」とお呼び出し中に移動。
7. ユーザースマホの画面がリアルタイムに「🔔 お呼び出し中!」に自動変化。
[Step 4: 入場スキャン]
8. https://localhost:3000/scanner でユーザーのQRコードをスキャン。
9. スキャナーに「✓ 入場完了」、ユーザー画面が「🍽️ ご案内完了 (テーブル 5)」に変化。
[Step 5: 退場スキャン & データ削除]
10. ユーザー画面の「お会計・退場する」ボタンを押す。
11. 再度スキャナーでQRを読み取ると「✓ 退場完了」となり、ユーザー画面が「ご利用ありがとうございました」に変化。
12. 5秒後にユーザー画面が自動で初期トップへリダイレクトされ、DBレコードが完全削除される。
MIT License. 商用利用・改変・再配布が可能です。