本文目录导读:

我来详细介绍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
最佳实践建议
- 隔离性:确保租户数据完全隔离,包括数据库、缓存、文件系统
- 可扩展性:设计时考虑水平扩展,如分库分表
- 安全性:使用SSL、密码哈希、SQL注入防护
- 性能优化:使用缓存、数据库索引、查询优化
- 监控告警:实施租户资源使用监控和告警机制
- 计费系统:集成计费、订阅管理、用量统计
这个PHP SaaS启动器提供了完整的多租户支持,可以根据实际需求选择合适的架构方案。