ThinkPHP 6.0多租户SaaS系统架构设计与实现 – 完整实战指南

2025-10-05 0 367

一、多租户架构概述

多租户架构是现代SaaS应用的核心设计模式,允许单个应用实例为多个租户提供服务,同时保证数据的安全隔离。本文将深入探讨基于ThinkPHP 6.0的多租户系统完整实现方案。

二、架构设计模式选择

2.1 数据库隔离方案对比

  • 独立数据库:每个租户使用独立数据库,安全性最高
  • 共享数据库独立Schema:同一数据库,不同数据表结构
  • 共享数据库共享Schema:通过tenant_id字段进行数据隔离

2.2 技术选型理由

选择共享数据库共享Schema方案,平衡了开发复杂度与系统性能,适合中小型SaaS应用快速迭代。

三、核心代码实现

3.1 中间件实现租户识别


namespace appmiddleware;

class TenantMiddleware
{
    public function handle($request, Closure $next)
    {
        // 从子域名识别租户
        $subdomain = explode('.', $request->host())[0];
        
        // 从请求头识别租户
        $tenantId = $request->header('X-Tenant-ID');
        
        // 查询租户信息
        $tenant = appmodelTenant::where('identifier', $subdomain)
                    ->whereOr('id', $tenantId)
                    ->find();
                    
        if (!$tenant) {
            throw new thinkexceptionHttpException(404, '租户不存在');
        }
        
        // 设置全局租户上下文
        appfacadeTenantContext::setTenant($tenant);
        
        return $next($request);
    }
}
    

3.2 租户上下文管理


namespace appfacade;

use thinkFacade;

class TenantContext extends Facade
{
    protected static function getFacadeClass()
    {
        return appserviceTenantService::class;
    }
}

namespace appservice;

class TenantService
{
    protected $tenant;
    
    public function setTenant($tenant)
    {
        $this->tenant = $tenant;
        return $this;
    }
    
    public function getTenant()
    {
        return $this->tenant;
    }
    
    public function getId()
    {
        return $this->tenant ? $this->tenant->id : null;
    }
}
    

3.3 模型层自动过滤


namespace appmodel;

use thinkModel;

class BaseModel extends Model
{
    protected function scopeTenant($query)
    {
        $tenantId = appfacadeTenantContext::getId();
        if ($tenantId) {
            $query->where('tenant_id', $tenantId);
        }
    }
    
    protected static function onBeforeInsert($model)
    {
        $tenantId = appfacadeTenantContext::getId();
        if ($tenantId && !$model->tenant_id) {
            $model->tenant_id = $tenantId;
        }
    }
}

// 业务模型继承
class User extends BaseModel
{
    protected $table = 'users';
    
    // 所有查询自动加入租户过滤
    public function search($keyword)
    {
        return $this->tenant()->where('name|email', 'like', "%{$keyword}%")->select();
    }
}
    

四、数据库设计

4.1 租户表结构


CREATE TABLE `tenants` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(100) NOT NULL COMMENT '租户名称',
  `identifier` varchar(50) NOT NULL COMMENT '唯一标识符',
  `status` tinyint(1) DEFAULT '1' COMMENT '状态:0-禁用,1-启用',
  `created_at` datetime DEFAULT CURRENT_TIMESTAMP,
  `updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_identifier` (`identifier`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
    

4.2 业务表结构示例


CREATE TABLE `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `tenant_id` int(11) NOT NULL COMMENT '租户ID',
  `name` varchar(50) NOT NULL COMMENT '用户姓名',
  `email` varchar(100) NOT NULL COMMENT '邮箱',
  `created_at` datetime DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `idx_tenant` (`tenant_id`),
  KEY `idx_email` (`email`),
  CONSTRAINT `fk_user_tenant` FOREIGN KEY (`tenant_id`) REFERENCES `tenants` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
    

五、路由配置与子域名支持

5.1 路由配置文件


// route/app.php
Route::domain(':subdomain', function () {
    Route::get('/', 'index/index');
    Route::get('users', 'user/index');
    Route::post('users', 'user/create');
    Route::put('users/:id', 'user/update');
    Route::delete('users/:id', 'user/delete');
})->completeMatch(false);
    

5.2 控制器实现


namespace appcontroller;

class User extends BaseController
{
    public function index()
    {
        $keyword = $this->request->param('keyword');
        $users = appmodelUser::search($keyword)->paginate(10);
        
        return json([
            'code' => 200,
            'data' => $users->items(),
            'total' => $users->total()
        ]);
    }
    
    public function create()
    {
        $data = $this->request->post();
        $user = new appmodelUser();
        
        if ($user->save($data)) {
            return json(['code' => 200, 'message' => '创建成功']);
        }
        
        return json(['code' => 500, 'message' => '创建失败']);
    }
}
    

六、高级特性实现

6.1 租户自定义配置


namespace appservice;

class TenantConfigService
{
    public function getConfig($key, $default = null)
    {
        $tenantId = TenantContext::getId();
        $config = appmodelTenantConfig::where([
            'tenant_id' => $tenantId,
            'config_key' => $key
        ])->value('config_value');
        
        return $config ?: $default;
    }
    
    public function setConfig($key, $value)
    {
        $tenantId = TenantContext::getId();
        return appmodelTenantConfig::updateOrInsert(
            ['tenant_id' => $tenantId, 'config_key' => $key],
            ['config_value' => $value, 'updated_at' => date('Y-m-d H:i:s')]
        );
    }
}
    

6.2 数据迁移与种子数据


// 数据库迁移类
class CreateTenantTables
{
    public function up()
    {
        $sql = "
        CREATE TABLE IF NOT EXISTS `tenant_{TENANT_ID}_custom_data` (
          `id` int(11) NOT NULL AUTO_INCREMENT,
          `field1` varchar(255) DEFAULT NULL,
          `field2` text,
          `created_at` datetime DEFAULT CURRENT_TIMESTAMP,
          PRIMARY KEY (`id`)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
        ";
        
        // 为新租户创建自定义表
        $tenants = appmodelTenant::where('status', 1)->select();
        foreach ($tenants as $tenant) {
            $customSql = str_replace('{TENANT_ID}', $tenant->id, $sql);
            thinkfacadeDb::execute($customSql);
        }
    }
}
    

七、性能优化策略

7.1 数据库连接池配置


// database.php
return [
    'default' => 'mysql',
    'connections' => [
        'mysql' => [
            'type' => 'mysql',
            'hostname' => '127.0.0.1',
            'database' => 'saas_main',
            'username' => 'root',
            'password' => '',
            'hostport' => '3306',
            'params' => [
                PDO::ATTR_PERSISTENT => true, // 持久连接
                PDO::ATTR_TIMEOUT => 3
            ],
            'charset' => 'utf8mb4',
            'deploy' => 0,
            'rw_separate' => true, // 读写分离
            'master_num' => 1,
            'slave_no' => '',
            'fields_strict' => true,
        ]
    ]
];
    

7.2 缓存策略设计


namespace appservice;

class TenantCacheService
{
    protected $prefix = 'tenant:';
    
    public function remember($key, $callback, $ttl = 3600)
    {
        $fullKey = $this->prefix . TenantContext::getId() . ':' . $key;
        
        $data = thinkfacadeCache::get($fullKey);
        if (!$data) {
            $data = $callback();
            thinkfacadeCache::set($fullKey, $data, $ttl);
        }
        
        return $data;
    }
    
    public function clear($pattern = null)
    {
        $key = $this->prefix . TenantContext::getId();
        if ($pattern) {
            $key .= ':' . $pattern;
        }
        
        thinkfacadeCache::delete($key . '*');
    }
}
    

八、安全防护措施

8.1 数据越权访问防护


namespace appmiddleware;

class DataPermissionMiddleware
{
    public function handle($request, Closure $next)
    {
        $response = $next($request);
        
        // 验证返回数据是否属于当前租户
        if ($response->getData() instanceof thinkCollection) {
            foreach ($response->getData() as $item) {
                if (isset($item->tenant_id) && $item->tenant_id != appfacadeTenantContext::getId()) {
                    throw new thinkexceptionHttpException(403, '无权访问该数据');
                }
            }
        }
        
        return $response;
    }
}
    

九、部署与监控

9.1 系统监控指标

  • 租户数量增长趋势
  • API请求成功率
  • 数据库连接池使用率
  • 各租户资源消耗统计

9.2 日志记录策略


// 在全局中间件中记录租户操作日志
class OperationLogMiddleware
{
    public function handle($request, Closure $next)
    {
        $startTime = microtime(true);
        
        $response = $next($request);
        
        // 记录操作日志
        appmodelOperationLog::create([
            'tenant_id' => appfacadeTenantContext::getId(),
            'user_id' => $request->user->id ?? 0,
            'method' => $request->method(),
            'path' => $request->pathinfo(),
            'ip' => $request->ip(),
            'user_agent' => $request->header('user-agent'),
            'response_time' => round((microtime(true) - $startTime) * 1000, 2),
            'created_at' => date('Y-m-d H:i:s')
        ]);
        
        return $response;
    }
}
    

十、总结

本文详细介绍了基于ThinkPHP 6.0的多租户SaaS系统完整架构设计与实现。通过中间件、模型基类、服务层等多层次设计,实现了高效的数据隔离和租户管理。这种架构方案具有良好的扩展性和维护性,能够支撑企业级SaaS应用的快速发展需求。

在实际项目中,还需要考虑租户数据备份恢复、系统升级策略、性能监控告警等更多运维层面的问题。希望本文能为您的ThinkPHP多租户系统开发提供有价值的参考。

ThinkPHP 6.0多租户SaaS系统架构设计与实现 - 完整实战指南
收藏 (0) 打赏

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

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

淘吗网 thinkphp ThinkPHP 6.0多租户SaaS系统架构设计与实现 – 完整实战指南 https://www.taomawang.com/server/thinkphp/1170.html

常见问题

相关文章

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

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