PHP 怎么PHP SaaS 启动器

wen PHP项目 3

本文目录导读:

PHP 怎么PHP SaaS 启动器

  1. 架构选择
  2. 多租户架构实现
  3. 三种多租户策略
  4. 路由和中间件
  5. 认证和授权
  6. 自动化部署
  7. 租户注册流程
  8. 高级功能
  9. 配置文件
  10. 监控系统
  11. 部署和管理
  12. 最佳实践建议

我来详细介绍PHP SaaS启动器的搭建方案:

架构选择

基础架构

// 项目结构
saas-starter/
├── app/
│   ├── Core/
│   │   ├── Database.php
│   │   ├── Tenant.php
│   │   └── Router.php
│   ├── Models/
│   ├── Controllers/
│   └── Services/
├── config/
├── database/
├── public/
└── tenant/

多租户架构实现

数据库配置

// app/Core/Database.php
class Database {
    private static $connections = [];
    public static function getConnection($tenantId = null) {
        if ($tenantId) {
            if (!isset(self::$connections[$tenantId])) {
                $config = Tenant::getDatabaseConfig($tenantId);
                self::$connections[$tenantId] = new PDO(
                    "mysql:host={$config['host']};dbname={$config['dbname']}",
                    $config['username'],
                    $config['password']
                );
            }
            return self::$connections[$tenantId];
        }
        return self::getDefaultConnection();
    }
}

租户管理类

// app/Core/Tenant.php
class Tenant {
    private $id;
    private $domain;
    private $config;
    public function __construct() {
        $this->identify();
    }
    private function identify() {
        // 1. 通过域名识别
        $domain = $_SERVER['HTTP_HOST'];
        // 2. 查询租户信息
        $pdo = Database::getDefaultConnection();
        $stmt = $pdo->prepare("SELECT * FROM tenants WHERE domain = ?");
        $stmt->execute([$domain]);
        $tenant = $stmt->fetch(PDO::FETCH_ASSOC);
        if ($tenant) {
            $this->id = $tenant['id'];
            $this->config = $tenant;
        }
    }
    public function getDatabaseName() {
        return "tenant_{$this->id}";
    }
    public function getTablePrefix() {
        return "t{$this->id}_";
    }
}

三种多租户策略

单数据库单Schema

// 方式一:每个租户独立数据库
class TenantDatabase {
    public function createSchema($tenantId) {
        $dbname = "tenant_{$tenantId}";
        $pdo->exec("CREATE DATABASE IF NOT EXISTS {$dbname}");
        // 运行迁移脚本
        $this->runMigrations($dbname);
    }
}

共享数据库独立Schema

// 方式二:共享数据库,独立Schema
class SharedDatabase {
    public function createSchema($tenantId) {
        $schema = "tenant_{$tenantId}";
        $pdo->exec("CREATE SCHEMA IF NOT EXISTS {$schema}");
        $pdo->exec("CREATE TABLE {$schema}.users (id INT, name VARCHAR(100))");
    }
}

共享表和租户ID

// 方式三:共享表,使用租户ID区分
class SharedTable {
    private $tenantId;
    public function query($sql, $params = []) {
        $sql = str_replace('{{tenant_id}}', $this->tenantId, $sql);
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute($params);
        return $stmt;
    }
    public function getUsers() {
        return $this->query("SELECT * FROM users WHERE tenant_id = {{tenant_id}}");
    }
}

路由和中间件

// app/Core/Router.php
class Router {
    private static $routes = [];
    public static function add($method, $path, $handler, $middleware = []) {
        self::$routes[] = [
            'method' => $method,
            'path' => $path,
            'handler' => $handler,
            'middleware' => $middleware
        ];
    }
    public static function dispatch() {
        $uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
        $method = $_SERVER['REQUEST_METHOD'];
        foreach (self::$routes as $route) {
            if ($route['method'] === $method && preg_match("#^{$route['path']}$#", $uri, $matches)) {
                // 执行中间件
                foreach ($route['middleware'] as $middleware) {
                    $result = $middleware();
                    if ($result) continue;
                    return;
                }
                // 调用控制器
                return call_user_func_array($route['handler'], $matches);
            }
        }
    }
}
// 使用
Router::add('GET', '/users', 'UserController@index', [
    'auth',
    'tenant.check'
]);

认证和授权

// app/Services/AuthService.php
class AuthService {
    public function login($email, $password, $tenantId) {
        // 检查用户存在
        $pdo = Database::getConnection($tenantId);
        $stmt = $pdo->prepare("SELECT * FROM users WHERE email = ?");
        $stmt->execute([$email]);
        $user = $stmt->fetch();
        // 验证密码
        if (password_verify($password, $user['password'])) {
            $_SESSION['user_id'] = $user['id'];
            $_SESSION['tenant_id'] = $tenantId;
            // 生成JWT token
            $token = $this->generateJWT($user);
            return $token;
        }
        return false;
    }
    public function check() {
        return isset($_SESSION['user_id']);
    }
}

自动化部署

#!/bin/bash
# scripts/deploy.sh
# 1. 创建租户目录
if [ ! -d "tenant/$TENANT_ID" ]; then
    mkdir -p "tenant/$TENANT_ID"
    # 2. 复制公共文件
    cp -r public/ tenant/$TENANT_ID/
    # 3. 修改配置文件
    sed -i "s/TENANT_ID/$TENANT_ID/" tenant/$TENANT_ID/config.php
    # 4. 创建数据库
    mysql -u root -p -e "CREATE DATABASE tenant_$TENANT_ID"
fi
# 5. 运行迁移
php artisan migrate --tenant=$TENANT_ID

租户注册流程

// app/Controllers/TenantController.php
class TenantController {
    public function register() {
        // 1. 验证数据
        $data = $this->validate();
        // 2. 创建租户记录
        $tenantId = $this->createTenant($data);
        // 3. 创建数据库/Schema
        $this->createDatabase($tenantId);
        // 4. 运行迁移
        $this->runMigrations($tenantId);
        // 5. 创建管理员账号
        $this->createAdminUser($tenantId, $data);
        // 6. 配置域名
        $this->setupDomain($tenantId, $data['domain']);
        return redirect("/login?tenant={$tenantId}");
    }
}

高级功能

队列处理

// app/Services/QueueService.php
class QueueService {
    public function dispatch($job, $data, $tenantId) {
        // 使用Redis队列
        $this->redis->lpush("tenant:{$tenantId}:jobs", json_encode([
            'job' => $job,
            'data' => $data
        ]));
    }
    public function process($tenantId) {
        while ($job = $this->redis->rpop("tenant:{$tenantId}:jobs")) {
            $jobData = json_decode($job, true);
            $this->execute($jobData['job'], $jobData['data']);
        }
    }
}

缓存管理

// app/Services/CacheService.php
class CacheService {
    private $prefix;
    public function __construct($tenantId) {
        $this->prefix = "tenant:{$tenantId}:";
    }
    public function get($key) {
        return $this->redis->get($this->prefix . $key);
    }
    public function set($key, $value, $ttl = null) {
        $key = $this->prefix . $key;
        if ($ttl) {
            return $this->redis->setex($key, $ttl, $value);
        }
        return $this->redis->set($key, $value);
    }
}

配置文件

// config/tenant.php
return [
    'mode' => 'multi_db', // multi_db | shared_schema | shared_table
    'database_driver' => 'mysql',
    'auto_create_database' => true,
    'auto_run_migrations' => true,
    'cors' => [
        'allowed_origins' => ['*'],
        'allowed_methods' => ['GET', 'POST', 'PUT', 'DELETE']
    ],
    'limits' => [
        'max_users' => 100,
        'max_storage' => '1GB',
        'max_domains' => 3
    ],
    'features' => [
        'api_access' => true,
        'white_label' => false,
        'advanced_reporting' => false
    ]
];

监控系统

// app/Services/MonitorService.php
class MonitorService {
    public function track($tenantId, $action) {
        // 记录租户活动
        $data = [
            'tenant_id' => $tenantId,
            'action' => $action,
            'time' => date('Y-m-d H:i:s'),
            'ip' => $_SERVER['REMOTE_ADDR']
        ];
        $this->logToDatabase($data);
    }
    public function getUsage($tenantId) {
        return [
            'database_size' => $this->getDatabaseSize($tenantId),
            'api_calls' => $this->getApiCalls($tenantId),
            'users' => $this->getUserCount($tenantId),
            'storage' => $this->getStorageUsage($tenantId)
        ];
    }
}

部署和管理

# 管理命令
php artisan tenant:create --name=company1 --domain=company1.example.com
php artisan tenant:migrate --id=1
php artisan tenant:seed --id=1 --role=admin
php artisan tenant:backup --id=1
php artisan tenant:restore --id=1 --backup=2023-01-01

最佳实践建议

  1. 隔离性:确保租户数据完全隔离,包括数据库、缓存、文件系统
  2. 可扩展性:设计时考虑水平扩展,如分库分表
  3. 安全性:使用SSL、密码哈希、SQL注入防护
  4. 性能优化:使用缓存、数据库索引、查询优化
  5. 监控告警:实施租户资源使用监控和告警机制
  6. 计费系统:集成计费、订阅管理、用量统计

这个PHP SaaS启动器提供了完整的多租户支持,可以根据实际需求选择合适的架构方案。

抱歉,评论功能暂时关闭!