降りつぶし.netの構築
降りつぶし.netは、「日本の鉄道駅」にいつ乗下車したのか、を管理するソリューションです。第2回は、その基盤となる、全国の鉄道駅データベースの構築について説明しました。第3回は、その上に構築されているWebアプリケーションを説明します。
Webアプリ構築にCMSを利用
Webアプリとしての降りつぶし.netに最低限求められる仕様は、『複数のユーザが自らの乗下車記録を管理できる』ことです。そのためには、各ユーザを識別し、ユーザのみが自分の記録を入出力できるようにしなければなりません。また、駅データベースやスマホアプリの更新情報をアナウンスしたり、ユーザからデータベースの誤りについてのご指摘を承ったりするUIがあれば、ベターです。
そこで、何らかのCMSを利用し、記録の入出力や同期機能などは独自実装して、アナウンスにはブログ機能を、ユーザからの承りは掲示板機能を使うことにしました。
CMSとして「Geeklog」を採用
GeeklogはオープンソースCMSです。動作がまずまず軽量であり、細かな権限設定ができ、デザインや出力内容のカスタマイズも容易です。日本語化、および日本語での情報交換も活発です(Geeklog Japanese)。また、OAuth認証に対応しており、Twitterなどのユーザであれば、フォーム入力なしで、簡単に新規登録ができます。
筆者の会社では、このGeeklogを用いたカスタマイズを行った経験がいくつかあります(導入例:NPO法人寿クリーンセンター)。また第1回で触れたとおり、降りつぶしという趣味はニッチであり、ユーザ数はたかだか数千人と踏んでいましたので、パフォーマンスはさほどシビアではありません。これらを考慮し、とりわけOAuth認証による新規登録の容易さに惹かれ、Geeklogを採用することにしました。
Geeklogのカスタマイズ
Geeklogにはさまざまなカスタマイズ方法が提供されています。プラグインによる開発はもちろん、ヘッダやフッタの表示のみの流用、認証機能のみの流用など、PHPの開発力さえあれば、かなり複雑なWebアプリケーションでも平易に構築することができます。
実際に、記録入力トップページ“http://oritsubushi.net/oritsubushi/index.php”のコードを抜粋してみます。
COM_xxxxという関数はGeeklogの共通処理関数で、自由に呼び出すことができます。一方、ORITSUBUSHI_xxxxという関数は、筆者が実装した関数です。
COM_siteHeader、COM_startBlock、COM_endBlock、COM_siteFooterの各関数は、それぞれ、ユーザの権限に応じた適切なヘッダ・サイドバー・フッタなどを生成しています。そして、ORITSUBUSHI_build関数で、好き勝手なコンテンツを生成しさえすれば、Geeklogの他のページとまったく同じ外観のページができあがる、という寸法です。
一方、これらのCOM_xxxx関数を呼び出さず適切なコンテントタイプと内容を出力することにより、整形されたHTMLではなく、JSONなどを返すことも可能となるわけです。
最初に呼び出しているORITSUBUSHI_checkFirst関数は、最初にインクルードした“oritsubushi.inc.php”内で、以下のとおりに定義されています。
スクリプトの冒頭でGeeklogの“lib-common.php”をインクルードするだけで、自動的に認証が行われます。グローバル変数$_USERには認証済のユーザ情報が連想配列で格納されており、$_USER['uid']はユーザIDです。Geeklogでは、この値はゲストユーザの場合1となり、デフォルトの管理者Adminの場合が2で、一般の登録ユーザは3以上となります。
降りつぶし.netでは、登録ユーザ全員に乗下車記録の入出力を認めているため、権限チェックなしで、ユーザIDが1より大きければ正規ユーザとみなせることになります。ORITSUBUSHI_checkFirst関数内では、ユーザIDが1より大きくない、すなわちゲストユーザの場合、ログインフォームを持つサイトトップへとリダイレクトさせ、乗下車記録ページヘのアクセスを拒むことになります。
これらにより、ORITSUBUSHI_build関数の内容の実装のみに集中できました。CMSの面目躍如です。
降りつぶし.net 乗下車記録ページの構成
降りつぶし.netが独自に実装した、呼び出し可能な各PHPスクリプトは、すべて“oritsubushi”ディレクトリ直下に置かれており、以下のような遷移関係にあります(管理者ログイン時のみ動作するメンテナンススクリプトを除く)。
記録入力トップページ“index.php”
鉄道事業者一覧です。各事業者名のリンクをクリックすると、その事業者のページが開きます。ただし、すべての事業者を表示してしまうと、コンテンツエリアが長くなりすぎてしまうため、事業者種別ごとにまとめて畳んでおき、事業者種別のクリックによって事業者一覧を展開するようにしました。
また、このページに限らず、記録入力の各ページのコンテンツトップには一括アップロード用フォームとボタンがあり、コンテンツボトムには一括ダウンロード用ボタンがあります。
事業者ページ“operator.php”
記録入力トップページで選択された事業者に属する路線の一覧です。パラメータで渡される事業者コードにより、データベースから路線を検索しています。各路線名のリンクをクリックすると、記録入力ページ(路線ページ)が開きます。
記録入力ページ(路線ページ)“line.php”
事業者ページで選択された路線に属する駅一覧と、それらの駅の乗下車記録およびメモが、表形式で表示されます。各駅のリンクをクリックすると、別ウィンドウで日本語版ウィキペディアのページが開き、その駅の詳細情報を読むことができます。
日付入力UIは、第2回で紹介のとおりです。年選択で「不明」を選択すると、JavaScriptにより、月および日は空欄のみとなります。また、年および月を選択するたびに、日の最大値は28~31の間で自動調整されます。記録を入力し、「乗下車記録を更新する」ボタンをクリックすると、登録スクリプトへのhttp POSTで、入力内容が送信されます。
登録スクリプト“register.php”
UIはなく、記録入力ページからのPOSTのみを受け付けます。データベースを更新し、呼び出し元の記録入力ページへとリダイレクトで戻ります。記録入力ページ→登録スクリプトヘPOST→記録入力ページヘリダイレクト、と遷移させることにより、登録後の記録入力ページをWebブラウザからリロードした場合の再POST発生を回避しています。
アップロードスクリプト“upload.php”
CSVファイルのアップロードを処理します。単に、ダウンロードスクリプトが出力するフォーマットのCSVファイルをパースし、データベースに登録し、結果を表示するだけです。
登録スクリプトは、POSTの意図的な捏造がない限りは異常データを受信することがないため、不正なデータに対するエラーメッセージを出力しません。一方こちらは、ユーザが手作業でフォーマットを壊してしまう可能性があるため、エラーメッセージを表示します。
どのページからのアップロードであっても、登録に必要なキーは駅コードのみであるため、動作は共通ですが、元のページへ戻るリンクを生成するため、事業者ページおよび記録入力ページ(路線ページ)からのアップロードの場合は、それぞれのコードをパラメータとして受け取ります。
ダウンロードスクリプト“download.php”
CSVファイルを生成します。UIはなく、text/csv形式のファイル内容をダウンロード用に出力します。
フォーマットは、Shift_JIS、かつ項目内に改行文字を含むことができる、いわゆるExcel形式です。もちろん、Excelなどの表計算ソフトでの読み書きを可能とするためです。第1回でも触れたとおり、この機能は、すでに多くの駅に乗下車を済ませたマニアユーザには必須です。
どのページからも呼び出されますが、事業者コードや路線コードが指定されていた場合、それぞれに対応したSQL文を生成してクエリを実行し、その結果をCSVフォーマットに変換しています。
ブログパーツ生成ページ“parts.php”
後述しますが、降りつぶし.netでは、乗下車記録のうちの、事業者種別別の統計情報を、JSONPで提供しています。その呼び出しURLには、ユーザを一意に識別するランダムな文字列が含まれます。このページでは、そのJSONPによる埋め込みコード、および実際に埋め込んだ場合の表示例を例示します。
ユーザトークン変更ページ“change_token.php”
前述したブログパーツに埋め込まれるランダムな文字列を、何らかの事情で変更する場合に必要なものです。第2回で紹介しなかったデータベーステーブル“user_tokens”は、まさにこのランダムな文字列を管理しています。
同期ページ“sync.php”
スマホアプリ(i降りつぶし・降りつぶしroid)から呼び出され、乗下車データの同期を司る、本ソリューション最大のキモです。詳細は第4回で解説しますが、スマホとサーバの同期ためにのみ存在しているのにもかかわらず、UIがあります。そのため、同期「ページ」としています。
このページは、http GETで呼び出されるとまずログイン状態をチェックし、未ログインの場合は、スマホ画面で適切に表示される、専用のログイン画面のHTMLを出力します。スマホアプリは、これをWeb表示ビューにそのまま渡し、画面表示させています。一方、ログイン済の場合は、登録ユーザ名をプレーンテキストで返します。スマホアプリは、この文字列をログインユーザ名として、スマホ側のUI内で表示します。
また、POSTで呼び出された場合は、ログイン状態であれば、本来の同期機能を実行します。
ブログパーツJSONP“stat.php”
指定されたパラメータに紐付いているユーザの乗下車記録の統計情報を、JSONで返します。JavaScript関数名が指定された場合は、結果のJSONをパラメータとして、その関数を呼び出します。これにより、ブログパーツを実現しています。ブログパーツは非ユーザのWebブラウザ上で表示されるものなので、このスクリプトは認証をせず、user_tokensテーブルを参照し、ユーザ識別トークンからユーザIDを得て、統計情報を表示しています。
また、このスクリプトは、本サイト全体の中で最も頻繁に呼び出される可能性があるものです。その度に統計情報を集計することは、まったく実用的ではありません。
そのため、前述のregister.php、upload.php、およびsync.phpが乗下車記録を更新した際、トランザクション内で統計情報を集計し、データベーステーブル“statistics”に書き込んでいます。stat.phpは、statisticsテーブルに書き込まれたデータをプライマリキーたるユーザIDで検索し、出力しているに過ぎません。
お知らせページ“timeline.php”
単に、降りつぶし.net公式Twitterアカウント(@oritsubushi)の直近ツイートを表示するだけです。これは、スマホアプリの「情報」メニューで表示されます。同期機能を利用しない限り、スマホアプリの利用にはWebアプリへのユーザ登録やログインは不要であり、このページも認証をしていません。
デザイン、仕様制限など
サイトデザインは、絵心のない筆者が考えたラフなものを、鉄道ファンでもあるデザイナー、伊藤壮氏(@souitoh)にブラッシュアップしていただきました。バナーには、いわゆる国鉄フォントを用いています。HTMLやCSSの編集は、Geeklogの管理画面から筆者が行っています。
なお、Geeklogの機能である「各種OAuth認証に自動対応」については、現時点では独自に制限を設けています。これはスマホアプリ側の仕様制限によります。同期機能での認証ではGeeklogの認証画面をそのまま利用しますが、ここから各認証サイトに移動している間、Web表示ビューをそのままスマホに表示し続ける必要があります。が、その認証サイト内認証ページにある別リンクがタップされた場合、Web表示ビューではなく、外部Webブラウザを起動しなければなりません。つまり、各OAuth認証サイトの認証ページの遷移をすべて把握し、それ以外のリンク先は外部Webブラウザに渡す、という個別の実装が、スマホ側に必要となるのです。
これに対応し切れていないため、現時点では筆者が個人的に利用しているTwitterでの認証のみの対応となっています。
次回はいよいよ同期機能
第2回のデータベース解説、そして今回のWebサイト解説で、同期機能の解説の準備ができました。
第4回は、同期機能のみについてお届けします。