GAミント至上主義

Web Monomaniacal Developer.

Google Apps ScriptとNuxtJSで簡易来客受付システムをつくったら実質サーバーコスト無料だった

シニアジョブのオフィスの引越にあたり、セキュリティの面からも来客の記録システムが必要になりました。

最初すでにいろんな会社が作ってると思ったので、それ使えばいいじゃんと思いましたが、仕様を聞くとたしかに超簡易的なものなのでまあいいかということで作りました。
とりあえず一通り動くまで2,3日、あとCIとかの環境まわり、デザイナーさんの工数とか入れたら1週間ぐらい?

できたもの

iPadでこんな感じに表示されていて
f:id:uyamazak:20210309171400p:plain

名前とかいれて呼び出しすると「ピンポーン」という音がして
\ピンポーン/
f:id:uyamazak:20210309171503p:plain

スプレッドシートに記録されて
f:id:uyamazak:20210309171555p:plain

Chatworkにも送信されます
f:id:uyamazak:20210309171633p:plain

システム的には

  1. Google App Engineで公開されたNuxtJSアプリ(静的ファイル配信のみ)
  2. GASウェブアプリ(REST API的なもの)で来客リクエスト受け付け + iPadでピンポーン鳴らす
  3. GASでスプレッドシートにログ書き込み & Chatworkにメッセージ送信
  4. 備え付けたiPadでこれを開く

だけのシンプルな構成です。

iPadの選定

まずこれから頼まれた。
生まれてからMac製品買ったこと無い人間なので、いろいろ調べてこんな条件で中古で選んで買ってもらいました。
3万ちょっとだった気がする。

  • 新しめ(2019)
  • 大きめ(10.2インチ)
  • WiFiで厚さとかストレージ気にしない

Raspbery Pi + タッチディスプレイも考えたけどめんどいよね。

フロント側選定

他でも使ってるしNuxtJSでいいよね。UIも大してないので定番のVuetify
いざとなったらサーバー側の処理もできるし(結局GASで済んだのでいらなかった)

インフラ選定

NetlifyとかVercelとか便利だけど、スプレッドシートへの書き込みなので、Google内が良さそう。
常時起動の必要はなく、必要なときだけ起動するGoogle App Engine(スタンダード環境)を選びました。
NuxtJSで普通に作ってローカルで動かしたあとapp.yaml作るだけでデプロイ出来ました。

休日開けとかの起動時間がちょっとネックだけど10秒はいかない感じなので今の所常時起動とかはまあいいかなと。

おそらく無料枠に収まるので実質無料では?

サーバー側選定

この記事で詳しく書いたけど結局サーバーサイド的な処理はGASのウェブアプリだけで完結していましました。
もともとGoogle Workplaceはつかってるので実質無料では?
uyamazak.hatenablog.com

CI、デプロイ

これもGitHubのmainブランチへのプッシュをフックにGCPのCloud Buildを設定しました。
権限周りでちょっとハマったけど、無料枠以上デプロイする気がしないので実質無料では?
uyamazak.hatenablog.com

小技集

ピンポーン鳴らす

もしネットが切れてるときにお客さんが操作するとただエラー画面が表示されるだけで、Chatworkにもスプレッドシートにも飛ばないので人をイラだたせる板になってしまいます。
そこでエラー時にも音出せば原始的なチャイムとして機能する?と思ってつけました。
音量最大にしてオフィスの中聞こえるかはまだ新オフィス行ったこと無いのでわかりません。

音はmp3などでネットで無料でライセンス的にも問題なさそうなのを拾ってきます。
ブラウザで音鳴らすの随分久しぶりな気がしましたが、この関数だけで動きました。
リモートなので実機テストはデザイナーの方に頼みましたが問題なかったそう。

ping-pong.ts

let audio: HTMLAudioElement

export const pingPong = () => {
  if (!audio) {
    audio = new Audio('pingpong.mp3')
  }
  audio.pause()
  audio.currentTime = 0
  audio.play()
  console.log('pinpong')
}

エラーページ出たらしばらくしたらトップにもどる

ネットワーク等なにかの問題でエラー画面がでてそのままだったらうざいので、自動でトップに戻るようにしました。

せっかくなので新しいcomposition-apiプラグイン的に作りました
手で戻った場合に備えてonUnmountedでキャンセルするのも忘れない。

plugins/setup-auto-back-top

import { onMounted, onUnmounted, useRouter } from '@nuxtjs/composition-api'

export const setupAutoBackToTop = (timeout: number) => {
  return () => {
    const router = useRouter()
    let timeoutId: number
    onMounted(() => {
      timeoutId = window.setTimeout(() => {
        router.push('/')
      }, timeout)
    })
    onUnmounted(() => {
      window.clearTimeout(timeoutId)
    })
  }
}

これをエラーページのテンプレートでsetupに時間(ミリ秒)を付けて入れるだけ。
15秒でトップに自動で戻ります。

error.vue

<script lang="ts">
import { defineComponent } from '@nuxtjs/composition-api'
import { setupAutoBackToTop } from '@/plugins/setup-auto-back-top'

export default defineComponent({
  setup: setupAutoBackToTop(15000),
})
</script>

PWAでiPadにインストール&アドレスバー無しを実現する

そのままだとアドレスバーとか出ててダサいのでアプリっぽく動くようにします。
といってもNuxtJSのモジュール入れて、アイコンとか設定するだけ。

pwa.nuxtjs.org

これでiPadにインストールすれば、アプリっぽくなります。
iPad手元にないので画像はない。

IP制限でオフィスからだけ見れるようにする

App Engineということでお気づきの方はいるかもしれませんが、グローバルに公開されちゃいます。
知らない国の知らない人に来客通知送られても困るので制限します。

メニューのファイアウォールで、デフォルト拒否にして、許可するIP追加するだけなので簡単。
ファイアウォールによるアクセスの制御  |  Python 2 の App Engine スタンダード環境  |  Google Cloud

ベーシック認証とかも考えたけど、認証切れたりした画面をお客さんに見せたくない&新オフィスは固定IPがあるのでIP制限にしました。

そういえばApp Engineの選定理由にこれもありました。

あともし同じIPを使われてURLを知っていて悪意がある人がいても、できるのはスプレッドシートへのログ書き込みとChatwork通知だけで、個人情報漏洩とかの心配はなかったものあり。
なにかあってもすぐGASのウェブアプリを止めればOK。


たぶん実運用が始まってからいろいろと問題が出ると思いますが、ご来社の際は触ってみてください(画面消毒用のアイテム用意しないと)