ThinkPHP 5 到 ThinkPHP 8 路由迁移完整指南
本文档详细说明如何在 ThinkPHP 8.1.3 中实现 ThinkPHP 5.0.24(易优CMS)的路由效果
目录
路由系统对比分析
ThinkPHP 5.0.24(易优CMS)
路由文件位置 :application/route.php
核心特点:
- 单文件配置,所有路由规则在一个文件中
- 支持动态路由生成(从数据库读取配置)
- 使用数组方式定义路由规则
- 支持复杂的伪静态规则
路由定义示例:
php
<?php
use think\Route;
$route = [
'__pattern__' => [
'tid' => '[\-\w]+',
'dirname' => '[\-\w]+',
'aid' => '(.*)',
],
];
// 列表页路由
$route['<tid>$'] = [
'home/Lists/index',
['method' => 'get', 'ext' => ''],
['tid' => '[\-\w]+'],
];
// 内容页路由
$route['<dirname>/<aid>$'] = [
'home/View/index',
['method' => 'get', 'ext' => 'html'],
['dirname' => '[\-\w]+', 'aid' => '(.*)'],
];
return $route;
ThinkPHP 8.1.3
路由文件位置:
- 配置:
config/route.php - 定义:
route/app.php
核心特点:
- 配置与定义分离
- 使用 Facade 模式
- 支持链式调用
- 更强的类型约束
路由定义示例:
php
<?php
declare(strict_types=1);
use think\facade\Route;
// 列表页路由
Route::get('<tid>$', 'index/Lists/index')
->ext('')
->pattern(['tid' => '[\-\w]+'])
->name('lists');
// 内容页路由
Route::get('<dirname>/<aid>$', 'index/View/index')
->ext('html')
->pattern([
'dirname' => '[\-\w]+',
'aid' => '(.*)'
])
->name('view');
易优CMS路由特点
通过分析易优CMS的 application/route.php 文件,发现其路由系统具有以下特点:
1. 动态配置驱动
php
// 从缓存读取全局配置
$globalTpCache = tpCache('global');
// 根据配置动态设置路由行为
$seo_pseudo = !empty($globalTpCache['seo_pseudo'])
? intval($globalTpCache['seo_pseudo'])
: config('ey_config.seo_pseudo');
2. 三种URL模式
| 模式 | 值 | 说明 | 示例 |
|---|---|---|---|
| 动态模式 | 1 | 兼容模式,使用 ?s= 参数 |
index.php?s=/article/view/id/123 |
| 静态模式 | 2 | 纯静态HTML文件 | /article/news/123.html |
| 伪静态模式 | 3 | SEO友好的伪静态URL | /article/news/123.html |
3. 多语言/多站点支持
多语言路由:
php
// 中文:/cn/article/news/123.html
// 英文:/en/article/news/123.html
$lang_rewrite_str = '<lang>/';
多城市路由:
php
// 北京站:/beijing/article/news/123.html
// 上海站:/shanghai/article/news/123.html
$site_rewrite_str = '<site>/';
4. 内容模型路由
易优CMS为不同内容类型定义了专门的路由规则:
精简格式(seo_rewrite_format = 1)
php
// 列表页
'<tid>$' => 'home/Lists/index'
'<tid>/list_<typeid>_<page>$' => 'home/Lists/index'
// 内容页
'<dirname>/<aid>$' => 'home/View/index'
URL示例:
- 列表页:
/news或/news/list_1_2 - 内容页:
/news/article-123.html
完整格式(seo_rewrite_format = 2)
php
// 文章模型
'article/<tid>$' => 'home/Article/lists'
'article/<dirname>/<aid>$' => 'home/Article/view'
// 产品模型
'product/<tid>$' => 'home/Product/lists'
'product/<dirname>/<aid>$' => 'home/Product/view'
// 图集模型
'images/<tid>$' => 'home/Images/lists'
'images/<dirname>/<aid>$' => 'home/Images/view'
// 下载模型
'download/<tid>$' => 'home/Download/lists'
'download/<dirname>/<aid>$' => 'home/Download/view'
// 视频模型
'media/<tid>$' => 'home/Media/lists'
'media/<dirname>/<aid>$' => 'home/Media/view'
// 专题模型
'special/<tid>$' => 'home/Special/lists'
'special/<dirname>/<aid>$' => 'home/Special/view'
// 单页模型
'single/<tid>$' => 'home/Single/lists'
URL示例:
- 文章列表:
/article/news.html - 文章内容:
/article/news/article-123.html - 产品列表:
/product/electronics.html - 产品详情:
/product/electronics/product-456.html
5. 自定义模型动态路由
php
// 从数据库读取自定义模型
$channeltype_row = \think\Db::name('channeltype')
->field('nid,ctl_name')
->where(['ifsystem' => 0])
->select();
// 动态生成路由
foreach ($channeltype_row as $value) {
$home_rewrite += [
$value['nid'].'/<tid>$' => [
'home/'.$value['ctl_name'].'/lists',
['method' => 'get', 'ext' => 'html'],
],
$value['nid'].'/<dirname>/<aid>$' => [
'home/'.$value['ctl_name'].'/view',
['method' => 'get', 'ext' => 'html'],
],
];
}
6. 特殊功能路由
php
// 会员中心
'user$' => 'user/Users/login'
'reg$' => 'user/Users/reg'
'centre$' => 'user/Users/centre'
'cart$' => 'user/Shop/shop_cart_list'
// 搜索
'sindex$' => 'home/Search/index'
'search$' => 'home/Search/lists'
// 标签
'tags$' => 'home/Tags/index'
'tags/<tagid>$' => 'home/Tags/lists'
'tags/<tagid>_<page>$' => 'home/Tags/lists'
// 问答(如果安装了问答插件)
'ask$' => 'home/Ask/index'
'ask/view_<ask_id>$' => 'home/Ask/details'
7. 参数正则约束
php
'__pattern__' => [
'tid' => '[\-\w]+', // 栏目标识:字母、数字、下划线、横线
'dirname' => '[\-\w]+', // 目录名称
'aid' => '(.*)', // 文档ID:任意字符
'typeid' => '[\d]+', // 类型ID:纯数字
'page' => '[\d]+', // 页码:纯数字
'tagid' => '[\d]+', // 标签ID:纯数字
]
ThinkPHP 8路由机制
1. 配置与定义分离
配置文件 :config/route.php
php
<?php
return [
// pathinfo分隔符
'pathinfo_depr' => '/',
// 是否强制使用路由
'url_route_must' => false,
// URL伪静态后缀
'url_html_suffix' => 'html',
// 默认的路由变量规则
'default_route_pattern' => '[\w\.]+',
];
路由定义 :route/app.php
php
<?php
declare(strict_types=1);
use think\facade\Route;
Route::get('user/<id>', 'user/read');
2. Facade 模式
php
use think\facade\Route;
// 而不是 TP5 的
use think\Route;
3. 链式调用
php
Route::get('article/<id>', 'article/read')
->ext('html') // 设置URL后缀
->pattern(['id' => '\d+']) // 参数约束
->name('article.read') // 路由命名
->middleware(['auth']) // 中间件
->cache(3600); // 路由缓存
4. 路由分组
php
Route::group('admin', function () {
Route::get('user/index', 'admin.User/index');
Route::get('user/add', 'admin.User/add');
Route::post('user/save', 'admin.User/save');
})->middleware(['auth', 'admin']);
5. 资源路由
php
// 自动生成 RESTful 路由
Route::resource('article', 'Article');
// 等同于:
// GET /article -> index
// GET /article/create -> create
// POST /article -> save
// GET /article/:id -> read
// GET /article/:id/edit -> edit
// PUT /article/:id -> update
// DELETE /article/:id -> delete
6. 参数约束
php
// 方式1:在路由中直接约束
Route::get('user/<id:\d+>', 'user/read');
// 方式2:使用 pattern 方法
Route::get('user/<id>', 'user/read')
->pattern(['id' => '\d+']);
// 方式3:全局约束(在 config/route.php 中)
'default_route_pattern' => '\d+',
完整实现方案
方案选择
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 方案1:静态路由 | 小型项目、路由规则固定 | 简单直观、性能好 | 不支持动态配置 |
| 方案2:动态路由 | 大型CMS、需要动态配置 | 灵活、完全模拟易优CMS | 实现复杂 |
本文档推荐使用方案2:动态路由生成,完全模拟易优CMS的路由特性。
实现步骤
步骤 1:创建路由配置文件
创建 config/eyoucms.php:
php
<?php
declare(strict_types=1);
return [
// URL模式:1=动态,2=静态,3=伪静态
'seo_pseudo' => 3,
// 伪静态格式:1=精简,2=完整,3=目录层级,4=父目录/当前目录
'seo_rewrite_format' => 1,
// 多语言数量
'system_langnum' => 1,
// 默认语言
'system_home_default_lang' => 'cn',
// 是否启用多城市
'city_switch_on' => false,
// URL后缀
'url_html_suffix' => 'html',
// 路由参数正则规则
'route_pattern' => [
'tid' => '[\-\w]+',
'dirname' => '[\-\w]+',
'aid' => '(.*)',
'typeid' => '\d+',
'page' => '\d+',
'tagid' => '\d+',
'ask_id' => '\d+',
'type_id' => '\d+',
],
// 内容模型列表
'content_models' => [
'article', // 文章
'product', // 产品
'images', // 图集
'download', // 下载
'media', // 视频
'special', // 专题
],
];
步骤 2:创建路由服务类
创建 app/service/RouteService.php:
php
<?php
declare(strict_types=1);
namespace app\service;
use think\facade\Route;
use think\facade\Db;
use think\facade\Cache;
class RouteService
{
/**
* 注册易优CMS风格的路由
*/
public static function register(): void
{
$config = config('eyoucms');
$seo_pseudo = $config['seo_pseudo'] ?? 3;
$seo_rewrite_format = $config['seo_rewrite_format'] ?? 1;
$pattern = $config['route_pattern'] ?? [];
// 根据URL模式注册不同的路由
match ($seo_pseudo) {
3 => self::registerRewriteRoutes($seo_rewrite_format, $pattern),
2 => self::registerStaticRoutes($pattern),
1 => self::registerDynamicRoutes(),
default => null,
};
}
/**
* 注册伪静态路由(seo_pseudo = 3)
*/
private static function registerRewriteRoutes(int $format, array $pattern): void
{
$langPrefix = self::getLangPrefix();
if ($format === 1) {
// 精简格式
self::registerSimpleRewriteRoutes($langPrefix, $pattern);
} else {
// 完整格式
self::registerFullRewriteRoutes($langPrefix, $pattern);
}
}
/**
* 注册精简伪静态路由
*/
private static function registerSimpleRewriteRoutes(string $langPrefix, array $pattern): void
{
// 首页
Route::get($langPrefix . '$', 'index/index')
->ext('')
->name('home');
// 会员中心
Route::group($langPrefix . 'user', function () {
Route::get('$', 'user.Users/login')->name('user.login');
Route::get('reg$', 'user.Users/reg')->name('user.reg');
Route::get('centre$', 'user.Users/centre')->name('user.centre');
Route::get('index$', 'user.Users/index')->name('user.index');
})->ext('');
// 购物车
Route::get($langPrefix . 'cart$', 'user.Shop/shop_cart_list')
->ext('')
->name('cart');
// 搜索
Route::get($langPrefix . 'sindex$', 'index/Search/index')
->ext('')
->name('search.index');
Route::get($langPrefix . 'search$', 'index/Search/lists')
->ext('html')
->name('search.lists');
// 标签
self::registerTagsRoutes($langPrefix, $pattern);
// 通用列表页和内容页
self::registerCommonRoutes($langPrefix, $pattern);
}
/**
* 注册完整伪静态路由
*/
private static function registerFullRewriteRoutes(string $langPrefix, array $pattern): void
{
// 会员中心
Route::group($langPrefix . 'Users', function () {
Route::get('login$', 'user.Users/login')->name('user.login');
Route::get('reg$', 'user.Users/reg')->name('user.reg');
Route::get('centre$', 'user.Users/centre')->name('user.centre');
Route::get('index$', 'user.Users/index')->name('user.index');
Route::get('cart$', 'user.Shop/shop_cart_list')->name('user.cart');
})->ext('html');
// 各种内容模型
$models = config('eyoucms.content_models', []);
foreach ($models as $model) {
self::registerModelRoutes($langPrefix, $model, $pattern);
}
// 单页模型
Route::get($langPrefix . 'single/<tid>$', 'index/Single/lists')
->ext('html')
->pattern(['tid' => $pattern['tid']])
->name('single');
// 留言模型
Route::get($langPrefix . 'guestbook/<tid>$', 'index/Guestbook/lists')
->ext('html')
->pattern(['tid' => $pattern['tid']])
->name('guestbook');
// 搜索
Route::get($langPrefix . 'sindex$', 'index/Search/index')
->ext('')
->name('search.index');
Route::get($langPrefix . 'search$', 'index/Search/lists')
->ext('html')
->name('search.lists');
// 标签
self::registerTagsRoutes($langPrefix, $pattern);
// 自定义模型(从数据库读取)
self::registerCustomModelRoutes($langPrefix, $pattern);
}
/**
* 注册标签路由
*/
private static function registerTagsRoutes(string $langPrefix, array $pattern): void
{
Route::group($langPrefix . 'tags', function () use ($pattern) {
Route::get('$', 'index/Tags/index')->name('tags.index');
Route::get('<tagid>_<page>$', 'index/Tags/lists')
->pattern([
'tagid' => $pattern['tagid'],
'page' => $pattern['page']
])
->name('tags.lists.page');
Route::get('<tagid>$', 'index/Tags/lists')
->pattern(['tagid' => $pattern['tagid']])
->name('tags.lists');
})->ext('html');
}
/**
* 注册通用列表和内容路由(精简格式)
*/
private static function registerCommonRoutes(string $langPrefix, array $pattern): void
{
// 列表页 - 带分页
Route::get($langPrefix . '<tid>/list_<typeid>_<page>$', 'index/Lists/index')
->ext('')
->pattern([
'tid' => $pattern['tid'],
'typeid' => $pattern['typeid'],
'page' => $pattern['page']
])
->name('lists.page');
// 列表页
Route::get($langPrefix . '<tid>$', 'index/Lists/index')
->ext('')
->pattern(['tid' => $pattern['tid']])
->name('lists');
// 内容页
Route::get($langPrefix . '<dirname>/<aid>$', 'index/View/index')
->ext('html')
->pattern([
'dirname' => $pattern['dirname'],
'aid' => $pattern['aid']
])
->name('view');
}
/**
* 注册内容模型路由
*/
private static function registerModelRoutes(string $langPrefix, string $model, array $pattern): void
{
$controller = ucfirst($model);
Route::group($langPrefix . $model, function () use ($controller, $pattern) {
// 列表页 - 带分页
Route::get('<tid>/list_<typeid>_<page>$', "index/{$controller}/lists")
->pattern([
'tid' => $pattern['tid'],
'typeid' => $pattern['typeid'],
'page' => $pattern['page']
]);
// 列表页
Route::get('<tid>$', "index/{$controller}/lists")
->pattern(['tid' => $pattern['tid']]);
// 内容页
Route::get('<dirname>/<aid>$', "index/{$controller}/view")
->pattern([
'dirname' => $pattern['dirname'],
'aid' => $pattern['aid']
]);
})->ext('html')->name($model);
}
/**
* 注册自定义模型路由(从数据库读取)
*/
private static function registerCustomModelRoutes(string $langPrefix, array $pattern): void
{
// 使用缓存避免每次请求都查询数据库
$cacheKey = 'eyoucms_custom_models';
$models = Cache::get($cacheKey);
if (empty($models)) {
// 从数据库读取自定义模型
$models = Db::name('channeltype')
->field('nid,ctl_name')
->where('ifsystem', 0)
->select()
->toArray();
Cache::set($cacheKey, $models, 3600); // 缓存1小时
}
foreach ($models as $model) {
$nid = $model['nid'];
$ctlName = $model['ctl_name'];
Route::group($langPrefix . $nid, function () use ($ctlName, $pattern) {
// 列表页 - 带分页
Route::get('<tid>/list_<typeid>_<page>$', "index/{$ctlName}/lists")
->pattern([
'tid' => $pattern['tid'],
'typeid' => $pattern['typeid'],
'page' => $pattern['page']
]);
// 列表页
Route::get('<tid>$', "index/{$ctlName}/lists")
->pattern(['tid' => $pattern['tid']]);
// 内容页
Route::get('<dirname>/<aid>$', "index/{$ctlName}/view")
->pattern([
'dirname' => $pattern['dirname'],
'aid' => $pattern['aid']
]);
})->ext('html');
}
}
/**
* 获取多语言前缀
*/
private static function getLangPrefix(): string
{
$config = config('eyoucms');
$system_langnum = $config['system_langnum'] ?? 1;
if ($system_langnum > 1) {
return '<lang>/';
}
return '';
}
/**
* 注册静态路由(seo_pseudo = 2)
*/
private static function registerStaticRoutes(array $pattern): void
{
// 静态模式主要访问HTML文件,只需要少量动态接口
Route::get('api/<controller>/<action>', 'api.<controller>/<action>');
}
/**
* 注册动态路由(seo_pseudo = 1)
*/
private static function registerDynamicRoutes(): void
{
// 动态模式使用默认路由,不需要特殊配置
}
}
步骤 3:在应用启动时注册路由
修改 route/app.php:
php
<?php
declare(strict_types=1);
use think\facade\Route;
use app\service\RouteService;
// 注册易优CMS风格的路由
RouteService::register();
// 其他自定义路由
Route::get('think', function () {
return 'hello,ThinkPHP8!';
});
步骤 4:配置路由参数
修改 config/route.php:
php
<?php
return [
// pathinfo分隔符
'pathinfo_depr' => '/',
// 是否强制使用路由
'url_route_must' => false,
// URL伪静态后缀
'url_html_suffix' => 'html',
// 默认的路由变量规则
'default_route_pattern' => '[\w\.]+',
// 是否区分大小写
'url_case_sensitive' => false,
// 路由是否完全匹配
'route_complete_match' => false,
// 去除URL尾部斜杠
'remove_slash' => false,
];
关键差异对照表
| 特性 | ThinkPHP 5(易优CMS) | ThinkPHP 8 实现方式 |
|---|---|---|
| 路由定义位置 | application/route.php |
route/app.php |
| 路由注册方式 | 数组方式 | Facade + 链式调用 |
| 参数语法 | <id> 或 :id |
统一使用 <id> |
| 参数约束 | 数组第3个参数 | ->pattern([]) 方法 |
| 扩展名设置 | ['ext' => 'html'] |
->ext('html') 方法 |
| 路由命名 | 不支持 | ->name('route.name') |
| 中间件 | 不支持(使用行为) | ->middleware([]) |
| 路由分组 | 手动数组合并 | Route::group() |
| 动态路由 | 直接在route.php中执行 | 通过服务类注册 |
| 缓存支持 | 手动实现 | ->cache(3600) |
URL生成对比
TP5(易优CMS)
php
// 生成URL
url('home/Article/view', ['aid' => 123, 'dirname' => 'news']);
// 输出:/article/news/123.html
// 带域名的URL
url('home/Article/view', ['aid' => 123], true, true);
TP8
php
// 方式1:使用路由名称生成URL(推荐)
url('article.view', ['aid' => 123, 'dirname' => 'news']);
// 输出:/article/news/123.html
// 方式2:使用完整路径
url('index/Article/view', ['aid' => 123, 'dirname' => 'news']);
// 带域名的URL
url('article.view', ['aid' => 123, 'dirname' => 'news'])->domain(true);
最佳实践建议
1. 使用路由缓存
生成路由缓存以提升性能:
bash
# 生成路由缓存
php think optimize:route
# 清除路由缓存
php think clear --route
2. 分离路由文件
建议按模块分离路由文件:
route/
├── app.php # 主路由入口
├── admin.php # 后台路由
├── api.php # API路由
└── web.php # 前台路由
在 route/app.php 中引入:
php
<?php
declare(strict_types=1);
use app\service\RouteService;
// 注册易优CMS路由
RouteService::register();
// 引入其他路由文件
require __DIR__ . '/admin.php';
require __DIR__ . '/api.php';
3. 使用路由中间件
php
// 后台路由使用认证中间件
Route::group('admin', function () {
Route::get('user/index', 'admin.User/index');
Route::get('article/index', 'admin.Article/index');
})->middleware(['auth', 'admin']);
// API路由使用限流中间件
Route::group('api', function () {
Route::get('user/info', 'api.User/info');
})->middleware(['throttle:60,1']); // 每分钟60次
4. 合理使用缓存
php
// 自定义模型路由使用缓存
$cacheKey = 'eyoucms_custom_models';
$models = Cache::remember($cacheKey, 3600, function () {
return Db::name('channeltype')
->field('nid,ctl_name')
->where('ifsystem', 0)
->select()
->toArray();
});
5. 类型声明
在控制器方法中使用类型声明:
php
<?php
declare(strict_types=1);
namespace app\controller;
class Article
{
public function view(int $aid, string $dirname): string
{
// 控制器逻辑
return view('article/view', [
'aid' => $aid,
'dirname' => $dirname
]);
}
}
6. 路由命名规范
建议使用统一的路由命名规范:
php
// 模块.控制器.操作
Route::get('article/<id>', 'Article/read')->name('article.read');
Route::get('product/<id>', 'Product/read')->name('product.read');
// 使用时
url('article.read', ['id' => 123]);
迁移检查清单
环境准备
- 确认PHP版本 >= 8.0
- 确认已安装Composer 2.0+
- 确认数据库版本符合要求
配置文件
- 创建路由配置文件
config/eyoucms.php - 修改路由配置
config/route.php - 配置数据库连接
config/database.php
代码实现
- 创建路由服务类
app/service/RouteService.php - 修改
route/app.php注册路由 - 更新控制器添加类型声明
- 更新模型添加类型声明
功能测试
- 测试首页访问
- 测试列表页路由(带分页和不带分页)
- 测试内容页路由
- 测试会员中心路由
- 测试搜索功能路由
- 测试标签路由
- 测试多语言路由(如果启用)
- 测试自定义模型路由
- 测试URL生成功能
性能优化
- 生成路由缓存
- 测试路由缓存是否生效
- 配置OPcache
- 配置Redis缓存(如果使用)
上线准备
- 备份原有系统
- 准备回滚方案
- 编写部署文档
- 进行压力测试
常见问题解答
Q1: 路由不生效怎么办?
A: 检查以下几点:
- 确认
RouteService::register()已在route/app.php中调用 - 清除路由缓存:
php think clear --route - 检查 Nginx/Apache 的 rewrite 规则是否正确配置
- 确认
config/route.php中url_route_must设置正确
Q2: 如何调试路由?
A: 使用以下方法:
php
// 在 route/app.php 中添加
Route::get('debug/routes', function () {
$routes = app()->route->getRuleList();
dump($routes);
});
访问 /debug/routes 查看所有已注册的路由。
Q3: 多语言路由如何实现?
A: 在 config/eyoucms.php 中设置:
php
'system_langnum' => 2, // 启用多语言
路由会自动添加 <lang>/ 前缀,如:
- 中文:
/cn/article/news/123.html - 英文:
/en/article/news/123.html
Q4: 如何添加新的内容模型?
A: 有两种方式:
方式1:配置文件添加
php
// config/eyoucms.php
'content_models' => [
'article',
'product',
'mynewmodel', // 新增模型
],
方式2:数据库添加
在 channeltype 表中添加记录,路由会自动从数据库读取。
Q5: 性能如何优化?
A: 建议采取以下措施:
- 启用路由缓存:
php think optimize:route - 使用Redis缓存自定义模型数据
- 启用OPcache
- 使用CDN加速静态资源
- 开启Gzip压缩
总结
本文档详细说明了如何在 ThinkPHP 8.1.3 中实现 ThinkPHP 5.0.24(易优CMS)的路由效果。核心要点:
- 配置驱动:通过配置文件控制路由行为
- 服务类注册:使用服务类动态注册路由规则
- 完全兼容:支持易优CMS的所有路由特性
- 性能优化:使用缓存提升性能
- 类型安全:利用PHP 8的类型系统提升代码质量
通过本方案,可以在保持易优CMS路由特性的同时,享受ThinkPHP 8带来的现代化特性和性能提升。
参考资料
文档版本 :v1.0
最后更新 :2025-12-31
适用版本:ThinkPHP 8.1.3 / 易优CMS (ThinkPHP 5.0.24)