ThinkPHP 6.0 多租户SaaS系统架构实战指南 | 完整实现方案

2025-11-29 0 690

原创作者:陈工程师 | 最后更新:2023年11月

一、多租户SaaS架构核心概念

多租户架构是SaaS(软件即服务)系统的核心设计模式,允许单个应用实例为多个客户(租户)提供服务,同时保持数据隔离和个性化配置。本教程将基于ThinkPHP 6.0实现三种主流的多租户方案。

1.1 三种多租户数据隔离方案

  • 独立数据库:每个租户拥有独立的数据库实例
  • 共享数据库独立Schema:同一数据库,不同数据表前缀
  • 共享数据表:通过tenant_id字段进行数据隔离

二、数据库架构设计

我们采用混合方案:核心租户信息独立存储,业务数据通过tenant_id隔离。

2.1 租户管理表结构

CREATE TABLE `tenants` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(100) NOT NULL COMMENT '租户名称',
  `subdomain` varchar(50) UNIQUE NOT NULL COMMENT '子域名标识',
  `database_name` varchar(50) DEFAULT NULL COMMENT '独立数据库名',
  `status` tinyint(1) DEFAULT '1' COMMENT '状态:1-正常 0-禁用',
  `created_at` datetime DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `tenant_id` int(11) NOT NULL COMMENT '租户ID',
  `username` varchar(50) NOT NULL,
  `email` varchar(100) NOT NULL,
  `password` varchar(255) NOT NULL,
  `created_at` datetime DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_tenant` (`tenant_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

三、租户识别中间件开发

通过自定义中间件实现基于子域名的租户自动识别。

3.1 创建租户中间件

<?php
namespace appmiddleware;

class TenantMiddleware
{
    public function handle($request, Closure $next)
    {
        // 从子域名获取租户标识
        $host = $request->host();
        $subdomain = $this->extractSubdomain($host);
        
        if (empty($subdomain)) {
            return json(['error' => '租户标识缺失'], 400);
        }
        
        // 查询租户信息
        $tenant = appmodelTenant::where('subdomain', $subdomain)
                    ->where('status', 1)
                    ->find();
                    
        if (!$tenant) {
            return json(['error' => '租户不存在或已被禁用'], 404);
        }
        
        // 将租户信息绑定到请求对象
        $request->tenant = $tenant;
        $request->tenantId = $tenant->id;
        
        return $next($request);
    }
    
    private function extractSubdomain($host)
    {
        $parts = explode('.', $host);
        if (count($parts) > 2) {
            return $parts[0];
        }
        return null;
    }
}

3.2 中间件注册配置

// app/middleware.php
return [
    // 全局中间件
    appmiddlewareTenantMiddleware::class,
];

四、多租户模型基类实现

创建基础模型类自动处理租户数据隔离。

4.1 多租户模型基类

<?php
namespace appmodel;

use thinkModel;

class BaseModel extends Model
{
    protected $tenantId = null;
    
    protected static function boot()
    {
        parent::boot();
        
        // 全局范围:自动过滤当前租户数据
        static::addGlobalScope('tenant', function ($query) {
            $tenantId = app('request')->tenantId ?? null;
            if ($tenantId && $query->getTableFields()->contains('tenant_id')) {
                $query->where('tenant_id', $tenantId);
            }
        });
        
        // 自动设置租户ID
        static::event('before_insert', function ($model) {
            $tenantId = app('request')->tenantId ?? null;
            if ($tenantId && in_array('tenant_id', $model->getTableFields())) {
                $model->tenant_id = $tenantId;
            }
        });
    }
    
    // 移除租户过滤(用于跨租户查询)
    public function withoutTenantScope()
    {
        return $this->withoutGlobalScope('tenant');
    }
}

4.2 业务模型继承示例

<?php
namespace appmodel;

class User extends BaseModel
{
    protected $table = 'users';
    
    public function tenant()
    {
        return $this->belongsTo(Tenant::class);
    }
}

五、完整实战:多租户CRM系统

基于上述架构实现一个简易的多租户客户关系管理系统。

5.1 控制器实现

<?php
namespace appcontroller;

use appBaseController;
use appmodelCustomer;

class CustomerController extends BaseController
{
    // 获取当前租户的客户列表
    public function index()
    {
        $page = $this->request->param('page', 1);
        $size = $this->request->param('size', 15);
        
        $customers = Customer::withSearch(['name', 'email'], [
            'name' => $this->request->param('name'),
            'email' => $this->request->param('email')
        ])->paginate(['page' => $page, 'list_rows' => $size]);
        
        return json([
            'code' => 200,
            'data' => $customers->items(),
            'total' => $customers->total()
        ]);
    }
    
    // 创建客户(自动绑定当前租户)
    public function create()
    {
        $data = $this->request->only(['name', 'email', 'phone']);
        
        $customer = new Customer();
        $result = $customer->save($data);
        
        if ($result) {
            return json(['code' => 200, 'message' => '客户创建成功']);
        } else {
            return json(['code' => 500, 'message' => '客户创建失败']);
        }
    }
}

5.2 模型搜索器实现

<?php
namespace appmodel;

class Customer extends BaseModel
{
    protected $table = 'customers';
    
    // 定义搜索器
    public function searchNameAttr($query, $value)
    {
        $value && $query->where('name', 'like', '%' . $value . '%');
    }
    
    public function searchEmailAttr($query, $value)
    {
        $value && $query->where('email', 'like', '%' . $value . '%');
    }
}

5.3 路由配置

// route/app.php
use thinkfacadeRoute;

Route::group('customer', function () {
    Route::get('list', 'CustomerController/index');
    Route::post('create', 'CustomerController/create');
    Route::put('update/:id', 'CustomerController/update');
    Route::delete('delete/:id', 'CustomerController/delete');
});

六、高级特性与优化建议

6.1 租户数据库动态连接

// 动态切换租户独立数据库
public static function switchTenantDatabase($tenant)
{
    if ($tenant->database_name) {
        $config = [
            'type' => 'mysql',
            'hostname' => '127.0.0.1',
            'database' => $tenant->database_name,
            'username' => 'root',
            'password' => 'password',
            'charset' => 'utf8mb4',
        ];
        
        Db::connect($config, 'tenant_' . $tenant->id);
    }
}

6.2 性能优化策略

  • 使用Redis缓存租户配置信息
  • 实现数据库连接池管理
  • 添加租户级别的查询缓存
  • 定期清理无效租户数据

七、部署与测试

完整的测试用例确保多租户隔离的正确性。

7.1 单元测试示例

<?php
namespace tests;

use thinktestingTestCase;

class TenantTest extends TestCase
{
    public function testTenantIsolation()
    {
        // 模拟租户A请求
        $this->withHeader('Host', 'companya.example.com')
             ->get('/customer/list');
             
        $customersA = Customer::select();
        $this->assertTrue($customersA->every(function ($item) {
            return $item->tenant_id == 1;
        }));
        
        // 模拟租户B请求  
        $this->withHeader('Host', 'companyb.example.com')
             ->get('/customer/list');
             
        $customersB = Customer::select();
        $this->assertTrue($customersB->every(function ($item) {
            return $item->tenant_id == 2;
        }));
    }
}

ThinkPHP 6.0 多租户SaaS系统架构实战指南 | 完整实现方案
收藏 (0) 打赏

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

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

淘吗网 thinkphp ThinkPHP 6.0 多租户SaaS系统架构实战指南 | 完整实现方案 https://www.taomawang.com/server/thinkphp/1452.html

下一篇:

已经没有下一篇了!

常见问题

相关文章

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

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