一、多租户SaaS系统:现代企业级应用的核心架构
多租户SaaS(Software as a Service)架构是当前企业级应用开发的重要模式,它允许单个应用实例为多个客户(租户)提供服务,同时确保数据的安全隔离。ThinkPHP 6.0凭借其强大的扩展性和灵活性,成为构建此类系统的理想选择。
多租户架构的核心挑战:
- 数据隔离:确保不同租户数据的完全隔离
- 资源共享:高效共享系统资源同时保证性能
- 租户识别:准确识别和路由租户请求
- 扩展性:支持租户数量的动态扩展
二、多租户架构设计模式深度解析
2.1 三种主流多租户模式对比
独立数据库模式
每个租户拥有独立的数据库实例,数据隔离级别最高,但运维成本较高。
共享数据库独立Schema
共享数据库服务器,但每个租户有独立的数据库Schema,平衡了隔离性和资源利用率。
共享数据库共享Schema
所有租户共享相同的数据库和表结构,通过tenant_id字段进行数据隔离,资源利用率最高。
2.2 基于ThinkPHP的混合架构设计
我们采用共享数据库独立Schema模式,结合ThinkPHP的多数据库连接特性:
// 数据库配置示例
return [
'default' => 'tenant_common',
'connections' => [
'tenant_common' => [
'type' => 'mysql',
'hostname' => '127.0.0.1',
'database' => 'saas_common',
'username' => 'root',
'password' => '',
'charset' => 'utf8mb4',
],
'tenant_template' => [
'type' => 'mysql',
'hostname' => '127.0.0.1',
'database' => 'tenant_template',
'username' => 'root',
'password' => '',
'charset' => 'utf8mb4',
]
]
];
三、ThinkPHP 6.0多租户开发环境搭建
3.1 项目初始化与扩展安装
# 创建ThinkPHP项目
composer create-project topthink/think saas-system
# 安装多租户扩展
composer require topthink/think-multi-app
composer require hhxsv5/laravel-s
3.2 目录结构设计
saas-system/
├── app/
│ ├── common/ # 公共模块
│ ├── tenant/ # 租户业务模块
│ ├── admin/ # 管理后台模块
│ └── middleware/ # 中间件
├── config/
├── database/
│ ├── migrations/ # 数据库迁移
│ └── seeds/ # 数据填充
└── public/
3.3 数据库迁移文件设计
<?php
// 租户表迁移文件
use thinkmigrationMigrator;
use thinkmigrationdbColumn;
class CreateTenantsTable extends Migrator
{
public function change()
{
$table = $this->table('tenants');
$table->addColumn('name', 'string', ['limit' => 100])
->addColumn('subdomain', 'string', ['limit' => 50])
->addColumn('database_name', 'string', ['limit' => 50])
->addColumn('status', 'integer', ['default' => 1])
->addColumn('created_at', 'timestamp', ['default' => 'CURRENT_TIMESTAMP'])
->addColumn('updated_at', 'timestamp', ['null' => true])
->addIndex(['subdomain'], ['unique' => true])
->create();
}
}
四、核心功能实现:租户识别与数据隔离
4.1 租户识别中间件
<?php
declare (strict_types = 1);
namespace appmiddleware;
use appcommonserviceTenantService;
class TenantIdentify
{
public function handle($request, Closure $next)
{
// 通过子域名识别租户
$subdomain = $this->getSubdomain($request->host());
if ($subdomain && $subdomain != 'www' && $subdomain != 'admin') {
// 识别租户并设置数据库连接
$tenant = TenantService::getTenantBySubdomain($subdomain);
if ($tenant) {
// 设置租户上下文
app()->bind('tenant', $tenant);
// 切换数据库连接
$this->switchTenantDatabase($tenant);
} else {
// 租户不存在
return redirect('/error/tenant-not-found');
}
}
return $next($request);
}
private function getSubdomain($host)
{
$parts = explode('.', $host);
if (count($parts) > 2) {
return $parts[0];
}
return null;
}
private function switchTenantDatabase($tenant)
{
$databaseConfig = config('database.connections.tenant_template');
$databaseConfig['database'] = $tenant['database_name'];
config(['database.connections.tenant_current' => $databaseConfig]);
config(['database.default' => 'tenant_current']);
}
}
4.2 租户服务类实现
<?php
declare (strict_types = 1);
namespace appcommonservice;
use thinkfacadeDb;
use thinkfacadeCache;
class TenantService
{
public static function getTenantBySubdomain($subdomain)
{
$cacheKey = "tenant:{$subdomain}";
return Cache::remember($cacheKey, function() use ($subdomain) {
return Db::connect('tenant_common')
->name('tenants')
->where('subdomain', $subdomain)
->where('status', 1)
->find();
}, 3600); // 缓存1小时
}
public static function createTenant($data)
{
Db::connect('tenant_common')->startTrans();
try {
// 创建租户记录
$tenantId = Db::connect('tenant_common')
->name('tenants')
->insertGetId($data);
// 创建租户数据库
self::createTenantDatabase($data['database_name']);
Db::connect('tenant_common')->commit();
return $tenantId;
} catch (Exception $e) {
Db::connect('tenant_common')->rollback();
throw $e;
}
}
private static function createTenantDatabase($databaseName)
{
// 复制模板数据库结构
$templateDb = config('database.connections.tenant_template.database');
Db::connect('tenant_template')->execute(
"CREATE DATABASE {$databaseName} LIKE {$templateDb}"
);
}
}
4.3 多租户模型基类
<?php
declare (strict_types = 1);
namespace appcommonmodel;
use thinkModel;
class TenantModel extends Model
{
// 自动写入租户ID
protected $autoWriteTimestamp = true;
protected static function onBeforeInsert($model)
{
$tenant = app()->has('tenant') ? app()->get('tenant') : null;
if ($tenant) {
$model->setAttr('tenant_id', $tenant['id']);
}
}
protected function scopeTenant($query)
{
$tenant = app()->has('tenant') ? app()->get('tenant') : null;
if ($tenant) {
$query->where('tenant_id', $tenant['id']);
}
}
// 所有查询自动加上租户条件
protected function base($query)
{
$query->tenant();
}
}
五、高级特性与系统优化
5.1 租户数据隔离增强
<?php
// 租户数据范围控制
class User extends TenantModel
{
public function posts()
{
return $this->hasMany(Post::class)->tenant();
}
// 跨租户数据访问(仅管理员)
public function scopeCrossTenant($query, $tenantId = null)
{
if (app('admin')->isSuperAdmin()) {
if ($tenantId) {
$query->where('tenant_id', $tenantId);
}
return $query;
}
return $query->tenant();
}
}
5.2 性能优化策略
数据库连接池
// 使用连接池管理数据库连接
'database' => [
'default' => 'tenant_common',
'connections' => [
'tenant_common' => [
'type' => 'mysql',
'hostname' => '127.0.0.1',
'database' => 'saas_common',
'username' => 'root',
'password' => '',
'charset' => 'utf8mb4',
'break_reconnect' => true,
'break_match_str' => [
'server has gone away',
'no connection to the server',
'Lost connection',
'is dead or not enabled',
],
]
]
]
Redis多数据库支持
// 为每个租户分配独立的Redis数据库
'redis' => [
'default' => [
'host' => '127.0.0.1',
'port' => 6379,
'password' => '',
'select' => 0, // 公共数据
'timeout' => 0,
],
'tenant' => [
'host' => '127.0.0.1',
'port' => 6379,
'password' => '',
'select' => 1, // 租户数据
'timeout' => 0,
]
]
5.3 文件存储隔离
<?php
class TenantStorage
{
public static function getDisk($tenantId = null)
{
$tenantId = $tenantId ?: app()->get('tenant')['id'];
return thinkfacadeFilesystem::disk('tenant')->setPath($tenantId);
}
public static function upload($file, $path = '')
{
$disk = self::getDisk();
$filename = $disk->putFile($path, $file);
return [
'url' => $disk->url($filename),
'path' => $filename
];
}
}
六、生产环境部署与运维
6.1 Nginx多租户路由配置
server {
listen 80;
server_name ~^(?<subdomain>.+).saas-app.com$;
root /var/www/saas-system/public;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ .php$ {
fastcgi_pass unix:/var/run/php/php8.0-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
# 传递子域名信息
fastcgi_param HTTP_X_TENANT_SUBDOMAIN $subdomain;
}
}
6.2 数据库备份策略
<?php
// 自动化备份脚本
class TenantBackup
{
public function backupAllTenants()
{
$tenants = Db::connect('tenant_common')
->name('tenants')
->where('status', 1)
->select();
foreach ($tenants as $tenant) {
$this->backupTenant($tenant);
}
}
private function backupTenant($tenant)
{
$backupFile = "backup/tenant_{$tenant['id']}_" . date('Ymd_His') . '.sql';
$command = sprintf(
'mysqldump -u%s -p%s %s > %s',
config('database.connections.tenant_template.username'),
config('database.connections.tenant_template.password'),
$tenant['database_name'],
$backupFile
);
system($command, $result);
if ($result === 0) {
// 备份成功,记录日志
$this->logBackup($tenant['id'], $backupFile);
}
}
}
6.3 监控与日志系统
// 租户操作日志中间件
class TenantLog
{
public function handle($request, Closure $next)
{
$response = $next($request);
if (app()->has('tenant')) {
$tenant = app()->get('tenant');
// 记录租户操作日志
Db::connect('tenant_common')
->name('tenant_operation_logs')
->insert([
'tenant_id' => $tenant['id'],
'user_id' => session('user_id') ?: 0,
'url' => $request->url(),
'method' => $request->method(),
'ip' => $request->ip(),
'user_agent' => $request->header('user-agent'),
'created_at' => date('Y-m-d H:i:s')
]);
}
return $response;
}
}
总结
通过本教程,我们完整地构建了一个基于ThinkPHP 6.0的多租户SaaS系统。从架构设计、环境搭建到核心功能实现,我们深入探讨了多租户系统的关键技术点。ThinkPHP 6.0的灵活性和扩展性为构建复杂的企业级应用提供了强有力的支持。
多租户SaaS系统的核心在于数据隔离和资源共享的平衡。通过合理的架构设计和代码组织,我们可以构建出既安全又高效的SaaS应用。在实际项目中,还需要根据具体业务需求调整和优化,但本文提供的框架和思路将为您的开发工作奠定坚实的基础。
建议在实际开发中充分测试各种边界情况,确保系统的稳定性和安全性,为企业的数字化转型提供可靠的技术支撑。

