🛣️ 路由配置

ThinkAdmin 使用 ThinkPHP 路由能力,并通过 think\admin\support\middleware\MultAccess 在请求开始时解析当前应用或插件。解析完成后,系统会把当前应用路径、命名空间、视图路径和 route/ 目录设置到 ThinkPHP HTTP 对象中。

因此路由文档需要先区分两件事:应用解析由 ThinkAdmin 完成,路由匹配仍由 ThinkPHP 完成。

请求解析

MultAccess 的应用解析顺序如下:

  1. 读取当前入口脚本名。若入口不是 indexrouterthink,例如 admin.php,则绑定到同名应用 admin
  2. 读取 app.domain_bind。按当前完整主机、子域名、* 依次匹配,命中后绑定到配置的应用。
  3. 读取 PathInfo 第一段作为应用名或插件名,例如 /admin/user/index 的第一段是 admin;如果第一段包含点号,会先截取点号前的内容。
  4. 读取 app.app_map。若第一段是映射键,则映射到实际应用或插件编码;映射值也可以是闭包,闭包返回值为空时继续使用原名称。
  5. 如果第一段直接命中 app.app_map 的映射值,或命中 app.deny_app_list,会抛出 404,避免绕过映射直接访问真实应用名。
  6. 如果配置了 app.app_map['*'],未命中的第一段会映射到该默认应用或插件。
  7. 若第一段命中插件注册表,则使用插件目录和插件命名空间。
  8. 若没有第一段,则使用 route.default_app,默认是 index。若解析出的应用或插件不存在,且 app.app_expresstrue,会回退到默认应用;否则不设置多应用并继续后续请求流程。

解析成功后会调用:

$this->app->setNamespace($space)->setAppPath($appPath);
$this->app->http
    ->setBind($appBind)
    ->name($appName)
    ->path($appPath)
    ->setRoutePath($appPath . 'route' . DIRECTORY_SEPARATOR);

同时系统会把模板路径切到当前应用的 view/ 目录,并加载当前应用的 common.phpconfig/*.phpprovider.phpevent.phpmiddleware.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 中的 appmapdomain 合并到 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/ 目录。是否适合放全局根路径,取决于注册时机、路由规则和当前项目结构;公共回调接口通常更适合放在明确的应用或插件路由中,减少路径冲突。

常见问题

应用路由为什么没生效?

按顺序检查:

  1. 请求是否先被解析到了正确应用或插件。
  2. 路由文件是否位于当前应用或插件的 route/ 目录。
  3. 路由目标是否按当前应用命名空间书写。
  4. 是否受 route_complete_match 严格匹配影响。
  5. 是否需要清理缓存。
php think clear

admin.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');
最近更新:
Contributors: 邹景立, Anyon