ThinkPHP6多租户SaaS系统架构实战:动态数据库切换方案

2025-07-19 0 951

ThinkPHP6多租户SaaS系统架构实战:动态数据库切换方案

一、多租户架构设计模式

三种主流多租户实现方案对比:

方案类型 优点 缺点
独立数据库 数据完全隔离,安全性高 运维成本高
共享数据库独立Schema 平衡隔离与成本 需要数据库权限
共享数据表 成本最低 隔离性差

本文采用独立数据库+动态切换方案

二、核心实现方案

1. 数据库配置动态化

// config/database.php
return [
    'default' => env('database.default', 'mysql'),
    'connections' => [
        'mysql' => [
            'type' => 'mysql',
            'hostname' => env('database.hostname', '127.0.0.1'),
            'database' => env('database.database', ''),
            'username' => env('database.username', 'root'),
            'password' => env('database.password', ''),
            // 其他公共配置...
        ],
        // 租户数据库模板
        'tenant_template' => [
            'type' => 'mysql',
            'hostname' => env('tenant.db_host', '127.0.0.1'),
            'username' => env('tenant.db_user', 'tenant_user'),
            'password' => env('tenant.db_pass', ''),
            'charset' => 'utf8mb4',
            'prefix' => '',
        ]
    ]
];

2. 租户识别中间件

// app/middleware/Tenancy.php
class Tenancy
{
    public function handle($request, Closure $next)
    {
        // 从域名或请求头获取租户标识
        $tenantId = $this->getTenantId($request);
        
        // 初始化租户上下文
        TenantContext::init($tenantId);
        
        // 动态切换数据库
        $this->switchDatabase($tenantId);
        
        return $next($request);
    }

    protected function switchDatabase($tenantId)
    {
        // 获取租户专属数据库名
        $dbName = 'tenant_' . $tenantId;
        
        // 动态修改配置
        config([
            'database.connections.tenant' => array_merge(
                config('database.connections.tenant_template'),
                ['database' => $dbName]
            ),
            'database.default' => 'tenant'
        ]);
        
        // 重新连接数据库
        Db::connect('tenant')->reconnect();
    }
}

3. 租户上下文管理

// app/common/TenantContext.php
class TenantContext
{
    protected static $tenantId;

    public static function init($tenantId)
    {
        self::$tenantId = $tenantId;
        app()->bind('tenant', function() use ($tenantId) {
            return TenantModel::find($tenantId);
        });
    }

    public static function getTenantId()
    {
        return self::$tenantId;
    }

    public static function getCurrent()
    {
        return app('tenant');
    }
}

4. 租户数据范围控制

// app/model/BaseModel.php
class BaseModel extends Model
{
    protected static function boot()
    {
        parent::boot();
        
        // 自动限定租户数据范围
        static::addGlobalScope('tenant', function($builder) {
            if ($tenantId = TenantContext::getTenantId()) {
                $builder->where('tenant_id', $tenantId);
            }
        });
    }
}

三、关键问题解决方案

1. 租户数据库自动化创建

// 使用事件监听器自动建库
Event::listen('TenantCreated', function($tenant) {
    $dbName = 'tenant_' . $tenant->id;
    
    // 使用主连接创建数据库
    Db::connect('mysql')->execute(
        "CREATE DATABASE IF NOT EXISTS `{$dbName}` CHARACTER SET utf8mb4"
    );
    
    // 执行基础SQL脚本
    $sql = file_get_contents(app_path('database/tenant_template.sql'));
    Db::connect('tenant')->execute($sql);
});

2. 跨租户数据迁移工具

class TenantDataMigrator
{
    public function migrate($fromTenant, $toTenant)
    {
        $tables = ['users', 'products', 'orders'];
        
        foreach ($tables as $table) {
            $this->migrateTable($fromTenant, $toTenant, $table);
        }
    }

    protected function migrateTable($from, $to, $table)
    {
        $data = Db::connect("tenant_$from")
            ->table($table)
            ->get()
            ->toArray();
            
        Db::connect("tenant_$to")
            ->table($table)
            ->insert($data);
    }
}

四、性能优化建议

  • 连接池管理:使用Hyperf数据库连接池
  • 配置缓存:缓存租户数据库配置
  • 查询优化:避免跨库JOIN操作
  • 分库策略:按租户活跃度分组部署
ThinkPHP6多租户SaaS系统架构实战:动态数据库切换方案
收藏 (0) 打赏

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

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

淘吗网 thinkphp ThinkPHP6多租户SaaS系统架构实战:动态数据库切换方案 https://www.taomawang.com/server/thinkphp/480.html

常见问题

相关文章

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

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