PHP项目泛域名解析如何适配项目路由

wen PHP项目 24

本文目录导读:

PHP项目泛域名解析如何适配项目路由

  1. Web服务器配置(以Nginx为例)
  2. PHP路由适配实现
  3. 进阶:使用中间件模式
  4. Apache配置(如果使用Apache)
  5. 数据库驱动的动态路由
  6. 最佳实践建议

在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']);
        // 设置数据库连接等
    }
}

最佳实践建议

  1. 缓存DNS解析:使用Redis或APC缓存子域名对应的配置
  2. 安全性考虑
    • 限制允许的子域名字符
    • 防止SSRF攻击
    • 检查子域名白名单
  3. 性能优化
    • 使用FastRoute等高性能路由库
    • 启用OPcache
  4. 测试
    • 使用/etc/hosts文件模拟多个子域名
    • 编写单元测试覆盖不同子域名场景

这个方案可以灵活地处理带有子域名的多租户系统、API网关或多站点应用。

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