Webサービス「QRフォトシェア」の技術トピックス
本投稿はWebサービス「QRフォトシェア」の設計・実装に関する技術トピックス(概要)です。
自社でサーバーインフラを構築しなくても従量課金で利用できるクラウドサービスを利用すれば、小規模な事業者でも容易にWebサービス・アプリの構築が出来るようになりました。
「QRフォトシェア」ではGoogle社のクラウド環境を利用して開発・運用を行っています。
類似の要素をもつサービスの開発を計画する際、課題抽出や設計検討でご参考下さい。
はじめに
初対面や軽い顔見知りではお互いにSNSやメールアドレスを交換するのに抵抗感があります。
QRフォトシェアは、一眼デジカメを持ち歩く趣味の弊所代表寺田が地元川越での人付き合いの中で、連絡先を交換するほどではない距離感の相手にも写真の画像ファイルを気軽に送れるよう作ったものです。
送り手と受け手がお互いの情報をもつ必要がなく、送り手は画像に紐づくQRコードをスマホやPCの画面に表示し、受け手はそれをスマホでスキャンすれば画像を受信(URLを介してダウンロード)できる仕組みです。
送り手は自分の履歴を管理するため利用登録して使いますが、受け手は一過性の情報を受けとるだけなので利用登録不要(匿名・不特定でよい)という非対称な関係になっています。
因みに、友人相手でも各々のつながり方に応じて送り分けるのは億劫で「後で送るね」といってそのままになりがちですが、その場にいれば一度の手間で済んで楽です。
詳しくはリリースもご参照ください。
このシステムの概要は以下のとおりです。
- 画像を送る人、受け取る人、の2種類のユーザーがいる
- 送り手も受け手も事前に相手に関する情報を必要としない
- 画像はインターネットに送信されてクラウドサーバーを介して受け手の端末に送られる
- テンポラリなクラウドの保管場所のURLを伝達するためにQRコードが使われる
- 画像がサーバーにある間はそのURLから誰でも何度でもダウンロードできる
- 画像は1時間で自動削除されQRコードも消える(送り手はそれ以前に手動で削除も可)
- 受け手は、送り手のニックネーム、画像へのコメント(送り手がつけていれば)、画像のプレビューと写真のExif情報※をみて必要に応じて「保存する」
(※カメラメーカー機種、レンズ名、絞りやシャッター速度、ISO感度など写真好きのための情報)
クラウドサービスの機能を組み合わせてこれを実現しています。
1.形態
利用登録したユーザーに一時的な画像共有手段を提供するWebアプリケーションです。
2種類のユーザーがいますがプラットフォームとしての機能はありません。
2.仕組み
カスタムドメインからWebアプリとして提供しスマートフォンとPCへレスポンシブに対応します。
Webアプリにすることで送り手は iPhone, Android, PC(Windows/mac)を問わず画像送信でき、受け手もQRコードをスキャンするカメラ機能があれば iPhone, Android を問わず利用できます。
1つのWebアプリに以下2種類の入口を設け、送り手と受け手がそれぞれ異なるコンテキストで呼び分けるようにします。
因みに一眼デジカメには通常BluetoothやWifiを使ってスマートフォンへ撮影した画像を送る機能がありますので、スマートフォンでは撮るのが難しい写真もスマートフォン経由で相手に贈ることができるわけです。
(1)送り手用:アカウントにログインして画像アップロードするページ
画像を選択してアップロードしテンポラリな限定公開ファイル(知っている人だけがアクセスできるURL)を生成します。このURLを直接QRコードにするわけではなく、送信者のニックネームなどの附帯情報とともにデータベースレコードに一旦登録します。このデータベースレコードのIDをQuery文字列に含んだアプリページURLをQRコードにして画面に表示します。
自動削除の期限を待たずに任意で削除できるようにするため、送った画像は削除されるまで履歴にリスト表示します。データベースレコードと画像ファイルはペアで削除されます。
(2)受け手用:アカウントを使わずに画像をダウンロードするページ
スマホカメラのQRリーダーでスキャンすることでブラウザから開くページです。
上記(1)によりQuery文字列に含まれるIDを用いてデータベースレコードを取得し、画像プレビューと附帯情報を画面に表示します。
この画面を表示するたびに重複してダウンロードが発生しないよう明示的に「保存する」ボタンを設けています。また、このボタンでデータベースレコードにある当該画像のダウンロード回数をカウントアップすることにより、送り手は相手が画像を入手できたかどうかを確認できるようにしています。
データベースレコードが既に削除されていたら期限切れで消去された旨の画面を表示します。
3.実現手段
クラウド基盤は Google Firebase を使用しています。
Firebase Hosting でカスタムドメインの静的WebサーバーにWebアプリをデプロイします。
フロントエンドのWebアプリはREACTを用いたGUIです。
画像管理のために Firebase Firestore、画像の一時保存先にGoogle Cloud Storageを使用します。
一定時間で自動削除するためにサーバーレス実行環境 Firebase Functions を使用します。
前記2の仕組みを Firebase で実装すると下図のようになります。
![](https://esslab.co.jp/wp-content/uploads/2024/04/スクリーンショット-2024-04-27-000022-1024x494.jpg)
なお、オリジナルの hostingは firebaseが提供するドメインの qrcheckit.web.app、これにカスタムドメインの qrcheckit.com を割り当てています。
以下に今回の実装でのイシューを挙げます。
(コード例はネットのサンプルや Github copilot のアシストに譲って割愛します。)
4.ユーザー登録の仕組み(送信者のみ)
Firebase Authentication を使用しています。
Winesplityの技術トピックスにも記載の通りです。
ユーザーのRole等の識別情報を埋め込んで保管することができる CustomUserClaims は今回使っていませんが、保持できるQRコードの数や保管日数を増やしたプレミアムアカウントを作る必要が生じたら、そこに情報を入れてユーザーを区別して機能制限することになるでしょう。
5.データの構成
Firebase Firestore と Google Cloud Storage を組み合わせて期限付きストレージを構成します。
Firestore についてもWinesplityの技術トピックスで触れていますのでご参照ください。
QRフォトシェアでは、送信したい画像を Google Cloud Storageの Bucketにアップロードし、そのファイルの在処を Firestoreの Document に画像管理レコードをつくって保持します。このとき Document に送信者のニックネームや画像へのコメント、有効期限の日時を追加し、ダウンロード回数を初期化します。また、このDocumentが作成されたことをトリガーにしてサーバーサイドの function が起動し、対象画像からExif情報(写真の絞りや露出、ISO感度等)を抽出して Documentに追加しています。
このDocumentにアクセスするためのQuery文字列を含んだURLをQRコード化することで、必要な情報一式を送りてから受け手に引き渡しています。
この構成により、画像の受信用Webページは、Query文字列を手掛かりにFirestoreのDocumentを読むことで画像プレビューとともに送信者のニックネームや画像へのコメント、画像のExif情報を同時に表示できます。
また、受信者がWebページのダウンロードボタンを押したら、Google Cloud Storageの画像ファイルをローカルストレージにダウンロードするとともに Document内のダウンロードカウンターをIncrementします。これにより送信者は、QRコードを経由して実際に何人がダウンロードしたかがわかる仕掛けになっています。
6.データの保護
特別なアカウントなしに画像を受けとれる必要があるため、画像の保存先、および対応するDocument ID はいずれもランダムな長い文字列を知っている者だけがアクセスできる限定公開方式で保護します。
併せて、Firestoreの Document のリスティングと削除は、ユーザーアカウントが自分で作成した Documentのみ可能な制限にして、画像を送信したユーザーのみが削除期限前の自分の画像を一覧表示したり、期限前に手動で削除できるようにしています。
1時間で自動消去される仕組みは、クラウドで一定時間おきに functionが走り、期限を過ぎた Documentがあるかどうかを監視しており、該当の Documentがあればそれを削除します。Firestoreは、Document の削除をトリガーにして別の functionを実行できるため、そのハンドラーによってStorage内の紐づく画像ファイルを削除します。これは、ユーザーが手動で Documentを削除したときにも働きます。
これらにより、QRをスキャンした受け手のみが画像を受け取ることができ、かつ一定時間を過ぎたものはシステムから完全に削除されます。画像の送信者は、第三者が直接サーバーを一覧したり送った画像が放置されて残る心配なく画像を送ることができます。(前述の仕組み概要図参照下さい)
7.画像のダウンロード処理(受け手)
受け手が明示的に画像をダウンロードをするための「保存する」ボタンの実装は、よく見るWebページサンプルでは自分のコード内で画像ファイルへのリンクを仮生成して自分でクリックするコーディングになっていますが、REACTアプリ内ではワークしません。そのため、URLから画像データを直接 fetch で読み込んで保存しています。
このときCORSが適切に設定されていないと fetchできません。具体的には、画像本体を置いている Google Cloud Storage に対するアクセス元として2つのドメイン qrcheckit.web.app, qrcheckit.com を許可するよう、cors.json を記述してUploadしておきます。
> gsutil cors set cors.json gs://Storageのバケットアドレス
8.サーバーサイドで実行する処理
上記5と6に記載のサーバーサイド処理は Firebase Functionsで実装しています。
(1)画像がUploadされたときに画像からExif情報を抽出して情報を揃える
(2)定期的に期限切れ管理レコードを検出して削除する(紐づく画像も削除する)
定期的に実行する functions(v1)は以下のようなコードです。
exports.checkExpiredRecord =
functions.region(REGION).pubsub.schedule(INTERVAL).timeZome(TIMEZONE)
.onRun( async (context) => { … } ) ;
これ以外にも実はシステム管理者のためだけの処理が走っています。
(3)毎日 0:00 を過ぎたら日報をシステム管理者へ送信する
所定の時刻をトリガーに function を実行することができます。
画像が大量にアップされるなどのアクシデントが起きていないかをデイリーに監視するために、毎日所定の時刻になると過去24時間以内の利用状況をカウントして、一定以上の利用があればシステムからサービス管理担当のメールアドレスへ「日報」メールが送られるようにしています。
9.Exif情報について
JPEG画像ファイルには附帯情報を記録するヘッダー部分と圧縮された画像データの本体があり、ヘッダー部分には可変長の附帯情報を埋め込める仕掛けがあります。デジタルカメラで撮影して保存されるJPEG画像ファイルには、この仕掛けを利用してExif規格が定める附帯情報(Exif情報タグ)が書かれています。Exifはデジタルカメラで作られた規格ですがスマートフォン含めて「静止画写真」を撮る殆どのデバイスが準拠しています。
附帯情報として書かれているのはカメラのメーカー、モデル、レンズ、撮影日時、絞り値やシャッター速度・ISO感度などの撮影パラメータ等、たくさんありますが、必須タグ以外はどこまで記録しているかはまちまちです。
本アプリでは一般的な写真カメラ趣味で関心を持たれる基本情報のみピックアップしています。
但しこのアプリの実装では ExifJPEGではなくHIFファイルで写真を保存する最近のiPhoneで撮った画像を送られた場合は、撮影情報不明として扱っています。HIFへの対応は機会あれば検討したいと思います。
Exif情報を調べるための node モジュールは exif-parser, get-stream を使用しています。
バケットのファイルをstreamに読み込み exifParser に送って上記のタグを取得しています。
10.PWA/OGPの実装
Webサイトのルートにマニフェストファイルを含む情報一式を配置することで、ホーム画面やデスクトップへアイコンをインストールしてアプリライクに利用できる PWA形式にしています。
また、Webアプリのサムネイルやタイトル、要約などの情報がSNSで表示されるようOGPに対応したコンテンツをサイトルートに配置しています。
(Winesplityの技術トピックスでも触れています)
11. フィードバック機能
利用者からのフィードバック収集手段にはGoogleフォームを使用しています。
Webアプリのページに「アンケート」の項目を設けてフォームへリンクし、投稿された情報はGoogleシートに蓄積します。Googleシートの通知設定をすることで投稿があると管理者側で把握できますし、必要に応じて Google Apps Scriptでデータを処理できます。
利用者が膨大になると対応できませんが、小規模のサービスや立ち上げ当初の運用では十分ワークするので便利です。
以上