メインコンテンツまでスキップ

システムアーキテクチャ

このページでは、FeedbackPulse SaaS の内部構造を詳しく解説します。アーキテクチャを理解することで、問題のデバッグ、プラットフォームの拡張、適切なデプロイ判断がしやすくなります。


テックスタック

レイヤー技術用途
バックエンドLaravel 12(PHP 8.4+)アプリケーションフレームワーク、ルーティング、ORM、キュー
データベースMySQL 8.0+ / MariaDB 10.6+永続的データストレージ
フロントエンドBlade + Alpine.js + Tailwind CSSリアクティブコンポーネントを持つサーバーレンダリング UI
決済Stripe PHP SDK + PayPal REST APIサブスクリプション請求
AIOpenAI GPT APIセンチメント分析、自動タグ付け、返信提案
認証Laravel SocialiteGoogle & GitHub OAuth
メールLaravel Mail(SMTP)トランザクションメール、ダイジェスト、レポート
リアルタイムServer-Sent Events(SSE)ライブ投稿ストリーミング
タスクスケジューリングLaravel Scheduler(cron)ダイジェスト、トライアル期限、データ保持

マルチテナンシーモデル

FeedbackPulse はシングルデータベース・共有スキーマのマルチテナンシーモデルを採用しています。つまり:

  • 1 つのデータベースがすべてのテナントを扱います
  • テナントスコープのすべてのテーブルには tenant_id 列があります
  • グローバルスコープTenantScope)がすべてのクエリを現在のテナントに自動的にフィルタリングします
  • トレイトBelongsToTenant)が作成時に tenant_id を自動入力し、スコープを適用します

テナント解決の仕組み

リクエストが届くと、ResolveTenant ミドルウェアが以下の優先順位で現在のテナントを特定します:

  1. サブドメインacme.yourdomain.com → サブドメイン「acme」のテナントを検索
  2. カスタムドメインfeedback.acmecorp.comtenant_domains テーブルで検証済みドメインを検索
  3. 認証済みユーザー — ログイン中のユーザーの tenant_id にフォールバック

パブリックページ(/wall/acme-corp など)の場合、テナントはコントローラー内で URL スラッグから直接解決されます(ミドルウェアをバイパス)。

データ分離

+------------------------------------------+
| データベース |
| |
| products (tenant_id = 1) -> Acme データ |
| products (tenant_id = 2) -> TechCorp |
| products (tenant_id = 3) -> E-Commerce |
| |
| TenantScope により Acme は |
| tenant_id = 1 の行のみ参照可能 |
+------------------------------------------+

セキュリティ: パブリック向けコントローラーは withoutGlobalScopes() を使用してテナントスコープをバイパスし、手動でテナント ID によるフィルタリングを行います。これは意図的な設計です — パブリックページは認証済みセッションなしにデータを表示する必要があるためです。


ディレクトリ構造

feedbackpulse-saas/
+-- app/
| +-- Console/Commands/ # 7 つの Artisan コマンド(ダイジェスト、データ保持、アラート)
| +-- Http/
| | +-- Controllers/
| | | +-- Admin/ # スーパー管理者パネルコントローラー
| | | +-- Auth/ # ログイン、登録、2FA、OAuth、なりすまし
| | | +-- Customer/ # カスタマーポータル
| | | +-- Public/ # パブリックページ(ウォール、フォーム、ロードマップ、チェンジログ、ハブ)
| | | +-- Tenant/ # テナントダッシュボードコントローラー
| | | +-- Webhooks/ # Stripe & PayPal Webhook ハンドラー
| | +-- Middleware/ # 15 のカスタムミドルウェア
| +-- Mail/ # 7 つのメール送信クラス
| +-- Models/ # 28 の Eloquent モデル
| +-- Scopes/ # TenantScope(グローバルクエリスコープ)
| +-- Services/ # ビジネスロジック(AI、決済、Webhook など)
| +-- Traits/ # BelongsToTenant トレイト
| +-- Providers/ # サービスプロバイダー
+-- config/ # 11 の Laravel 設定ファイル
+-- database/
| +-- migrations/ # 35 以上のマイグレーションファイル
| +-- seeders/ # デモデータシーダー
+-- public/ # ウェブルート(index.php、アセット、ストレージシンリンク)
+-- resources/views/ # 86 の Blade テンプレート
| +-- admin/ # スーパー管理者ビュー
| +-- auth/ # 認証ビュー(ログイン、登録、2FA)
| +-- tenant/ # テナントダッシュボードビュー
| +-- public/ # パブリックページビュー
| +-- emails/ # メールテンプレート
| +-- layouts/ # レイアウトテンプレート(管理者、テナント、ゲスト、インストール)
| +-- partials/ # 共有コンポーネント(ナビ、メタ、コマンドパレット)
| +-- install/ # Web インストーラービュー
| +-- legal/ # プライバシー、利用規約、Cookie ページ
| +-- errors/ # エラーページ(403、404、419、429、500)
| +-- landing/ # ランディングページパーツ
+-- routes/
| +-- web.php # 416 行のウェブルート
| +-- api.php # API v2 ルート
+-- storage/ # アップロード、キャッシュ、セッション、ログ
+-- bootstrap/ # フレームワークブートストラップファイル

ユーザーロール

FeedbackPulse には users.role 列に格納された4 つのユーザーロールがあります:

ロールアクセスログイン URL
superadminプラットフォーム全体の制御(/admin/*/login
tenant_adminテナントの完全な制御(/dashboard/settings/*/login
tenant_staffテナントへの制限アクセス(請求・削除不可)/login
customerカスタマーポータルのみ(/customer/dashboard/customer/login

ロール階層

superadmin
+-- 任意の tenant_admin になりすまし可能
+-- tenant_admin
+-- tenant_staff を招待可能
+-- 請求、設定、チームを管理可能
+-- tenant_staff
+-- 投稿、キャンペーンを管理可能
+-- customer
+-- 自分のフィードバックを閲覧可能

リクエストライフサイクル

FeedbackPulse にリクエストが届くと以下の処理が行われます:

ブラウザリクエスト
|
v
public/index.php
|
v
Laravel カーネル(ミドルウェアスタック)
|
+-- EnsureInstalled -> 未セットアップの場合 /install にリダイレクト
+-- SecurityHeaders -> HSTS、CSP、X-Frame-Options を追加
+-- VerifyCsrfToken -> CSRF トークンを確認(Webhook を除く)
+-- ResolveTenant -> 現在のテナントを特定
+-- Authenticate -> ユーザーのログイン状態を確認
+-- EnsureTenantAccess -> ユーザーがテナントに属するか確認
+-- EnsureTwoFactorVerified -> 2FA が有効な場合に確認
+-- CheckPlanLimit -> プランの機能制限を適用
|
v
コントローラー(リクエストを処理)
|
v
Blade ビュー(HTML をレンダリング)
|
v
レスポンス -> ブラウザ

データベースアーキテクチャ

コアテーブル

テーブル対象用途
tenantsプラットフォームマルチテナントアカウント
usersプラットフォームすべてのユーザーアカウント(全ロール)
plansプラットフォームサブスクリプションプラン
platform_settingsプラットフォームグローバルキーバリュー設定
productsテナントフィードバック対象の製品
feedback_campaignsテナントフィードバックフォームの設定
feedback_submissionsテナント個別のフィードバックエントリ
feedback_tagsテナントタグ(投稿との多対多)
roadmap_itemsテナントロードマップのカンバンアイテム
roadmap_votesテナントロードマップアイテムへの匿名投票
feature_requestsテナントコミュニティからの機能提案
changelog_entriesテナント製品のリリースノート
team_membersテナントチームメンバーレコード
team_invitationsテナント保留中の招待
api_keysテナントAPI アクセスキー
audit_logsプラットフォームアクション監査ログ
notificationsプラットフォームアプリ内通知
payment_eventsプラットフォームStripe/PayPal Webhook イベント
webhook_logsテナント送信 Webhook の配信ログ
data_deletion_requestsテナントGDPR 削除リクエスト
landing_pagesプラットフォームランディングページビルダーデータ
tenant_domainsテナントカスタムドメインマッピング
referral_codesテナント紹介コード
referral_conversionsプラットフォーム紹介コンバージョントラッキング
cron_logsプラットフォームスケジュールタスクの実行ログ

主要インデックス

テナントスコープのすべてのテーブルは、最適なクエリパフォーマンスのために (tenant_id, created_at) でインデックスが付けられています。feedback_submissions テーブルには statusproduct_idcampaign_idsentiment_label に追加インデックスがあります。


セキュリティアーキテクチャ

レイヤー保護
通信HTTPS 強制、HSTS ヘッダー
認証Bcrypt(12 ラウンド)、オプションの 2FA(TOTP)
認可ロールベースミドルウェア + ポリシークラス
CSRFすべてのフォームに Laravel CSRF トークン
XSSBlade の自動エスケープ({{ }}
SQL インジェクションEloquent のパラメータ化クエリ
レート制限ルートごとのスロットリング(5〜120 リクエスト/分)
API セキュリティSHA256 ハッシュ API キー、テナントごとのレート制限
Webhook セキュリティHMAC シグネチャ検証(Stripe)、SSRF 保護
データ機密設定の保存時暗号化
セッション暗号化、HTTP-only、セキュア Cookie
ヘッダーCSP、X-Frame-Options、X-Content-Type-Options

次のステップ