ThinkPHP 6.0多租户SaaS系统架构实战:从设计到部署完整指南

2025-11-18 0 958

一、多租户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应用。在实际项目中,还需要根据具体业务需求调整和优化,但本文提供的框架和思路将为您的开发工作奠定坚实的基础。

建议在实际开发中充分测试各种边界情况,确保系统的稳定性和安全性,为企业的数字化转型提供可靠的技术支撑。

ThinkPHP 6.0多租户SaaS系统架构实战:从设计到部署完整指南
收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

淘吗网 thinkphp ThinkPHP 6.0多租户SaaS系统架构实战:从设计到部署完整指南 https://www.taomawang.com/server/thinkphp/1439.html

下一篇:

已经没有下一篇了!

常见问题

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务