ThinkPHP多租户SaaS平台架构设计与实现 | 完整开发指南

2025-11-12 0 792

SaaS架构概述与ThinkPHP优势

在云计算时代,多租户SaaS(Software as a Service)平台已成为企业软件交付的主流模式。本文将深入探讨如何使用ThinkPHP 6.1构建一个高性能、可扩展的多租户SaaS平台,实现数据隔离、租户管理和系统扩展的全套解决方案。

系统架构设计

我们的SaaS平台采用分层架构设计,包含以下核心模块:

  • 租户数据隔离引擎
  • 动态数据库路由系统
  • 租户生命周期管理
  • 统一认证授权中心
  • 模块化业务组件

环境配置与项目初始化

Composer项目创建

# 创建ThinkPHP 6.1项目
composer create-project topthink/think saas-platform

# 安装多租户扩展
composer require hhxsv5/laravel-s

# 项目目录结构
app/
├── controller/
├── model/
├── middleware/
├── service/
├── common.php
config/
├── database.php
├── tenant.php
public/
routes/
tenant/
├── database/
└── migrations/

多租户配置文件

// config/tenant.php
<?php
return [
    // 租户识别方式
    'identification' => [
        'type' => 'subdomain', // subdomain, header, query
        'field' => 'tenant_id',
    ],
    
    // 数据库连接策略
    'database' => [
        'strategy' => 'database_per_tenant', // shared, database_per_tenant
        'prefix' => 'tenant_',
    ],
    
    // 租户生命周期
    'lifecycle' => [
        'trial_days' => 30,
        'grace_period' => 7,
    ],
];

核心中间件实现

租户识别中间件

<?php
namespace appmiddleware;

class TenantIdentification
{
    public function handle($request, Closure $next)
    {
        // 从子域名识别租户
        $host = $request->host();
        $subdomain = $this->extractSubdomain($host);
        
        if ($subdomain) {
            $tenant = appmodelTenant::where('subdomain', $subdomain)->find();
            
            if (!$tenant) {
                return json(['error' => '租户不存在'], 404);
            }
            
            if (!$tenant->is_active) {
                return json(['error' => '租户已被停用'], 403);
            }
            
            // 设置当前租户上下文
            app()->bind('tenant', $tenant);
            $request->tenant = $tenant;
        }
        
        return $next($request);
    }
    
    private function extractSubdomain($host)
    {
        $parts = explode('.', $host);
        if (count($parts) > 2) {
            return $parts[0];
        }
        return null;
    }
}

数据库路由中间件

<?php
namespace appmiddleware;

class DatabaseRouter
{
    public function handle($request, Closure $next)
    {
        $tenant = $request->tenant;
        
        if ($tenant) {
            // 动态切换数据库连接
            $this->switchDatabase($tenant);
        }
        
        return $next($request);
    }
    
    private function switchDatabase($tenant)
    {
        $config = [
            // 默认数据库配置
            'type'      => 'mysql',
            'hostname'  => env('database.hostname', '127.0.0.1'),
            'database'  => 'tenant_' . $tenant->id,
            'username'  => env('database.username', 'root'),
            'password'  => env('database.password', ''),
            'hostport'  => env('database.hostport', '3306'),
            'charset'   => 'utf8mb4',
            'prefix'    => '',
        ];
        
        // 设置租户数据库连接
        thinkfacadeDb::connect($config, 'tenant');
    }
}

数据模型设计

租户主模型

<?php
namespace appmodel;

use thinkModel;

class Tenant extends Model
{
    protected $autoWriteTimestamp = true;
    
    // 定义租户状态
    const STATUS_ACTIVE = 1;
    const STATUS_SUSPENDED = 0;
    const STATUS_TRIAL = 2;
    
    public function users()
    {
        return $this->hasMany(User::class);
    }
    
    public function subscriptions()
    {
        return $this->hasMany(Subscription::class);
    }
    
    public function getCurrentSubscriptionAttribute()
    {
        return $this->subscriptions()
            ->where('status', Subscription::STATUS_ACTIVE)
            ->order('id', 'desc')
            ->find();
    }
    
    public function scopeActive($query)
    {
        return $query->where('is_active', self::STATUS_ACTIVE);
    }
    
    public function isOnTrial()
    {
        return $this->trial_ends_at && $this->trial_ends_at > time();
    }
}

租户感知的基础模型

<?php
namespace appmodel;

use thinkModel;

class TenantAwareModel extends Model
{
    protected $tenantField = 'tenant_id';
    
    protected static function onBeforeInsert(Model $model)
    {
        $tenant = app('tenant');
        if ($tenant) {
            $model->setAttr('tenant_id', $tenant->id);
        }
    }
    
    public function scopeForTenant($query, $tenantId = null)
    {
        $tenantId = $tenantId ?: (app('tenant')->id ?? null);
        if ($tenantId) {
            return $query->where('tenant_id', $tenantId);
        }
        return $query;
    }
    
    public function tenant()
    {
        return $this->belongsTo(Tenant::class);
    }
}

服务层架构

租户管理服务

<?php
namespace appservice;

use appmodelTenant;
use thinkfacadeDb;

class TenantService
{
    public function createTenant($data)
    {
        Db::startTrans();
        try {
            // 创建租户记录
            $tenant = Tenant::create([
                'name' => $data['name'],
                'subdomain' => $data['subdomain'],
                'email' => $data['email'],
                'trial_ends_at' => strtotime('+30 days'),
            ]);
            
            // 创建租户数据库
            $this->createTenantDatabase($tenant);
            
            // 执行租户数据库迁移
            $this->runTenantMigrations($tenant);
            
            // 创建默认管理员
            $this->createDefaultAdmin($tenant, $data['admin_password']);
            
            Db::commit();
            return $tenant;
            
        } catch (Exception $e) {
            Db::rollback();
            throw $e;
        }
    }
    
    private function createTenantDatabase(Tenant $tenant)
    {
        $databaseName = 'tenant_' . $tenant->id;
        
        $sql = "CREATE DATABASE IF NOT EXISTS `{$databaseName}` 
                CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci";
                
        Db::execute($sql);
    }
    
    private function runTenantMigrations(Tenant $tenant)
    {
        // 切换到租户数据库执行迁移
        $originalConfig = Db::getConfig();
        
        try {
            $this->switchToTenantDatabase($tenant);
            
            // 执行租户级别的数据表创建
            $migrations = [
                'create_users_table',
                'create_products_table',
                'create_orders_table',
                // ... 其他业务表
            ];
            
            foreach ($migrations as $migration) {
                $this->runMigration($migration);
            }
            
        } finally {
            // 恢复默认数据库连接
            Db::connect($originalConfig);
        }
    }
}

控制器实现

租户注册控制器

<?php
namespace appcontroller;

use appserviceTenantService;
use thinkfacadeValidate;

class TenantController
{
    protected $tenantService;
    
    public function __construct(TenantService $tenantService)
    {
        $this->tenantService = $tenantService;
    }
    
    public function register()
    {
        $data = request()->post();
        
        // 验证输入
        $validate = Validate::rule([
            'name' => 'require|max:100',
            'subdomain' => 'require|alphaDash|unique:tenant,subdomain',
            'email' => 'require|email',
            'admin_password' => 'require|min:6',
        ]);
        
        if (!$validate->check($data)) {
            return json(['error' => $validate->getError()], 422);
        }
        
        try {
            $tenant = $this->tenantService->createTenant($data);
            
            return json([
                'message' => '租户注册成功',
                'tenant' => $tenant->toArray(),
                'login_url' => 'http://' . $tenant->subdomain . '.' . request()->rootDomain()
            ]);
            
        } catch (Exception $e) {
            return json(['error' => '注册失败: ' . $e->getMessage()], 500);
        }
    }
    
    public function updateSubscription()
    {
        $tenant = app('tenant');
        $plan = request()->post('plan');
        
        try {
            $subscription = $this->tenantService
                ->updateSubscription($tenant, $plan);
                
            return json([
                'message' => '订阅更新成功',
                'subscription' => $subscription
            ]);
            
        } catch (Exception $e) {
            return json(['error' => $e->getMessage()], 500);
        }
    }
}

业务数据控制器

<?php
namespace appcontroller;

use appmodelProduct;

class ProductController
{
    public function index()
    {
        $page = request()->param('page', 1);
        $limit = request()->param('limit', 15);
        
        $products = Product::forTenant()
            ->with(['category'])
            ->order('id', 'desc')
            ->paginate([
                'list_rows' => $limit,
                'page' => $page,
            ]);
            
        return json([
            'data' => $products->items(),
            'total' => $products->total(),
            'current_page' => $products->currentPage(),
        ]);
    }
    
    public function create()
    {
        $data = request()->post();
        
        $product = Product::create($data);
        
        return json([
            'message' => '产品创建成功',
            'product' => $product
        ]);
    }
}

数据库迁移与种子数据

系统表迁移

<?php
// database/migrations/20240101000000_create_tenants_table.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('email', 'string', ['limit' => 255])
            ->addColumn('is_active', 'boolean', ['default' => true])
            ->addColumn('trial_ends_at', 'integer', ['null' => true])
            ->addColumn('created_at', 'integer')
            ->addColumn('updated_at', 'integer')
            ->addIndex(['subdomain'], ['unique' => true])
            ->create();
    }
}

租户业务表迁移

<?php
// tenant/migrations/20240101000001_create_products_table.php
class CreateProductsTable extends Migrator
{
    public function change()
    {
        $table = $this->table('products');
        $table->addColumn('tenant_id', 'integer')
            ->addColumn('name', 'string', ['limit' => 100])
            ->addColumn('description', 'text', ['null' => true])
            ->addColumn('price', 'decimal', ['precision' => 10, 'scale' => 2])
            ->addColumn('stock', 'integer', ['default' => 0])
            ->addColumn('is_active', 'boolean', ['default' => true])
            ->addColumn('created_at', 'integer')
            ->addColumn('updated_at', 'integer')
            ->addIndex(['tenant_id'])
            ->create();
    }
}

路由配置

多租户路由定义

<?php
// route/tenant.php
use thinkfacadeRoute;

// 租户子域名路由组
Route::domain(':subdomain', function () {
    
    // 租户认证路由
    Route::group('auth', function () {
        Route::post('login', 'tenant/Auth/login');
        Route::post('logout', 'tenant/Auth/logout');
        Route::post('refresh', 'tenant/Auth/refresh');
    });
    
    // 租户业务路由
    Route::group('api', function () {
        Route::get('products', 'tenant/Product/index');
        Route::post('products', 'tenant/Product/create');
        Route::put('products/:id', 'tenant/Product/update');
        Route::delete('products/:id', 'tenant/Product/delete');
        
        Route::get('orders', 'tenant/Order/index');
        Route::post('orders', 'tenant/Order/create');
    })->middleware(['appmiddlewareTenantJwtAuth']);
    
})->middleware(['appmiddlewareTenantIdentification']);

// 系统管理路由
Route::group('system', function () {
    Route::post('tenants/register', 'Tenant/register');
    Route::put('tenants/:id', 'Tenant/update');
    Route::get('tenants', 'Tenant/index');
})->middleware(['appmiddlewareSystemAuth']);

事件与监听器

租户事件定义

<?php
namespace appevent;

class TenantCreated
{
    public $tenant;
    
    public function __construct($tenant)
    {
        $this->tenant = $tenant;
    }
}

class TenantSuspended
{
    public $tenant;
    
    public function __construct($tenant)
    {
        $this->tenant = $tenant;
    }
}

// 事件监听器
class SendTenantWelcomeNotification
{
    public function handle(TenantCreated $event)
    {
        $tenant = $event->tenant;
        
        // 发送欢迎邮件
        $mailer = new appserviceMailService();
        $mailer->sendWelcomeEmail($tenant);
        
        // 创建初始数据
        $seeder = new appserviceTenantSeeder();
        $seeder->seedInitialData($tenant);
    }
}

// 事件注册
// app/event.php
return [
    'listen' => [
        'TenantCreated' => [
            'applistenerSendTenantWelcomeNotification',
            'applistenerCreateInitialData',
        ],
        'TenantSuspended' => [
            'applistenerNotifyTenantSuspension',
        ],
    ],
];

性能优化策略

  • 数据库连接池:使用连接池管理租户数据库连接
  • Redis缓存:缓存租户配置和频繁访问的数据
  • 查询优化:为租户数据表添加合适索引
  • 懒加载:按需加载租户数据库连接
  • CDN加速:静态资源使用CDN分发

安全防护措施

  • 数据隔离:确保租户间数据完全隔离
  • SQL注入防护:使用参数绑定和ORM
  • XSS防护:输出数据自动转义
  • CSRF保护:表单请求令牌验证
  • 速率限制:API访问频率控制

部署与监控

Docker部署配置

# docker-compose.yml
version: '3.8'
services:
  app:
    build: .
    ports:
      - "80:80"
    environment:
      - DB_HOST=mysql
      - REDIS_HOST=redis
    depends_on:
      - mysql
      - redis
  
  mysql:
    image: mysql:8.0
    environment:
      - MYSQL_ROOT_PASSWORD=secret
      - MYSQL_DATABASE=saas_platform
    volumes:
      - mysql_data:/var/lib/mysql
  
  redis:
    image: redis:alpine

volumes:
  mysql_data:

总结与最佳实践

通过本教程,我们构建了一个基于ThinkPHP 6.1的完整多租户SaaS平台。这个架构展示了如何实现数据隔离、租户管理和系统扩展的核心功能,为构建企业级SaaS应用提供了可靠的技术基础。

核心技术创新:

  • 动态数据库路由实现数据隔离
  • 中间件链完成租户识别和路由
  • 服务层封装复杂业务逻辑
  • 事件系统处理租户生命周期
  • 模块化设计支持功能扩展

这种架构设计不仅保证了系统的安全性和性能,还为未来的功能扩展和维护提供了良好的基础,是构建现代SaaS平台的理想技术选择。

ThinkPHP多租户SaaS平台架构设计与实现 | 完整开发指南
收藏 (0) 打赏

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

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

淘吗网 thinkphp ThinkPHP多租户SaaS平台架构设计与实现 | 完整开发指南 https://www.taomawang.com/server/thinkphp/1417.html

常见问题

相关文章

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

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