从0到1构建多租户SaaS平台:LaravelTenancy实战指南
你是否正在为如何用Laravel快速开发支持多客户的SaaS系统而烦恼?是否在数据隔离、租户路由、权限控制等问题上反复踩坑?本文将通过5个实战步骤,带你从零构建一个企业级多租户应用,解决90%的常见痛点。读完本文你将掌握:
- 三种租户隔离方案的选型决策
- Stancl/Tenancy扩展的无缝集成
- 自动化租户迁移与数据隔离实现
- 多租户路由与中间件配置
- 生产环境部署的关键安全配置
多租户架构选型:3种方案优缺点对比
在开始编码前,需根据业务需求选择合适的租户隔离策略。Laravel生态中主流方案有三种:
| 方案 | 实现方式 | 优势 | 劣势 | 适用场景 |
|---|---|---|---|---|
| 独立数据库 | 为每个租户创建独立数据库 | 最高级别隔离,性能最优 | 维护成本高,备份复杂 | 金融、医疗等高合规要求场景 |
| 共享数据库独立Schema | 同一数据库不同表前缀 | 中等隔离,运维简单 | 跨租户查询复杂 | 中小型SaaS应用 |
| 共享数据库共享表 | 所有租户数据在同一表,通过tenant_id区分 | 部署简单,资源占用低 | 隔离性差,性能瓶颈 | 轻量级应用或原型验证 |
推荐选择:中小团队优先选择"共享数据库独立Schema"方案,平衡开发效率与数据安全。本文将基于此方案展开实现,使用Stancl/Tenancy扩展(需通过composer require stancl/tenancy安装)。
扩展集成与配置初始化
安装核心依赖
由于当前环境未检测到Composer,实际部署时需先执行:
composer require stancl/tenancy:^3.7
配置服务提供者
修改app/Providers/AppServiceProvider.php,注册租户服务:
public function register()
{
$this->app->register(\Stancl\Tenancy\TenancyServiceProvider::class);
$this->app->register(\Stancl\Tenancy\Providers\ConsoleServiceProvider::class);
}
生成配置文件
php artisan tenancy:install
该命令会自动创建config/tenancy.php配置文件,关键配置项说明:
// 租户识别方式:域名/子目录/请求头
'tenant_identification' => [
'driver' => 'domain', // 推荐生产环境使用域名识别
'prefix' => 'tenant-', // 数据库Schema前缀
],
// 租户数据连接
'database' => [
'connection' => 'tenant', // 对应config/database.php中的连接配置
'prefix_base' => 'tenant_', // Schema名称前缀
],
数据模型与迁移设计
创建租户模型
新建app/Models/Tenant.php:
use Stancl\Tenancy\Database\Models\Tenant as BaseTenant;
use Stancl\Tenancy\Contracts\TenantWithDatabase;
use Stancl\Tenancy\Database\Concerns\HasDatabase;
use Stancl\Tenancy\Database\Concerns\HasDomains;
class Tenant extends BaseTenant implements TenantWithDatabase
{
use HasDatabase, HasDomains;
protected $fillable = [
'id', 'name', 'plan', // 可扩展租户属性:名称、订阅计划等
];
}
多租户迁移策略
-
系统级迁移:保留原有的database/migrations/目录,用于创建租户表、系统配置表等全局数据
-
租户级迁移:创建database/migrations/tenant/目录,存放租户专属表结构:
mkdir -p database/migrations/tenant
php artisan make:migration create_tenant_users_table --path=database/migrations/tenant
修改租户迁移文件,添加租户隔离字段:
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->rememberToken();
$table->timestamps();
});
}
路由与中间件配置
配置多租户路由
修改routes/web.php,区分系统路由与租户路由:
// 系统路由:租户注册、登录等
Route::get('/register-tenant', [TenantController::class, 'create']);
Route::post('/register-tenant', [TenantController::class, 'store']);
// 租户路由组:需通过租户中间件
Route::middleware([
\Stancl\Tenancy\Middleware\InitializeTenancyByDomain::class,
\Stancl\Tenancy\Middleware\PreventAccessFromCentralDomains::class,
])->group(function () {
Route::get('/', function () {
return view('tenant.welcome'); // 租户专属视图
});
Route::get('/dashboard', [TenantDashboardController::class, 'index']);
});
创建租户控制器
新建app/Http/Controllers/TenantController.php:
use App\Models\Tenant;
use Illuminate\Http\Request;
class TenantController extends Controller
{
public function store(Request $request)
{
$validated = $request->validate([
'name' => 'required',
'domain' => 'required|unique:domains',
'plan' => 'required|in:basic,premium',
]);
// 创建租户
$tenant = Tenant::create([
'id' => Str::random(10), // 生成唯一租户ID
'name' => $validated['name'],
'plan' => $validated['plan'],
]);
// 关联域名
$tenant->domains()->create([
'domain' => $validated['domain'],
]);
// 运行租户迁移
$tenant->runMigrations();
return redirect()->route('tenant.login', $tenant);
}
}
生产环境安全与性能优化
关键安全配置
- 数据库权限控制:在config/database.php中为租户连接配置最小权限账户:
'connections' => [
'tenant' => [
'driver' => 'mysql',
'host' => env('DB_HOST'),
'database' => null, // 动态指定
'username' => env('TENANT_DB_USER'), // 仅拥有CREATE SCHEMA权限的账户
'password' => env('TENANT_DB_PASSWORD'),
],
],
- 文件存储隔离:修改config/filesystems.php添加租户磁盘:
'disks' => [
'tenant' => [
'driver' => 'local',
'root' => storage_path('app/tenants'),
'url' => env('APP_URL').'/storage/tenants',
'visibility' => 'public',
],
],
性能优化建议
- 缓存租户配置:在app/Providers/AppServiceProvider.php中添加:
public function boot()
{
if (tenancy()->initialized) {
Cache::store('redis')->rememberForever(
'tenant:'.$tenant->id.':config',
fn () => $tenant->config
);
}
}
- 队列任务隔离:使用租户ID作为队列名称前缀,修改config/queue.php:
'default' => tenancy()->initialized ? 'tenant_'.tenant('id') : 'default',
总结与进阶方向
通过本文实现的多租户架构,已具备企业级SaaS平台的核心能力:完整的数据隔离、独立的租户域名、自动化迁移管理。后续可扩展以下高级特性:
- 基于app/Models/User.php实现跨租户用户权限管理
- 集成Stripe实现租户订阅计费系统
- 使用Redis实现租户级缓存与限流
建议通过tests/Feature/TenancyTest.php编写自动化测试,确保多租户功能稳定性:
public function test_tenant_data_is_isolated()
{
$tenant1 = Tenant::factory()->create();
$tenant2 = Tenant::factory()->create();
tenancy()->initialize($tenant1);
User::create(['name' => 'Tenant 1 User']);
tenancy()->initialize($tenant2);
$this->assertCount(0, User::all()); // 验证数据隔离
}
完整代码结构可参考项目README.md中的多租户章节,建议配合Laravel Telescope进行租户请求调试与监控。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



