🛣️ 路由配置
ThinkAdmin 使用 ThinkPHP 路由能力,并通过 think\admin\support\middleware\MultAccess 在请求开始时解析当前应用或插件。解析完成后,系统会把当前应用路径、命名空间、视图路径和 route/ 目录设置到 ThinkPHP HTTP 对象中。
因此路由文档需要先区分两件事:应用解析由 ThinkAdmin 完成,路由匹配仍由 ThinkPHP 完成。
请求解析
MultAccess 的应用解析顺序如下:
- 读取当前入口脚本名。若入口不是
index、router、think,例如admin.php,则绑定到同名应用admin。 - 读取
app.domain_bind。按当前完整主机、子域名、*依次匹配,命中后绑定到配置的应用。 - 读取 PathInfo 第一段作为应用名或插件名,例如
/admin/user/index的第一段是admin;如果第一段包含点号,会先截取点号前的内容。 - 读取
app.app_map。若第一段是映射键,则映射到实际应用或插件编码;映射值也可以是闭包,闭包返回值为空时继续使用原名称。 - 如果第一段直接命中
app.app_map的映射值,或命中app.deny_app_list,会抛出 404,避免绕过映射直接访问真实应用名。 - 如果配置了
app.app_map['*'],未命中的第一段会映射到该默认应用或插件。 - 若第一段命中插件注册表,则使用插件目录和插件命名空间。
- 若没有第一段,则使用
route.default_app,默认是index。若解析出的应用或插件不存在,且app.app_express为true,会回退到默认应用;否则不设置多应用并继续后续请求流程。
解析成功后会调用:
$this->app->setNamespace($space)->setAppPath($appPath);
$this->app->http
->setBind($appBind)
->name($appName)
->path($appPath)
->setRoutePath($appPath . 'route' . DIRECTORY_SEPARATOR);同时系统会把模板路径切到当前应用的 view/ 目录,并加载当前应用的 common.php、config/*.php、provider.php、event.php、middleware.php 等文件。
应用路由
应用路由写在当前应用或插件的 route/ 目录中。请求被解析到某个应用后,ThinkPHP 会使用该应用的路由目录。
示例:
<?php
// app/admin/route/demo.php
use think\admin\Library;
Library::$sapp->route->get('demo', 'test/index');
Library::$sapp->route->post('user/pass', 'user/pass');访问方式取决于应用是否绑定:
| 场景 | 访问地址 | 说明 |
|---|---|---|
| 普通多应用入口 | /admin/demo | 第一段 admin 用于解析应用,剩余 demo 参与应用路由匹配 |
admin.php 入口 | /demo | 入口名已绑定 admin,路径不再需要 admin 前缀 |
域名绑定 admin | /demo | 域名已绑定 admin,路径不再需要 admin 前缀 |
路由目标按 ThinkPHP 当前应用规则解析。当前应用已经是 admin 时,test/index 指向 app\admin\controller\Test::index()。
插件路由
插件注册后会进入 Plugin::get() 的插件表。若请求第一段命中插件编码,MultAccess 会把当前应用路径切换到插件目录,并使用插件命名空间。
插件路由文件同样写在插件目录的 route/ 下。例如 Admin 插件存在:
// plugin/think-plugs-admin/src/route/demo.php
Library::$sapp->route->post('user/pass', static function () {
return json(['code' => 0, 'info' => lang('演示环境禁止修改密码!')]);
});ThinkPHP 8 下建议使用闭包、数组回调或当前应用内的短控制器写法。涉及完整类名的插件路由优先使用数组回调,避免旧的 Class@method 字符串格式在新版本中被识别为不存在的控制器方法。
<?php
// plugin/think-plugs-center/src/route/router.php
use plugin\center\controller\Index;
use think\admin\Library;
Library::$sapp->route
->any('layout/<encode>', [Index::class, 'layout'])
->pattern(['encode' => '[\w-]+']);当前内置路由
当前源码中显式注册的跨应用路由主要有以下几类,排查路由问题时应优先确认服务是否已经注册、发布文件是否已刷新、以及回调格式是否符合 ThinkPHP 8:
- 插件中心:
plugin/think-plugs-center/src/route/router.php注册layout/{encode},调度到plugin\center\controller\Index::layout(),当前使用数组回调。 - 支付插件:
plugin\payment\Service注册/plugin-payment-notify/:vars,用于统一接收支付通道通知,回调内部解析安全参数后调用支付服务通知逻辑。 - 微信插件:
app\wechat\Service注册/plugin-wxpay-notify/:vars,用于微信支付通知,回调内部解析安全参数后调用微信支付通知逻辑。 - 一物一码插件:
plugin\wuma\Service注册防伪查询入口,调度到plugin\wuma\controller\scaner\Query::index(),当前使用数组回调并限制查询模式、码值和校验码格式。
这些路由有的来自应用或插件 route/ 目录,有的来自服务类 register() 动态注册。若修改了服务声明、路由文件或 Composer 安装信息,建议执行 php think xadmin:publish 后再清理缓存。
插件可以设置别名,Plugin 构造时会把别名写入 app.app_map,因此访问别名也能解析到真实插件编码。
映射与绑定
ThinkAdmin 运行参数会把 RuntimeService 中的 appmap 和 domain 合并到 ThinkPHP 配置:
Library::$sapp->config->set([
'app_map' => $appmap,
'domain_bind' => $domain,
], 'app');典型配置含义:
return [
'app_map' => [
'manage' => 'admin',
'mall' => 'plugin-wemall',
],
'domain_bind' => [
'admin.example.com' => 'admin',
'm.example.com' => 'plugin-wemall',
],
];实际行为:
/manage/user/index会先映射到admin应用,再把剩余路径交给admin应用处理。admin.example.com/user/index会直接绑定到admin应用。domain_bind命中时会以绑定应用运行,不需要再写应用前缀。- 配置
manage => admin后,直接访问/admin/...会因为admin是映射值而被视为不可直接访问的应用名,当前实现会返回 404。 - 配置
app_map['*']可以把未命中的第一段统一映射到指定应用或插件;这会影响兜底路径的归属,需要避免与真实应用名冲突。
默认配置
路由关键默认值在 config/route.php 中:
return [
'pathinfo_depr' => '/',
'url_html_suffix' => 'html',
'url_route_must' => false,
'route_complete_match' => true,
'controller_layer' => 'controller',
'empty_controller' => 'Error',
'default_app' => 'index',
'default_controller' => 'Index',
'default_action' => 'index',
];需要注意:
url_route_must默认是false,没有命中自定义路由时仍可按默认控制器规则访问。route_complete_match默认是true,路由规则会更严格。default_app默认是index,不是后台admin。
动态注册
项目启动时,Library::register() 会扫描应用目录下两层以内的 sys.php 并加载。适合在这些文件中做全局启动注册,例如动态路由、事件或其他初始化逻辑。
<?php
// app/admin/sys.php
use think\admin\Library;
Library::$sapp->route->get('demo', 'admin/test/index');这种写法是运行期直接注册到 ThinkPHP 路由对象,不属于某个应用 route/ 目录。是否适合放全局根路径,取决于注册时机、路由规则和当前项目结构;公共回调接口通常更适合放在明确的应用或插件路由中,减少路径冲突。
常见问题
应用路由为什么没生效?
按顺序检查:
- 请求是否先被解析到了正确应用或插件。
- 路由文件是否位于当前应用或插件的
route/目录。 - 路由目标是否按当前应用命名空间书写。
- 是否受
route_complete_match严格匹配影响。 - 是否需要清理缓存。
php think clearadmin.php 和 /admin/... 有什么区别?
admin.php 会被 MultAccess 当作入口绑定应用名,因此访问 admin.php/demo 时当前应用已经是 admin,实际参与路由匹配的是 demo。
index.php/admin/demo 或 /admin/demo 则是通过 PathInfo 第一段解析应用,第一段 admin 会被移除,剩余 demo 再进入应用路由匹配。
如何查看路由?
可使用 ThinkPHP 命令查看当前可识别的路由:
php think route:list如果项目使用多应用或插件路由,命令输出是否完整取决于命令执行时的应用解析环境。排查线上请求时,通常还需要结合实际 URL、入口文件、域名绑定和 app_map 一起判断。
如何写 RESTful 路由?
ThinkAdmin 没有额外封装 RESTful 路由语法,按 ThinkPHP 原生写法即可:
<?php
// app/admin/route/api.php
use think\admin\Library;
Library::$sapp->route->get('api/user', 'user/index');
Library::$sapp->route->post('api/user', 'user/save');
Library::$sapp->route->get('api/user/:id', 'user/read');
Library::$sapp->route->put('api/user/:id', 'user/update');
Library::$sapp->route->delete('api/user/:id', 'user/delete');