前言
URL 路由是 MVC 框架的「交通指揮中心」,ThinkPHP 提供靈活的路由系統支援 RESTful、偽靜態、參數綁定、域名部署等高級特性。本文系統梳理 URL 訪問模式、路由配置語法、正則/選塊/資源路由、中介軟體整合、效能優化與遷移建議。
一、四大 URL 訪問模式
| 模式 | URL 範例 | 特點 | 適用場景 |
| 普通模式 | ?m=Home&c=User&a=login | 相容性最好、無需 Web 伺服器配置 | 開發測試、舊環境 |
| PATHINFO | /index.php/Home/User/login | 語義清晰、SEO 友好、無需 Rewrite | 大多數生產環境推薦 |
| REWRITE | /Home/User/login | 隱藏 index.php、最美觀 | 生產環境 (需 Nginx/Apache 配置) |
| 兼容模式 | ?s=/Home/User/login | 不支援 PATHINFO 環境的折中 | 特殊虛擬主機 |
配置切換 (config/app.php)
<?php
return [
'url_route_on' => true, // 開啟路由
'url_route_must' => false, // 強制使用路由 (true 時未匹配路由拋 404)
'url_convert' => true, // 自動轉換控制器/方法名 (UserType -> user_type)
'url_html_suffix' => 'html', // 偽靜態後綴
'url_common_param' => false, // 是否保留公共參數
];
二、路由定義完整語法 (ThinkPHP 5/6/8)
1. 基礎路由 (路由配置文件 route/app.php)
<?php
use think\facade\Route;
// 靜態地址路由
Route::get('login', 'user/login');
Route::post('register', 'user/register');
// 動態參數路由 (自動綁定到控制器方法參數)
Route::get('user/:id', 'user/detail'); // /user/123 -> detail(123)
Route::get('article/:year/:month/:id', 'article/detail'); // 多參數
// 參數類型約束
Route::get('user/:id\d', 'user/detail'); // id 必須數字
Route::get('user/:name\w+', 'user/profile'); // name 單詞字元
Route::get('page/:id\d{4,6}', 'page/view'); // id 4-6 位數字
// 可選參數
Route::get('search/[:keyword]', 'search/index'); // keyword 可選
// 完全匹配 (結尾 $)
Route::get('api/v1/users$', 'api/v1.user/list'); // 不匹配 /api/v1/users/123
// 正則路由 (以 / 開頭結尾)
Route::rule('/^blog\/(\d{4})\/(\d{2})\/(\w+)$/', 'blog/detail?year=:1&month=:2&slug=:3');
// 資源路由 (RESTful 一鍵生成 7 個路由)
Route::resource('posts', 'PostController');
// 生成:
// GET /posts -> index
// GET /posts/create -> create
// POST /posts -> save
// GET /posts/:id -> read
// GET /posts/:id/edit -> edit
// PUT /posts/:id -> update
// DELETE /posts/:id -> delete
2. 路由分組與中介軟體
<?php
// API 版本分組 + JWT 中介軟體
Route::group('api/v1', function() {
Route::resource('users', 'UserController');
Route::resource('posts', 'PostController');
Route::get('stats', 'StatsController/index');
})->middleware(['jwt.auth', 'rate_limit:100,60']); // 每分鐘 100 次
// 後台管理分組 + RBAC 權限
Route::group('admin', function() {
Route::get('', 'Index/index');
Route::resource('users', 'UserController');
Route::resource('roles', 'RoleController');
})->middleware(['admin.auth', 'permission'])
->domain('admin.example.com'); // 域名路由
// 多語言前綴分組
Route::group(':lang', function() {
Route::get('', 'Index/index');
Route::get('about', 'Page/about');
})->pattern(['lang' => 'zh|en|ja'])
->middleware('locale'); // 中介軟體自動設置語言
3. 域名/子域名路由
<?php
// 綁定模組到子域名
Route::domain('api', 'api'); // api.example.com -> api 模組
Route::domain('admin', 'admin'); // admin.example.com -> admin 模組
Route::domain('shop.*', 'shop'); // shop.example.com, shop.cn -> shop 模組
// 動態子域名 (多租戶)
Route::domain(':tenant.app.example.com', 'tenant')
->pattern(['tenant' => '\w+'])
->middleware('tenant.resolve'); // 中介軟體解析租戶 ID
4. 路由別名與 URL 生成
<?php
// 定義別名
Route::get('user/profile', 'user/profile')->name('user.profile');
// 控制器/模板生成 URL
url('user/profile', ['id' => 123]); // /user/profile/id/123
url(':user.profile', ['id' => 123]); // 使用別名 -> /user/123 (若路由定義為 user/:id)
route('user.profile', ['id' => 123]); // 同 url()
// 模板中
{:url('user/profile', ['id'=>$user->id])}
{:route('user.profile', ['id'=>$user->id])}
三、偽靜態配置 (REWRITE 模式)
Nginx 配置
server {
listen 80;
server_name example.com;
root /var/www/project/public;
index index.php index.html;
location / {
# 核心:嘗試文件 -> 目錄 -> 重寫到 index.php
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
fastcgi_buffering off; # 配合流式輸出
}
# 靜態資源長期緩存
location ~* \.(css|js|png|jpg|jpeg|gif|ico|woff2?|svg)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
Apache (.htaccess)
<IfModule mod_rewrite.c>
Options +FollowSymlinks -Multiviews
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ index.php/$1 [QSA,PT,L]
</IfModule>
四、路由優先級與匹配順序
- 靜態路由 (無參數) 優先級最高。
- 動態路由 按定義順序匹配,**越複雜/越具體的規則越要放前面**。
- 正則路由 最後匹配。
- 資源路由 展開為多條靜態/動態路由,按展開順序。
錯誤示範
<?php
// 錯誤:通用規則在前,具體規則永不匹配
Route::get('user/:name', 'user/profile'); // 匹配 user/123
Route::get('user/:id\d', 'user/detail'); // 永遠不會執行
// 正確:具體規則在前
Route::get('user/:id\d', 'user/detail');
Route::get('user/:name', 'user/profile');
五、效能優化建議
| 優化點 | 建議 | 效果 |
| 路由緩存 | 生產環境啟用 php think optimize:route 生成路由映射表 | 路由解析速度提升 10-50 倍 |
| 減少正則 | 優先使用參數約束 :id\d 替代正則 | 降低 CPU、避免回溯災難 |
| 合併靜態路由 | 大量靜態頁面用單條路由 + 控制器內部判斷 | 減少路由表大小 |
| 關閉不必要的轉換 | 'url_convert' => false (若控制器已蛇形命名) | 省去字串轉換開銷 |
| 路由懶加載 | ThinkPHP 8 支援 Route::lazy() 延遲載入大型路由文件 | 啟動階段記憶體降低 |
六、常見問題排查
| 現象 | 可能原因 | 排查步驟 |
| 404 Not Found | 路由未匹配 / Rewrite 未生效 | 1. 檢查 url_route_on 2. 檢查 Nginx try_files 3. 臨時關閉 url_route_must 測試 |
| 參數接收不到 | 變量名不匹配 / 類型約束失敗 | 1. 控制器方法參數名與路由變量名一致 2. 檢查約束 \d \w 3. var_dump($this->request->param()) |
| URL 生成錯誤 | 別名衝突 / 參數缺失 | 1. 檢查 route() 參數完整 2. 確認別名唯一 3. 查看生成的 URL 字串 |
| 中介軟體不執行 | 中介軟體類不存在 / 註冊順序 | 1. php think middleware:list 確認已註冊 2. 檢查命名空間 3. 中介軟體 handle() 返回 $next($request) |
七、遷移建議:從 ThinkPHP 3.2 到 5/6/8
| TP 3.2 | TP 5/6/8 | 說明 |
'URL_ROUTER_ON' => true | 'url_route_on' => true | 配置鍵名統一小寫下劃線 |
'URL_ROUTE_RULES' => [...] | Route::get()/post()/rule() | 從陣列配置改為代碼定義,支援 IDE 提示 |
U('User/login') | url('user/login') | 函數名小寫,支援別名 |
:id\d 在規則字串中 | Route::get('user/:id\d', ...) | 語法一致,但現在在代碼中 |
| 模組:控制器:方法 | 控制器@方法 或 完整命名空間 | 模組概念弱化,改用命名空間組織 |
結語
ThinkPHP 路由系統從 3.2 的陣列配置進化為 5/6/8 的流式代碼定義,獲得了類型安全、IDE 支援、中介軟體整合、資源路由、域名路由等現代能力。掌握 路由優先級、參數約束、分組中介軟體、路由緩存、Nginx Rewrite 五大核心,即可構建清晰、高效、可維護的 URL 架構。隨著業務增長,善用 路由懶加載、領域驅動設計 (DDD) 模組劃分、API 版本管理,讓路由層持續服務於業務演進。