从零构建企业级多租户应用平台的完整解决方案
多租户SaaS架构概述
什么是多租户SaaS架构?
多租户SaaS(Software as a Service)架构是一种软件架构模式,允许多个客户(租户)共享同一套应用程序实例,但每个租户的数据和配置相互隔离。ThinkPHP 6.0凭借其强大的中间件、事件系统和数据库抽象层,成为构建此类系统的理想选择。
共享数据库
所有租户共享同一数据库,通过tenant_id字段隔离数据
独立数据库
每个租户拥有独立的数据库实例,数据完全隔离
混合模式
结合共享和独立数据库的优势,灵活配置
多租户数据库设计策略
基于tenant_id的数据隔离方案
在共享数据库模式下,我们通过在每个表中添加tenant_id字段来实现数据隔离。ThinkPHP的全局查询范围功能可以自动为每个查询添加租户过滤条件。
数据库迁移文件示例
<?php
// 创建租户表
class CreateTenantsTable extends Migration
{
public function up()
{
Schema::create('tenants', function (Blueprint $table) {
$table->id();
$table->string('name', 100)->comment('租户名称');
$table->string('domain', 255)->unique()->comment('租户域名');
$table->string('database_name', 100)->nullable()->comment('独立数据库名');
$table->json('config')->nullable()->comment('租户配置');
$table->tinyInteger('status')->default(1)->comment('状态');
$table->timestamps();
});
}
}
// 用户表(包含tenant_id)
class CreateUsersTable extends Migration
{
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('tenant_id')->index()->comment('租户ID');
$table->string('name', 50)->comment('用户名');
$table->string('email', 100)->comment('邮箱');
$table->string('password', 255)->comment('密码');
$table->timestamps();
// 租户内唯一约束
$table->unique(['tenant_id', 'email']);
});
}
}
?>
租户识别中间件开发
基于域名的租户自动识别
通过自定义中间件,我们可以根据访问的域名自动识别当前租户,并设置全局的租户上下文。
租户识别中间件
<?php
namespace appmiddleware;
class TenantMiddleware
{
public function handle($request, Closure $next)
{
// 获取当前访问域名
$domain = $request->host();
// 从域名中提取子域名或直接匹配
$subdomain = $this->extractSubdomain($domain);
// 查询租户信息
$tenant = appmodelTenant::where('domain', $domain)
->where('status', 1)
->find();
if (!$tenant) {
// 租户不存在,返回错误页面
return json(['error' => 'Tenant not found'], 404);
}
// 设置全局租户上下文
appcommonTenantContext::setTenant($tenant);
// 如果是独立数据库模式,切换数据库连接
if ($tenant->database_name) {
$this->switchDatabase($tenant->database_name);
}
return $next($request);
}
protected function extractSubdomain($domain)
{
$mainDomain = config('tenant.main_domain');
return str_replace('.' . $mainDomain, '', $domain);
}
protected function switchDatabase($databaseName)
{
// 动态切换数据库配置
config('database.connections.tenant.database', $databaseName);
thinkfacadeDb::connect('tenant');
}
}
?>
全局查询范围自动过滤
// 在模型中使用全局范围自动添加tenant_id条件
class User extends Model
{
protected $globalScope = ['tenant'];
public function scopeTenant($query)
{
$tenantId = TenantContext::getTenantId();
if ($tenantId) {
$query->where('tenant_id', $tenantId);
}
}
}
实战案例:多租户CRM系统
客户关系管理系统架构
系统功能模块
- 客户管理(每个租户独立)
- 销售机会跟踪
- 合同管理
- 数据分析报表
- 团队协作
技术特性
- 自动租户识别
- 数据隔离保障
- 可配置的业务规则
- 多级权限控制
- API接口支持
控制器示例:客户管理
<?php
namespace apptenantcontroller;
use appBaseController;
use apptenantmodelCustomer;
class CustomerController extends BaseController
{
public function index()
{
$page = $this->request->param('page', 1);
$limit = $this->request->param('limit', 15);
// 自动过滤当前租户的客户数据
$customers = Customer::with(['contacts', 'opportunities'])
->where('status', 1)
->order('create_time', 'desc')
->paginate([
'list_rows' => $limit,
'page' => $page
]);
return json([
'code' => 200,
'data' => $customers,
'msg' => 'success'
]);
}
public function create()
{
$data = $this->request->post();
// 自动设置当前租户ID
$data['tenant_id'] = appcommonTenantContext::getTenantId();
$customer = new Customer();
if ($customer->save($data)) {
// 记录操作日志(自动关联租户)
appcommonOperationLog::record('创建客户', $customer);
return json([
'code' => 200,
'msg' => '客户创建成功'
]);
}
return json([
'code' => 500,
'msg' => '客户创建失败'
]);
}
}
?>
部署与性能优化
数据库优化策略
- 为tenant_id字段创建索引
- 使用数据库连接池
- 读写分离配置
- 定期数据归档
缓存优化方案
- 租户配置信息缓存
- Redis多数据库支持
- API响应缓存
- 静态资源CDN加速
环境配置建议
// config/tenant.php
return [
'main_domain' => env('TENANT_MAIN_DOMAIN', 'saas.com'),
'mode' => env('TENANT_MODE', 'shared'), // shared, isolated, hybrid
'cache_ttl' => 3600,
'max_tenants_per_server' => 1000,
'auto_create_db' => false,
];

