thinkphp实现saas泛域名需入口解析子域名、中间件注入租户标识、动态切换数据库与配置;nginx透传$subdomain,tp6不支持domain通配,须绕过静态绑定走动态初始化链路。

ThinkPHP 实现 SaaS 平台的泛域名解析与动态绑定,关键不在“写死规则”,而在于让框架在请求早期就识别出租户标识,并据此切换应用上下文、数据库、配置和路由。TP6 不支持 Route::domain('*') 直接通配,TP5.1 的 domain_bind.php 也不支持运行时计算——所以必须绕过静态绑定,走「入口解析 + 中间件注入 + 动态初始化」这条链路。
一、Nginx 正确传递子域名信息
泛域名(*.example.com)必须在 Nginx 层级就完成识别并透传,否则 PHP 根本收不到原始子域名。
- 在
http块中添加map指令提取子域名:
~^(?[a-z0-9\-]+)\.example\.com$ $sub;
default "";
}
- 在对应
server块中透传给 PHP-FPM:
fastcgi_param SCRIPT_FILENAME $document_root/index.php;
-
server_name必须包含泛解析和主域名:server_name *.example.com example.com; - 确保 SSL 证书为通配符证书(
*.example.com),否则 HTTPS 下子域名会直接被浏览器拦截
二、入口层提前解析并注入租户标识
不要依赖框架自动识别——TP 默认不解析泛域名中的变量部分。必须在 public/index.php 最顶部手动提取并挂载。
- 读取
$_SERVER['HTTP_X_SUBDOMAIN'](优先)或回退到$_SERVER['HTTP_HOST'] - 过滤保留字(如
www、api、admin),只允许小写字母、数字和短横线 - 存入全局变量或 Request 对象,供后续使用:
if ($sub && preg_match('/^[a-z0-9][a-z0-9\-]{1,61}[a-z0-9]$/', $sub)) {
define('TENANT_SUBDOMAIN', $sub);
}
三、动态加载租户专属配置与数据源
不同租户需隔离数据库、缓存前缀、模板路径等,不能靠环境变量硬切,必须在应用初始化阶段完成。
- 在
app/common.php或自定义初始化文件中读取TENANT_SUBDOMAIN - 覆盖数据库配置:
Config::set('database.database', 'db_' . TENANT_SUBDOMAIN); - 设置缓存键前缀:
Cache::store('redis')->setOptions(['prefix' => TENANT_SUBDOMAIN . '_']); - 模板路径可按租户隔离:
Config::set('template.view_path', app()->getAppPath() . 'view/' . TENANT_SUBDOMAIN . '/'); - 若租户配置存在数据库中,建议用 APCu 缓存查询结果,键名为
tenant_config:. TENANT_SUBDOMAIN
四、路由与控制器适配多租户逻辑
租户共用一套路由结构,但行为需差异化。避免为每个租户复制控制器,而是通过参数或中间件注入上下文。
- 所有路由保持统一前缀(如
/user、/order),不显式带子域名 - 在基础控制器中统一获取租户 ID:
$tenantId = Db::name('domains')->where('domain_name', TENANT_SUBDOMAIN)->value('tenant_id'); - 关键操作加租户校验中间件,防止跨租户越权访问
- URL 生成需手动指定 host:
Url::build('index/user')->domain(TENANT_SUBDOMAIN . '.example.com')
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











