Skip to content

アーキテクチャ ​

パッケージ構成 ​

cmd/server/          エントリーポイント(main.go)— 各コンポーネントの組み立てのみ
internal/config/      環境変数からの設定読み込み
internal/discordapi/  Discord REST API クライアント・Ed25519署名検証・型定義
internal/httpserver/  HTTPルーティングとInteractionのディスパッチ(ビジネスロジックは持たない)
internal/keymanager/  鍵管理機能の本体
internal/sqlitedb/    共有SQLiteコネクションのオープン処理

internal/keymanager が実質的なアプリケーション本体で、以下を1パッケージに集約しています。

  • service.go — /key /backdate /backdate-fix コマンドとボタンのハンドラ
  • commands.go — スラッシュコマンド定義・ボタンメッセージの構築
  • sheets.go — Google スプレッドシートへの読み書きロジック
  • alerts.go / alertstore.go — 返却期限超過アラートのスケジューリングと永続化
  • status.go — 常設ステータスメッセージの更新、ログチャンネルへの投稿
  • notify.go — Microsoft Teams Webhook通知
  • envpersist.go — .env ファイルへの安全な書き戻し
  • timeparse.go — HHMM / MMDD 形式の入力パースとJST時刻の扱い

リクエストフロー ​

  1. Discord → POST /(Interaction、Ed25519署名付き)
  2. httpserver が署名を検証
  3. その場で type: 5(deferred response, flags: 64 でephemeral)をDiscordへ即時返却
  4. go s.dispatch(interaction) で別goroutineに処理を渡し、ここから先は非同期
  5. keymanager.Service が Google Sheets から現在の貸出状況を取得
  6. Sheets へ打刻を追記(月別シート+年別ログシート)
  7. 鍵取りであれば返却期限アラートをスケジュール
  8. ログチャンネルへ操作ログを投稿
  9. 常設ステータスメッセージを編集(初回のみ新規作成)
  10. deferred response を最終結果の文言に編集して確定応答

なぜ deferred response + goroutine なのか ​

Discord は Interaction 受信から 3秒以内 に応答しないとタイムアウトになります。しかし実処理(スプレッドシートの読み書き、複数のDiscord API呼び出し)は3秒を超えうるため、handleInteraction はまず type 5(deferred response, ephemeral)を即座に返し、実際の処理は go s.dispatch(interaction) でバックグラウンドに逃がします。ゴルーチン内の dispatch は recover() でパニックを捕捉し、プロセス全体がクラッシュしないようにしています。処理が終わったら EditOriginalInteractionResponse で最終結果に書き換えます。

なぜ「操作ログ」と「deferred response」が両方あるのか ​

deferred response の編集内容は 本人にしか見えない(ephemeral)確認用メッセージです。一方、誰が・いつ鍵を取った/返したかという記録は、全員に見える形でログチャンネルへの投稿と常設ステータスメッセージの更新によって共有されます。この2つは役割が異なるため、常にセットで呼ばれます。

データの持ち場 ​

データ保存先理由
打刻の記録(誰が・いつ・何をしたか)Google スプレッドシート人が閲覧・集計しやすい正の記録として
返却期限アラートの予定SQLite(pending_alerts テーブル)プロセス再起動後も期限超過チェックを再開できるように
常設ステータスメッセージのID.env(STATUS_MESSAGE_ID)再起動後も同じメッセージを編集し続けるように

詳細は スプレッドシートのレイアウト と 返却期限アラート を参照してください。