本文目录导读:

在PHP项目中实现泛域名解析并适配项目路由,通常需要结合Web服务器配置和PHP路由处理两个层面,以下是完整的实现方案:
Web服务器配置(以Nginx为例)
server {
listen 80;
server_name ~^(?<subdomain>.+)\.yourdomain\.com$;
root /path/to/your/project/public;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param SUBDOMAIN $subdomain; # 传递子域名到PHP
include fastcgi_params;
}
}
PHP路由适配实现
基础路由类
<?php
class Router {
private $routes = [];
private $subdomain;
public function __construct() {
// 获取子域名(从Nginx或Apache传递)
$this->subdomain = $_SERVER['SUBDOMAIN'] ?? $this->parseSubdomain();
}
// 解析子域名
private function parseSubdomain() {
$host = $_SERVER['HTTP_HOST'];
$domains = explode('.', $host);
// 判断是否是泛域名(blog.yourdomain.com)
if (count($domains) >= 3) {
return $domains[0];
}
return null;
}
// 添加路由规则
public function addRoute($pattern, $callback, $options = []) {
$this->routes[] = [
'pattern' => $pattern,
'callback' => $callback,
'options' => $options,
'subdomain' => $options['subdomain'] ?? null
];
}
// 匹配并执行路由
public function dispatch($uri) {
$uri = parse_url($uri, PHP_URL_PATH);
$method = $_SERVER['REQUEST_METHOD'];
foreach ($this->routes as $route) {
// 检查子域名匹配
if ($route['subdomain'] !== null) {
if ($this->subdomain !== $route['subdomain']) {
continue;
}
}
// 将路由模式转换为正则表达式
$pattern = preg_replace('/\{(\w+)\}/', '(?P<$1>[^/]+)', $route['pattern']);
$pattern = '#^' . $pattern . '$#';
if (preg_match($pattern, $uri, $matches)) {
// 提取参数
$params = array_filter($matches, 'is_string', ARRAY_FILTER_USE_KEY);
// 执行回调
return call_user_func_array($route['callback'], [$params, $this->subdomain]);
}
}
// 404处理
http_response_code(404);
echo "404 Not Found";
}
}
// 使用示例
$router = new Router();
$router->addRoute('/user/{id}', function($params, $subdomain) {
echo "User ID: " . $params['id'] . " from subdomain: " . $subdomain;
}, ['subdomain' => 'api']);
$router->addRoute('/blog/{slug}', function($params, $subdomain) {
echo "Blog post: " . $params['slug'] . " from subdomain: " . $subdomain;
}, ['subdomain' => 'blog']);
$router->dispatch($_SERVER['REQUEST_URI']);
进阶:使用中间件模式
<?php
class SubdomainRouter {
private $subdomainRoutes = [];
private $middleware = [];
public function __construct() {
$this->parseSubdomain();
}
private function parseSubdomain() {
$host = $_SERVER['HTTP_HOST'];
// 支持多级子域名
preg_match('/^(?:(?<subdomain>[^.]+)\.)?(?<domain>[^.]+\.[^.]+)$/', $host, $matches);
$this->currentSubdomain = $matches['subdomain'] ?? '';
$this->domain = $matches['domain'];
}
public function addSubdomainRoute($subdomain, $callback, $middleware = []) {
$this->subdomainRoutes[$subdomain] = [
'callback' => $callback,
'middleware' => $middleware
];
}
public function addMiddleware($subdomain, $middleware) {
$this->middleware[$subdomain][] = $middleware;
}
public function run() {
$subdomain = $this->currentSubdomain;
// 检查是否有匹配的子域名路由
if (isset($this->subdomainRoutes[$subdomain])) {
$route = $this->subdomainRoutes[$subdomain];
// 执行中间件
foreach ($route['middleware'] as $middleware) {
if (is_callable($middleware)) {
$result = $middleware($this);
if ($result === false) {
return;
}
}
}
// 执行路由回调
return call_user_func($route['callback'], $this);
}
// 默认处理(www或其他)
return $this->handleDefault();
}
private function handleDefault() {
// 默认路由处理
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
echo "Default route for: " . $uri;
}
}
// 使用示例
$app = new SubdomainRouter();
// 添加子域名路由
$app->addSubdomainRoute('api', function($app) {
echo "API Router for subdomain: " . $app->currentSubdomain;
}, ['auth', 'rate-limit']);
$app->addSubdomainRoute('blog', function($app) {
echo "Blog Router for subdomain: " . $app->currentSubdomain;
});
// 添加中间件
$app->addMiddleware('api', function($app) {
// 验证API密钥
$apiKey = $_SERVER['HTTP_X_API_KEY'] ?? '';
if (empty($apiKey)) {
http_response_code(401);
echo "Unauthorized";
return false;
}
return true;
});
$app->run();
Apache配置(如果使用Apache)
<VirtualHost *:80>
ServerName yourdomain.com
ServerAlias *.yourdomain.com
DocumentRoot /path/to/project/public
<Directory /path/to/project/public>
Options Indexes FollowSymLinks
AllowOverride All
Require all granted
</Directory>
# 传递子域名
SetEnvIf Host ^(.*?)\.yourdomain\.com$ SUBDOMAIN=$1
</VirtualHost>
数据库驱动的动态路由
<?php
class DynamicSubdomainRouter {
private $db;
private $tenantCache = [];
public function __construct(PDO $db) {
$this->db = $db;
$this->parseSubdomain();
}
public function resolveTenant() {
$subdomain = $this->currentSubdomain;
// 缓存查询
if (!isset($this->tenantCache[$subdomain])) {
$stmt = $this->db->prepare("SELECT * FROM tenants WHERE subdomain = ?");
$stmt->execute([$subdomain]);
$this->tenantCache[$subdomain] = $stmt->fetch(PDO::FETCH_ASSOC);
}
return $this->tenantCache[$subdomain];
}
public function route() {
$tenant = $this->resolveTenant();
if (!$tenant) {
// 子域名不存在
http_response_code(404);
echo "Tenant not found";
return;
}
// 设置租户上下文
$this->setTenantContext($tenant);
// 根据租户类型路由
switch ($tenant['type']) {
case 'store':
$this->routeStore($tenant);
break;
case 'blog':
$this->routeBlog($tenant);
break;
default:
$this->routeDefault($tenant);
}
}
private function setTenantContext($tenant) {
define('TENANT_ID', $tenant['id']);
define('TENANT_SUBDOMAIN', $tenant['subdomain']);
// 设置数据库连接等
}
}
最佳实践建议
- 缓存DNS解析:使用Redis或APC缓存子域名对应的配置
- 安全性考虑:
- 限制允许的子域名字符
- 防止SSRF攻击
- 检查子域名白名单
- 性能优化:
- 使用FastRoute等高性能路由库
- 启用OPcache
- 测试:
- 使用
/etc/hosts文件模拟多个子域名 - 编写单元测试覆盖不同子域名场景
- 使用
这个方案可以灵活地处理带有子域名的多租户系统、API网关或多站点应用。