3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【GAS】スマホ対応&排他制御完備!高校生向け「受験校調査システム」をフルスクラッチ開発した話

Last updated at Posted at 2025-12-04

はじめに

高校の進路指導において、生徒の受験校や合否結果の集約は非常に煩雑な業務です。
Googleフォームでは「入力済みの内容を後から修正する」「大学マスタから検索する」といった要件を満たすのが難しく、かといって専用のSaaSを導入するにはコストがかかります。

そこで、Google Apps Script (GAS) + スプレッドシート をバックエンドに据え、完全レスポンシブ対応のWebアプリを開発しました。
GAS特有の「同時書き込み問題」や「キャッシュ容量制限」を乗り越え、実運用に耐えうる堅牢なシステムを構築できたので、その技術的な工夫を共有します。

作ったもの

受験校調査システム
生徒が自身のスマホから受験予定校を登録し、教員がリアルタイムで管理できるシステムです。
以下のURLでテンプレートを公開しています。
👉【テンプレート】受験校調査Webアプリ
MITライセンスで配布します。著作権 (c) 2025 Shigeru Suzuki
ソフトウェアは「現状のまま」提供され、明示的または黙示的な保証は一切ありません。
ご質問や改善提案があれば、この記事のコメントでお知らせください。


  • 生徒機能: 大学検索、受験登録、合否報告、調査書発行願のPDF自動生成
    生徒2.png

  • 教員機能: 全生徒データの閲覧、代理入力、CSVインポート
    教員.png

  • 技術スタック: Vanilla JS / CSS Grid / Google Apps Script / Google Spreadsheet

こだわった技術ポイント 4選

1. 徹底した「排他制御」と「なりすまし防止」

GASでWebアプリを作る際、最も怖いのが 「生徒Aと生徒Bが同時に保存ボタンを押してデータが消える(レースコンディション)」「他人のデータを書き換える(なりすまし)」 です。

排他制御 (LockService)

LockService を使用し、書き込み処理中は他のアクセスを待機させることでデータの整合性を保ちました。

function saveExamDataList(strJuken, mailAddr) {
  const lock = LockService.getDocumentLock();
  try {
    // 30秒間ロックを取得(取れなければエラー)
    lock.waitLock(30000); 
    
    // ... スプレッドシートの読み書き処理 ...

  } catch (e) {
    throw new Error('他ユーザーが処理中です。時間をおいて再試行してください。');
  } finally {
    lock.releaseLock(); // 必ずロックを解放
  }
}

2. GASの「100KBキャッシュ制限」を突破するチャンク分割

大学マスタデータ(Benesseコード等)はサイズが大きく、そのまま CacheService に入れると 「100KB制限」 に引っかかります。 そこで、データを100KBごとのチャンク(塊)に分割して保存し、読み込み時に結合するロジックを実装しました。

// キャッシュ保存(分割対応)
function putCache(key, value, expirationInSeconds) {
  const cache = CacheService.getScriptCache();
  const chunkSize = 100000; // 安全マージンをとって100KB
  
  if (value.length <= chunkSize) {
    cache.put(key, value, expirationInSeconds);
    return;
  }

  // 分割保存
  let chunkCount = 0;
  for (let i = 0; i < value.length; i += chunkSize) {
    const chunk = value.substring(i, i + chunkSize);
    cache.put(`${key}_${chunkCount}`, chunk, expirationInSeconds);
    chunkCount++;
  }
  // 分割数を記録
  cache.put(key, `CHUNKED_${chunkCount}`, expirationInSeconds);
}

3. CSS Gridによる「完全レスポンシブ」UI

生徒は99%スマホで操作します。PC用のテーブル表示をそのまま縮小するのではなく、CSS Grid と Media Query を駆使して、スマホでは「カード型」、PCでは「テーブル型」にレイアウトが動的に変更されます。

/* PC表示: 通常のテーブル */
.input-table tr {
  display: table-row;
}

/* スマホ表示: カード型 */
@media screen and (max-width: 700px) {
  .input-table tr {
    display: flex;
    flex-direction: column;
    border-bottom: 1px solid #ccc;
    margin-bottom: 10px;
  }
  
  /* data-label属性を使って見出しを表示 */
  .input-table td::before {
    content: attr(data-label);
    font-weight: bold;
    display: block;
  }
}

4. サーバー負荷を下げる「デバウンス検索」

大学検索はインクリメンタルサーチですが、一文字打つたびに google.script.run を呼ぶとサーバー(GAS)のクォータを消費し、動作も重くなります。 フロントエンド側で setTimeout を使ったデバウンス処理(入力が止まってから検索)を実装しました。

let debounceTimer;

function searchKeyword() {
  clearTimeout(debounceTimer);
  // 最後の入力から一定時間待ってから実行
  debounceTimer = setTimeout(() => {
    // ... google.script.run.getUniversityDataList() ...
  }, 800); 
}

5. 運用面での工夫

  • キャッシュの自動更新: スプレッドシートの onChange トリガーを使い、管理者がマスタデータを修正したら自動でキャッシュがクリアされるようにしました
  • 調査書発行願: 入力データを元にスプレッドシート上で帳票レイアウトを作成し、PDF化してメール送信する機能を実装しました。一時ファイルは finally ブロックで確実にゴミ箱へ移動させています

6. まとめ

「GASでWebアプリは遅い・脆い」と思われがちですが、以下のポイントを押さえれば十分に実用的なシステムが作れます。

  • LockService で排他制御をする
  • CacheService を限界まで活用する(分割保存)
  • クライアント側 でできる処理(バリデーション、整形)はクライアントに任せる
  • レスポンシブ はCSSで解決する
  • コストゼロでここまで作れるGASの可能性を改めて感じました。 どなたかの開発の参考になれば幸いです!

Tags: #GAS #GoogleAppsScript #JavaScript #GoogleSpreadsheet #個人開発 #EdTech

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?