免费资源下载
一、项目背景与技术选型
在现代企业级应用开发中,SaaS(软件即服务)模式越来越普及,多租户架构成为必备能力。本文将通过一个真实的电商SaaS平台案例,展示如何使用ThinkPHP 6.0构建高性能API服务,并实现三种不同级别的数据隔离方案。
技术栈特色:
- 框架核心:ThinkPHP 6.0.12 + PHP 7.4
- 数据库:MySQL 8.0 + Redis 6.0
- API规范:RESTful + JWT认证
- 部署环境:Docker + Nginx
二、系统架构设计
2.1 分层架构设计
应用层结构:
app/
├── controller/ # 控制器层
├── service/ # 业务服务层(新增)
├── repository/ # 数据仓库层(新增)
├── model/ # 模型层
├── middleware/ # 中间件层
└── lib/ # 自定义类库
2.2 多租户数据隔离模式对比
| 模式 | 数据库方案 | 隔离级别 | 适用场景 |
|---|---|---|---|
| 独立数据库 | 每个租户独立数据库 | 最高 | 大型企业客户 |
| 共享数据库独立Schema | 每个租户独立数据表 | 高 | 中型客户 |
| 共享数据表 | 通过tenant_id字段区分 | 中 | 中小型客户 |
三、核心代码实现
3.1 多租户中间件实现
<?php
namespace appmiddleware;
class TenantMiddleware
{
public function handle($request, Closure $next)
{
// 从JWT Token或子域名获取租户标识
$tenantId = $this->getTenantId($request);
if (!$tenantId) {
return json(['code' => 401, 'msg' => '租户标识缺失']);
}
// 设置租户上下文
app('tenant')->setId($tenantId);
// 动态切换数据库连接
$this->switchDatabase($tenantId);
return $next($request);
}
private function getTenantId($request)
{
// 方案1:从子域名获取(saas.tenant1.example.com)
$subdomain = explode('.', $request->host())[0];
// 方案2:从JWT Token获取
$token = $request->header('Authorization');
// 方案3:从请求头获取
$headerTenant = $request->header('X-Tenant-Id');
return $headerTenant ?: $subdomain;
}
private function switchDatabase($tenantId)
{
// 根据租户配置切换数据库
$config = config("tenant.{$tenantId}");
if ($config && $config['database']) {
Config::set([
'connections.tenant' => $config['database']
], 'database');
// 设置全局数据库连接
Db::connect('tenant');
}
}
}
3.2 服务层抽象基类
<?php
namespace appservice;
abstract class BaseService
{
protected $repository;
protected $tenantId;
public function __construct()
{
$this->tenantId = app('tenant')->getId();
$this->initialize();
}
abstract protected function initialize();
/**
* 自动注入租户ID
*/
protected function injectTenantId(&$data)
{
if (!isset($data['tenant_id'])) {
$data['tenant_id'] = $this->tenantId;
}
}
/**
* 统一响应格式
*/
protected function success($data = [], $message = '操作成功')
{
return [
'code' => 200,
'message' => $message,
'data' => $data,
'tenant_id' => $this->tenantId,
'timestamp' => time()
];
}
}
3.3 商品服务具体实现
<?php
namespace appserviceproduct;
use appserviceBaseService;
use apprepositoryProductRepository;
class ProductService extends BaseService
{
protected $repository;
protected function initialize()
{
$this->repository = new ProductRepository();
}
/**
* 创建商品(自动处理租户隔离)
*/
public function createProduct(array $data)
{
// 自动注入租户ID
$this->injectTenantId($data);
// 数据验证
$validate = new appvalidateProduct();
if (!$validate->check($data)) {
throw new Exception($validate->getError());
}
// 使用事务保证数据一致性
Db::startTrans();
try {
// 创建商品
$product = $this->repository->create($data);
// 记录操作日志
$this->logProductOperation($product->id, 'create');
Db::commit();
return $this->success($product);
} catch (Exception $e) {
Db::rollback();
throw $e;
}
}
/**
* 获取租户商品列表(自动过滤)
*/
public function getProducts(int $page = 1, int $size = 20, array $filters = [])
{
// 自动添加租户查询条件
$filters['tenant_id'] = $this->tenantId;
// 使用缓存提高性能
$cacheKey = "products:{$this->tenantId}:{$page}:{$size}:" . md5(json_encode($filters));
return cache($cacheKey, function() use ($page, $size, $filters) {
return $this->repository->paginate(
$filters,
$page,
$size,
['id', 'name', 'price', 'stock', 'created_at'],
['created_at' => 'desc']
);
}, 300); // 缓存5分钟
}
}
四、高级特性实现
4.1 数据库连接池优化
<?php
// config/database.php 配置优化
return [
'default' => 'mysql',
'connections' => [
'mysql' => [
'type' => 'mysql',
'hostname' => env('DB_HOST', '127.0.0.1'),
'database' => env('DB_NAME', ''),
'username' => env('DB_USER', ''),
'password' => env('DB_PASSWORD', ''),
'hostport' => env('DB_PORT', '3306'),
'charset' => 'utf8mb4',
'deploy' => 1, // 启用分布式
'rw_separate' => true, // 读写分离
'master_num' => 2, // 主服务器数量
'slave_no' => '2,3', // 从服务器编号
'break_reconnect' => true, // 断线重连
'break_match_str' => [ // 断线匹配字符串
'server has gone away',
'no connection to the server'
],
'fields_cache' => true, // 字段缓存
'trigger_sql' => true, // 触发SQL日志
'auto_timestamp' => true, // 自动时间戳
'query_cache' => [ // 查询缓存配置
'enable' => true,
'expire' => 3600,
'prefix' => 'db_cache:'
]
]
],
// 连接池配置(需要安装swoole扩展)
'pool' => [
'max_connections' => 100,
'min_connections' => 10,
'max_idle_time' => 3600,
'acquire_timeout' => 3.0
]
];
4.2 API限流与安全防护
<?php
namespace appmiddleware;
class RateLimitMiddleware
{
protected $redis;
public function __construct()
{
$this->redis = thinkfacadeCache::store('redis')->handler();
}
public function handle($request, Closure $next)
{
$key = 'rate_limit:' . $request->ip() . ':' . $request->path();
$tenantKey = 'rate_limit:tenant:' . app('tenant')->getId() . ':' . $request->path();
// IP级别限流:100次/分钟
if (!$this->checkRate($key, 100, 60)) {
return json(['code' => 429, 'msg' => '请求过于频繁']);
}
// 租户级别限流:1000次/分钟
if (!$this->checkRate($tenantKey, 1000, 60)) {
return json(['code' => 429, 'msg' => '租户API调用超限']);
}
// SQL注入防护
$this->sqlInjectionProtection($request);
// XSS防护
$this->xssProtection($request);
return $next($request);
}
private function checkRate($key, $limit, $period)
{
$current = $this->redis->get($key);
if ($current && $current >= $limit) {
return false;
}
$this->redis->multi();
$this->redis->incr($key);
$this->redis->expire($key, $period);
$this->redis->exec();
return true;
}
}
五、部署与监控
5.1 Docker部署配置
# docker-compose.yml
version: '3.8'
services:
app:
build:
context: .
dockerfile: Dockerfile
container_name: thinkphp-saas
restart: unless-stopped
networks:
- saas-network
volumes:
- ./app:/var/www/html/app
- ./runtime:/var/www/html/runtime
- ./logs:/var/www/html/logs
environment:
- APP_DEBUG=${APP_DEBUG}
- DB_HOST=mysql
- REDIS_HOST=redis
depends_on:
- mysql
- redis
mysql:
image: mysql:8.0
container_name: saas-mysql
environment:
MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
MYSQL_DATABASE: ${DB_NAME}
volumes:
- mysql-data:/var/lib/mysql
- ./docker/mysql/init.sql:/docker-entrypoint-initdb.d/init.sql
networks:
- saas-network
redis:
image: redis:6-alpine
container_name: saas-redis
command: redis-server --appendonly yes
volumes:
- redis-data:/data
networks:
- saas-network
nginx:
image: nginx:1.21-alpine
container_name: saas-nginx
ports:
- "80:80"
- "443:443"
volumes:
- ./docker/nginx/conf.d:/etc/nginx/conf.d
- ./public:/var/www/html/public
networks:
- saas-network
depends_on:
- app
networks:
saas-network:
driver: bridge
volumes:
mysql-data:
redis-data:
5.2 性能监控配置
<?php
// 应用性能监控
namespace applibmonitor;
class PerformanceMonitor
{
public static function start()
{
// 记录开始时间
define('APP_START_TIME', microtime(true));
define('APP_START_MEMORY', memory_get_usage());
}
public static function end()
{
$endTime = microtime(true);
$endMemory = memory_get_usage();
$peakMemory = memory_get_peak_usage();
$data = [
'execution_time' => round(($endTime - APP_START_TIME) * 1000, 2) . 'ms',
'memory_usage' => self::formatBytes($endMemory - APP_START_MEMORY),
'peak_memory' => self::formatBytes($peakMemory),
'sql_queries' => Db::getQueryTimes(),
'cache_hits' => Cache::getHitTimes(),
'tenant_id' => app('tenant')->getId() ?? 'unknown'
];
// 记录到日志
Log::write(json_encode($data), 'performance');
// 慢查询报警(执行时间超过1秒)
if (($endTime - APP_START_TIME) > 1) {
self::alertSlowRequest($data);
}
}
private static function formatBytes($bytes)
{
$units = ['B', 'KB', 'MB', 'GB'];
$i = 0;
while ($bytes >= 1024 && $i < count($units) - 1) {
$bytes /= 1024;
$i++;
}
return round($bytes, 2) . ' ' . $units[$i];
}
}
六、总结与最佳实践
6.1 关键技术要点
- 中间件优先:通过中间件实现租户识别和数据库切换,保持代码整洁
- 服务层抽象:业务逻辑集中在Service层,提高代码复用性
- 仓库模式:数据访问通过Repository统一管理,便于缓存和优化
- 配置驱动:多租户配置外部化,支持动态扩展
6.2 性能优化建议
- 使用OPcache加速PHP执行
- 数据库查询结果合理缓存
- 静态资源CDN加速
- API响应启用Gzip压缩
- 定期清理无用缓存和数据
6.3 安全注意事项
- 租户数据严格隔离,避免越权访问
- 敏感操作记录完整日志
- API接口实施限流和防刷策略
- 定期进行安全漏洞扫描

