Laravel Forge多域名单代码库:Laravel多租户数据隔离方案咨询
Nice one! I’ve built exactly this kind of multi-tenant SaaS setup with Laravel (API backend) + Vue frontend, so let me break down the actionable steps to make this work with a single codebase, single database, and custom domain support.
1. Domain Setup & Laravel Forge Configuration
First, let’s get the infrastructure sorted:
- Customer Domain Setup: Have your clients point their domain’s
Arecord (orCNAMEif using a CDN) to your Laravel Forge server’s public IP. - Forge Site Configuration: In Forge, add each customer’s domain to your existing Laravel project site (not a new site!). Ensure the document root still points to your project’s
publicdirectory. Enable Let’s Encrypt for each domain to enforce HTTPS—Forge handles this automatically once the DNS propagates. - Nginx Tweaks: Forge will auto-configure Nginx to serve all domains, but double-check that the server block includes a wildcard or explicit list of allowed domains (or just let it catch all, since your backend will validate legitimacy).
2. Tenant Identification & Middleware
You need a way to map incoming domains to a tenant in your database. Here’s how to implement it:
Database Setup
Create two core tables:
tenants: Stores tenant metadata (id,name,slug,created_at, etc.)tenant_domains: Maps domains to tenants (id,tenant_id,domain,primaryflag for default domain)
Tenant Identification Middleware
Build a middleware to detect the tenant on every request and make it globally accessible:
// app/Http/Middleware/IdentifyTenant.php namespace App\Http\Middleware; use Closure; use App\Models\Tenant; use Illuminate\Http\Request; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; class IdentifyTenant { public function handle(Request $request, Closure $next) { $host = strtolower($request->getHost()); // Skip your main app domain or local dev domains if (in_array($host, config('app.ignore_domains'))) { return $next($request); } // Cache tenant lookup to avoid repeated DB hits (1 hour TTL) $tenant = cache()->remember("tenant:{$host}", 3600, function () use ($host) { return Tenant::whereHas('domains', fn($q) => $q->where('domain', $host))->first(); }); if (!$tenant) { throw new NotFoundHttpException('Domain not registered'); } // Bind tenant to the app container for global access app()->instance('currentTenant', $tenant); return $next($request); } }
Register this middleware in app/Http/Kernel.php (add to web and api middleware groups).
3. Database Data Isolation
Ensure all tenant-specific data is filtered automatically:
Global Query Scope
Add a scope to all tenant-aware models to enforce tenant_id filtering:
// app/Scopes/TenantScope.php namespace App\Scopes; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Scope; class TenantScope implements Scope { public function apply(Builder $builder, Model $model) { if (app()->has('currentTenant')) { $builder->where('tenant_id', app('currentTenant')->id); } } }
Attach this scope to models like Car, Customer, User:
// app/Models/Car.php protected static function booted() { static::addGlobalScope(new TenantScope); }
Migration Updates
Add a tenant_id foreign key to all tenant-specific tables:
Schema::table('cars', function (Blueprint $table) { $table->foreignId('tenant_id')->constrained()->onDelete('cascade'); });
4. Vue Frontend Adaptation
Since your backend handles tenant isolation, the frontend just needs to:
- Fetch tenant-specific config (branding, settings) on initialization
- Use that config to customize the UI
Example Tenant Config API
Add an endpoint in Laravel to return tenant settings:
// routes/api.php Route::get('/tenant/config', function () { return response()->json([ 'name' => app('currentTenant')->name, 'logo_url' => app('currentTenant')->logo_url, 'primary_color' => app('currentTenant')->primary_color, ]); });
Vue Store Integration
Use Pinia (or Vuex) to store tenant config and fetch it on app load:
// src/stores/tenant.js import { defineStore } from 'pinia'; export const useTenantStore = defineStore('tenant', { state: () => ({ config: null }), actions: { async fetchConfig() { try { const res = await fetch('/api/tenant/config'); this.config = await res.json(); } catch (err) { console.error('Failed to load tenant config:', err); } } } });
Call fetchConfig() in your root App.vue component’s onMounted hook.
5. Critical Security & Optimization Tips
- Never Trust Frontend: All data filtering must happen on the backend—never rely on frontend logic to restrict access.
- User-Tenant Validation: Ensure authenticated users belong to the current tenant. Add a gate in
AuthServiceProvider:Gate::define('access-tenant', fn($user) => $user->tenant_id === app('currentTenant')->id); - Cache Wisely: Use tenant-specific cache keys (e.g.,
cache()->get("tenant:{$tenantId}:cars")) to avoid cross-tenant cache leaks. - HSTS Headers: Enable HSTS in Forge to force HTTPS and prevent domain hijacking.
This setup is scalable, low-maintenance, and aligns perfectly with your Laravel+Vue stack. I’ve used it for 100+ tenants without issues!
内容的提问来源于stack exchange,提问作者Lovelock

