1. 背景:TP8 为什么不再内置注解路由
ThinkPHP 从 8.0 开始做了一次比较彻底的"瘦身",注解路由(Annotation Route)被从核心框架中剥离,改为由独立扩展 topthink/think-annotation 提供。这不是简单的"少写几行代码",而是框架架构层面的一次取舍,理解它有助于你判断迁移成本。
1.1 剥离的三个直接原因
- 依赖解耦:核心框架不再依赖 doctrine/annotations 之类的反射解析库,安装体积和启动开销都更小。注解解析本质上是"读注释字符串 + 正则/反射解析",对每个控制器类都要做一次,属于典型的启动期开销。
- 演进节奏解耦:注解路由的语法、支持的属性、缓存策略都可以独立发版,不必跟着框架大版本走。社区扩展可以更快地适配 PHP 新版本特性。
- 职责单一:核心只保留"路由注册与匹配"这一层能力,注解只是"声明路由的一种方式",属于上层语法糖,天然适合外置。
1.2 代价:静默失效是最坑的地方
代价也很明确——升级到 TP8 后,原来写在控制器注释里的 @Route 会直接失效,而且不会报错,只是路由"凭空消失"。这一点比"报错"更难排查,因为:
- 框架不会提示"你用了不支持的注解语法";
- 控制器方法本身仍然可以被显式路由访问,容易误判为"路由没问题";
- 只有访问注解声明的 URL 时才会 404,且错误信息与"路由不存在"完全一致。
如果你是从 TP6 迁移过来的项目,这一点尤其要注意:旧式文档块注释(DocBlock)注解在 TP8 中默认不会被解析,必须显式安装扩展并初始化。换句话说,TP8 的注解路由是"可选能力",不是"默认能力"。
1.3 TP6 与 TP8 的写法对照
| 依赖 | 框架内置 | 需 composer require topthink/think-annotation |
| 语法 | /** @Route(…) */ 文档块注释 | #[Route(…)] PHP 8 原生 Attribute |
| 解析时机 | 运行时反射解析注释字符串 | 编译期由 PHP 引擎解析 Attribute |
| 语法错误 | 静默失效,不报错 | 直接抛错,定位快 |
| 初始化 | 无需额外调用 | 必须 AnnotationRoute::init() |
| 多应用 | 自动推断 | 需配置 scan 命名空间 |
下面这张图概括了 TP6 与 TP8 在注解路由解析链路上的差异:
#mermaid-svg-s50QHVZGtS2ndNcE{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-s50QHVZGtS2ndNcE .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-s50QHVZGtS2ndNcE .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-s50QHVZGtS2ndNcE .error-icon{fill:#552222;}#mermaid-svg-s50QHVZGtS2ndNcE .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-s50QHVZGtS2ndNcE .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-s50QHVZGtS2ndNcE .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-s50QHVZGtS2ndNcE .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-s50QHVZGtS2ndNcE .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-s50QHVZGtS2ndNcE .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-s50QHVZGtS2ndNcE .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-s50QHVZGtS2ndNcE .marker{fill:#333333;stroke:#333333;}#mermaid-svg-s50QHVZGtS2ndNcE .marker.cross{stroke:#333333;}#mermaid-svg-s50QHVZGtS2ndNcE svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-s50QHVZGtS2ndNcE p{margin:0;}#mermaid-svg-s50QHVZGtS2ndNcE .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-s50QHVZGtS2ndNcE .cluster-label text{fill:#333;}#mermaid-svg-s50QHVZGtS2ndNcE .cluster-label span{color:#333;}#mermaid-svg-s50QHVZGtS2ndNcE .cluster-label span p{background-color:transparent;}#mermaid-svg-s50QHVZGtS2ndNcE .label text,#mermaid-svg-s50QHVZGtS2ndNcE span{fill:#333;color:#333;}#mermaid-svg-s50QHVZGtS2ndNcE .node rect,#mermaid-svg-s50QHVZGtS2ndNcE .node circle,#mermaid-svg-s50QHVZGtS2ndNcE .node ellipse,#mermaid-svg-s50QHVZGtS2ndNcE .node polygon,#mermaid-svg-s50QHVZGtS2ndNcE .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-s50QHVZGtS2ndNcE .rough-node .label text,#mermaid-svg-s50QHVZGtS2ndNcE .node .label text,#mermaid-svg-s50QHVZGtS2ndNcE .image-shape .label,#mermaid-svg-s50QHVZGtS2ndNcE .icon-shape .label{text-anchor:middle;}#mermaid-svg-s50QHVZGtS2ndNcE .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-s50QHVZGtS2ndNcE .rough-node .label,#mermaid-svg-s50QHVZGtS2ndNcE .node .label,#mermaid-svg-s50QHVZGtS2ndNcE .image-shape .label,#mermaid-svg-s50QHVZGtS2ndNcE .icon-shape .label{text-align:center;}#mermaid-svg-s50QHVZGtS2ndNcE .node.clickable{cursor:pointer;}#mermaid-svg-s50QHVZGtS2ndNcE .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-s50QHVZGtS2ndNcE .arrowheadPath{fill:#333333;}#mermaid-svg-s50QHVZGtS2ndNcE .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-s50QHVZGtS2ndNcE .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-s50QHVZGtS2ndNcE .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-s50QHVZGtS2ndNcE .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-s50QHVZGtS2ndNcE .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-s50QHVZGtS2ndNcE .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-s50QHVZGtS2ndNcE .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-s50QHVZGtS2ndNcE .cluster text{fill:#333;}#mermaid-svg-s50QHVZGtS2ndNcE .cluster span{color:#333;}#mermaid-svg-s50QHVZGtS2ndNcE div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-s50QHVZGtS2ndNcE .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-s50QHVZGtS2ndNcE rect.text{fill:none;stroke-width:0;}#mermaid-svg-s50QHVZGtS2ndNcE .icon-shape,#mermaid-svg-s50QHVZGtS2ndNcE .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-s50QHVZGtS2ndNcE .icon-shape p,#mermaid-svg-s50QHVZGtS2ndNcE .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-s50QHVZGtS2ndNcE .icon-shape .label rect,#mermaid-svg-s50QHVZGtS2ndNcE .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-s50QHVZGtS2ndNcE .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-s50QHVZGtS2ndNcE .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-s50QHVZGtS2ndNcE :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
TP8 剥离后
默认不解析
控制器 DocBlock 注释
路由丢失
PHP 8 原生 Attribute
think-annotation 扩展
注册路由
TP6 内置注解路由
控制器 DocBlock 注释
框架内置解析器
注册路由
2. 环境准备与扩展安装
2.1 版本要求
| PHP | >= 8.0(推荐 8.1+) | 原生 Attribute 需要 8.0+;8.1+ 对命名参数、只读属性支持更完整 |
| ThinkPHP | 8.0.x | 需确认 composer.json 中 topthink/framework 为 ^8.0 |
| topthink/think-annotation | 与 TP8 对应的最新版本 | 建议锁定小版本,避免扩展升级带来的语法变动 |
先确认当前框架版本,避免装错扩展:
composer show topthink/framework | grep versions
php think version
2.2 安装扩展
在项目根目录执行:
composer require topthink/think-annotation
安装完成后,扩展会自动注册服务提供者(依赖 Composer 的 extra.think.services 自动发现机制)。若你的项目关闭了自动发现(config/app.php 中 'auto_discover' => false),需要在 services 中手动加入:
// config/app.php
return [
// …
'services' => [
\\think\\annotation\\Service::class,
],
];
2.3 验证安装是否成功
安装后先做一次最小验证,确认扩展类可以被自动加载:
php -r "var_dump(class_exists('think\\\\annotation\\\\route\\\\AnnotationRoute'));"
输出 bool(true) 说明扩展已就位。如果输出 bool(false),通常是以下原因:
- Composer 自动加载未刷新,执行 composer dump-autoload;
- 扩展装到了错误的项目目录(多项目共用 vendor 时常见);
- services 未注册且关闭了自动发现。
注意:这一步只验证"类存在",不代表注解已经生效,真正的开关在下一节的 AnnotationRoute::init()。
安装与验证的完整时序如下,可以对照检查每一步是否走通:
"TP8 应用""vendor 自动加载""Composer""开发者""TP8 应用""vendor 自动加载""Composer""开发者"#mermaid-svg-qulOjB6fwg9RnSBa{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-qulOjB6fwg9RnSBa .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-qulOjB6fwg9RnSBa .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-qulOjB6fwg9RnSBa .error-icon{fill:#552222;}#mermaid-svg-qulOjB6fwg9RnSBa .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-qulOjB6fwg9RnSBa .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-qulOjB6fwg9RnSBa .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-qulOjB6fwg9RnSBa .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-qulOjB6fwg9RnSBa .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-qulOjB6fwg9RnSBa .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-qulOjB6fwg9RnSBa .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-qulOjB6fwg9RnSBa .marker{fill:#333333;stroke:#333333;}#mermaid-svg-qulOjB6fwg9RnSBa .marker.cross{stroke:#333333;}#mermaid-svg-qulOjB6fwg9RnSBa svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-qulOjB6fwg9RnSBa p{margin:0;}#mermaid-svg-qulOjB6fwg9RnSBa .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-qulOjB6fwg9RnSBa text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-qulOjB6fwg9RnSBa .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-qulOjB6fwg9RnSBa .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-qulOjB6fwg9RnSBa .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-qulOjB6fwg9RnSBa .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-qulOjB6fwg9RnSBa #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-qulOjB6fwg9RnSBa .sequenceNumber{fill:white;}#mermaid-svg-qulOjB6fwg9RnSBa #sequencenumber{fill:#333;}#mermaid-svg-qulOjB6fwg9RnSBa #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-qulOjB6fwg9RnSBa .messageText{fill:#333;stroke:none;}#mermaid-svg-qulOjB6fwg9RnSBa .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-qulOjB6fwg9RnSBa .labelText,#mermaid-svg-qulOjB6fwg9RnSBa .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-qulOjB6fwg9RnSBa .loopText,#mermaid-svg-qulOjB6fwg9RnSBa .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-qulOjB6fwg9RnSBa .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-qulOjB6fwg9RnSBa .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-qulOjB6fwg9RnSBa .noteText,#mermaid-svg-qulOjB6fwg9RnSBa .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-qulOjB6fwg9RnSBa .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-qulOjB6fwg9RnSBa .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-qulOjB6fwg9RnSBa .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-qulOjB6fwg9RnSBa .actorPopupMenu{position:absolute;}#mermaid-svg-qulOjB6fwg9RnSBa .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-qulOjB6fwg9RnSBa .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-qulOjB6fwg9RnSBa .actor-man circle,#mermaid-svg-qulOjB6fwg9RnSBa line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-qulOjB6fwg9RnSBa :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}"此时注解仍未生效,需调用 init()""composer require topthink/think-annotation""写入依赖并生成 autoload""注册 think\\\\annotation\\\\Service""php -r class_exists(…) 验证""bool(true) 类已就位"
3. 关键一步:调用 AnnotationRoute::init()
这是最容易踩坑的地方。扩展安装完并不代表注解生效,必须在路由定义文件中调用 AnnotationRoute::init() 完成初始化,否则所有注解都会被 PHP 解析器当作普通注释忽略掉——注意,Attribute 在 PHP 层面是"元数据",不调用扫描器就永远不会被读取。
在 route/app.php(或对应应用的 route/ 目录下的路由文件)中加入:
<?php
// route/app.php
use think\\facade\\Route;
use think\\annotation\\route\\AnnotationRoute;
// 初始化注解路由,必须放在其它路由注册之前
AnnotationRoute::init();
Route::get('hello', function () {
return 'hello thinkphp8';
});
3.1 init() 到底做了什么
init() 的作用是向路由系统注册注解扫描器,让框架在路由解析阶段去读取控制器上的 Attribute。具体来说它完成了三件事:
3.2 顺序为什么重要
顺序很重要:它应当位于路由文件的最前面,早于任何依赖注解的路由注册。如果 init() 写在文件末尾,前面已经执行过的路由注册不会触发注解扫描,表现为"部分注解路由生效、部分不生效",非常难排查。
3.3 多应用模式下的放置位置
多应用模式下,每个应用都有自己的 route/ 目录。推荐做法是在全局公共路由文件(如 route/app.php)中调用一次 init(),而不是每个应用各调一次,避免重复注册扫描器:
<?php
// route/app.php —— 全局生效,各应用共享
use think\\annotation\\route\\AnnotationRoute;
AnnotationRoute::init();
如果确实需要在应用内单独初始化,请确保同一请求生命周期内只调用一次。
从请求进入框架到注解路由被命中的完整链路如下:
"控制器 Attribute""注解扫描器""路由文件 route/app.php""客户端""控制器 Attribute""注解扫描器""路由文件 route/app.php""客户端"#mermaid-svg-bAd040EW2uyMFsQE{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-bAd040EW2uyMFsQE .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-bAd040EW2uyMFsQE .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-bAd040EW2uyMFsQE .error-icon{fill:#552222;}#mermaid-svg-bAd040EW2uyMFsQE .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-bAd040EW2uyMFsQE .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-bAd040EW2uyMFsQE .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-bAd040EW2uyMFsQE .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-bAd040EW2uyMFsQE .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-bAd040EW2uyMFsQE .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-bAd040EW2uyMFsQE .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-bAd040EW2uyMFsQE .marker{fill:#333333;stroke:#333333;}#mermaid-svg-bAd040EW2uyMFsQE .marker.cross{stroke:#333333;}#mermaid-svg-bAd040EW2uyMFsQE svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-bAd040EW2uyMFsQE p{margin:0;}#mermaid-svg-bAd040EW2uyMFsQE .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-bAd040EW2uyMFsQE text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-bAd040EW2uyMFsQE .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-bAd040EW2uyMFsQE .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-bAd040EW2uyMFsQE .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-bAd040EW2uyMFsQE .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-bAd040EW2uyMFsQE #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-bAd040EW2uyMFsQE .sequenceNumber{fill:white;}#mermaid-svg-bAd040EW2uyMFsQE #sequencenumber{fill:#333;}#mermaid-svg-bAd040EW2uyMFsQE #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-bAd040EW2uyMFsQE .messageText{fill:#333;stroke:none;}#mermaid-svg-bAd040EW2uyMFsQE .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-bAd040EW2uyMFsQE .labelText,#mermaid-svg-bAd040EW2uyMFsQE .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-bAd040EW2uyMFsQE .loopText,#mermaid-svg-bAd040EW2uyMFsQE .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-bAd040EW2uyMFsQE .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-bAd040EW2uyMFsQE .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-bAd040EW2uyMFsQE .noteText,#mermaid-svg-bAd040EW2uyMFsQE .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-bAd040EW2uyMFsQE .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-bAd040EW2uyMFsQE .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-bAd040EW2uyMFsQE .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-bAd040EW2uyMFsQE .actorPopupMenu{position:absolute;}#mermaid-svg-bAd040EW2uyMFsQE .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-bAd040EW2uyMFsQE .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-bAd040EW2uyMFsQE .actor-man circle,#mermaid-svg-bAd040EW2uyMFsQE line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-bAd040EW2uyMFsQE :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}"发起 HTTP 请求""AnnotationRoute::init() 已注册扫描器""读取"返回路由规则、方法、中间件""匹配到对应路由""调用控制器方法""返回响应"
4. PHP 8 原生 Attribute 写法
TP8 的注解路由扩展已经全面转向 PHP 8 原生 #[Attribute] 语法,不再推荐使用 /** @Route(…) */ 这种文档块注释。相比旧写法,原生 Attribute 有几个明显优势:
- 由 PHP 引擎在编译期解析,语法错误会直接暴露,而不是静默失效;
- IDE 支持更好,参数有类型提示和自动补全;
- 支持命名参数,可读性更强,参数顺序不再敏感。
4.0 新旧写法对照
// ❌ TP6 旧写法:文档块注释,TP8 中默认不解析
/**
* @Route("user/list", method="GET", name="user.list")
*/
public function index() {}
// ✅ TP8 新写法:原生 Attribute
#[Route('list', method: 'GET', name: 'user.list')]
public function index() {}
注意新写法中路由规则只写 list,前缀由类上的 #[Group('user')] 提供,这是与旧写法最容易混淆的一点。
4.1 控制器类上声明路由前缀
<?php
namespace app\\controller;
use think\\annotation\\route\\Group;
use think\\annotation\\route\\Route;
#[Group('user')]
class User
{
// …
}
#[Group('user')] 等价于给该控制器下所有方法的路由加上 user/ 前缀。几个细节:
- Group 支持第二个参数指定该组统一中间件:#[Group('user', middleware: ['auth'])];
- 类上不写 Group 时,方法上的路由规则就是完整路径;
- 一个控制器只能有一个 Group,重复声明以最后一个为准(不建议依赖此行为)。
4.2 方法上声明路由、请求方法、中间件与路由名
<?php
namespace app\\controller;
use think\\annotation\\route\\Group;
use think\\annotation\\route\\Route;
#[Group('user')]
class User
{
#[Route('list', method: 'GET', name: 'user.list')]
public function index()
{
return json(['list' => []]);
}
#[Route('detail/:id', method: 'GET', middleware: ['auth'], name: 'user.detail')]
public function read(int $id)
{
return json(['id' => $id]);
}
#[Route('save', method: 'POST', middleware: ['auth', 'log'])]
public function save()
{
return json(['code' => 0]);
}
#[Route('update/:id', method: ['PUT', 'PATCH'], middleware: ['auth'])]
public function update(int $id)
{
return json(['id' => $id, 'updated' => true]);
}
}
要点说明:
- 第一个参数是路由规则,支持 :id 这类变量占位,也支持 :id\\d+ 这类正则约束;
- method 指定请求方法,可传字符串或数组(如 ['GET', 'POST']),不传则默认匹配任意方法;
- middleware 绑定中间件,支持数组,按声明顺序执行;
- name 为路由命名,便于 Route::buildUrl('user.detail', ['id' => 1]) 反向生成 URL;
- 方法参数 int $id 会自动从路由变量注入,类型不匹配时框架会抛类型错误。
4.3 使用通用 Annotation 声明
除了 #[Route],扩展还提供了通用的 #[Annotation],用于一次性声明多个路由属性:
<?php
namespace app\\controller;
use think\\annotation\\route\\Annotation;
class Article
{
#[Annotation([
'rule' => 'article/:id',
'method' => 'GET',
'middleware' => ['auth'],
'name' => 'article.read',
])]
public function read(int $id)
{
return json(['id' => $id]);
}
}
这种写法在需要动态拼装路由参数、或从配置中读取中间件时更灵活。例如从配置读取中间件列表:
#[Annotation([
'rule' => 'article/:id',
'method' => 'GET',
'middleware' => \\think\\facade\\Config::get('route.article_middleware', []),
])]
4.4 两种写法怎么选
| 常规 CRUD 路由 | #[Route],参数直观、IDE 提示好 |
| 需要动态拼装参数 | #[Annotation],数组形式更灵活 |
| 需要统一组中间件 | 类上 #[Group] + 方法上 #[Route] |
| 从配置读取中间件 | #[Annotation] |
#[Group] 与 #[Route] 的层级关系可以这样理解:
#mermaid-svg-bfRb8a60yf4GLVg2{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-bfRb8a60yf4GLVg2 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-bfRb8a60yf4GLVg2 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-bfRb8a60yf4GLVg2 .error-icon{fill:#552222;}#mermaid-svg-bfRb8a60yf4GLVg2 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-bfRb8a60yf4GLVg2 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-bfRb8a60yf4GLVg2 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-bfRb8a60yf4GLVg2 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-bfRb8a60yf4GLVg2 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-bfRb8a60yf4GLVg2 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-bfRb8a60yf4GLVg2 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-bfRb8a60yf4GLVg2 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-bfRb8a60yf4GLVg2 .marker.cross{stroke:#333333;}#mermaid-svg-bfRb8a60yf4GLVg2 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-bfRb8a60yf4GLVg2 p{margin:0;}#mermaid-svg-bfRb8a60yf4GLVg2 .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-bfRb8a60yf4GLVg2 .cluster-label text{fill:#333;}#mermaid-svg-bfRb8a60yf4GLVg2 .cluster-label span{color:#333;}#mermaid-svg-bfRb8a60yf4GLVg2 .cluster-label span p{background-color:transparent;}#mermaid-svg-bfRb8a60yf4GLVg2 .label text,#mermaid-svg-bfRb8a60yf4GLVg2 span{fill:#333;color:#333;}#mermaid-svg-bfRb8a60yf4GLVg2 .node rect,#mermaid-svg-bfRb8a60yf4GLVg2 .node circle,#mermaid-svg-bfRb8a60yf4GLVg2 .node ellipse,#mermaid-svg-bfRb8a60yf4GLVg2 .node polygon,#mermaid-svg-bfRb8a60yf4GLVg2 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-bfRb8a60yf4GLVg2 .rough-node .label text,#mermaid-svg-bfRb8a60yf4GLVg2 .node .label text,#mermaid-svg-bfRb8a60yf4GLVg2 .image-shape .label,#mermaid-svg-bfRb8a60yf4GLVg2 .icon-shape .label{text-anchor:middle;}#mermaid-svg-bfRb8a60yf4GLVg2 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-bfRb8a60yf4GLVg2 .rough-node .label,#mermaid-svg-bfRb8a60yf4GLVg2 .node .label,#mermaid-svg-bfRb8a60yf4GLVg2 .image-shape .label,#mermaid-svg-bfRb8a60yf4GLVg2 .icon-shape .label{text-align:center;}#mermaid-svg-bfRb8a60yf4GLVg2 .node.clickable{cursor:pointer;}#mermaid-svg-bfRb8a60yf4GLVg2 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-bfRb8a60yf4GLVg2 .arrowheadPath{fill:#333333;}#mermaid-svg-bfRb8a60yf4GLVg2 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-bfRb8a60yf4GLVg2 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-bfRb8a60yf4GLVg2 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bfRb8a60yf4GLVg2 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-bfRb8a60yf4GLVg2 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bfRb8a60yf4GLVg2 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-bfRb8a60yf4GLVg2 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-bfRb8a60yf4GLVg2 .cluster text{fill:#333;}#mermaid-svg-bfRb8a60yf4GLVg2 .cluster span{color:#333;}#mermaid-svg-bfRb8a60yf4GLVg2 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-bfRb8a60yf4GLVg2 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-bfRb8a60yf4GLVg2 rect.text{fill:none;stroke-width:0;}#mermaid-svg-bfRb8a60yf4GLVg2 .icon-shape,#mermaid-svg-bfRb8a60yf4GLVg2 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bfRb8a60yf4GLVg2 .icon-shape p,#mermaid-svg-bfRb8a60yf4GLVg2 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-bfRb8a60yf4GLVg2 .icon-shape .label rect,#mermaid-svg-bfRb8a60yf4GLVg2 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bfRb8a60yf4GLVg2 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-bfRb8a60yf4GLVg2 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-bfRb8a60yf4GLVg2 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
#[Group('user')] 类级前缀
#[Route('list')] → user/list
#[Route('detail/:id')] → user/detail/:id
#[Route('save')] → user/save
method: GET / name: user.list
method: GET / middleware: auth
method: POST / middleware: auth, log
4.5 Attribute 从声明到生效的完整生命周期
理解下面这条链路,能帮你判断问题出在"写错注解"还是"没被扫描":
#mermaid-svg-FQF24g8I56Cxx5BI{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-FQF24g8I56Cxx5BI .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-FQF24g8I56Cxx5BI .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-FQF24g8I56Cxx5BI .error-icon{fill:#552222;}#mermaid-svg-FQF24g8I56Cxx5BI .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-FQF24g8I56Cxx5BI .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-FQF24g8I56Cxx5BI .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-FQF24g8I56Cxx5BI .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-FQF24g8I56Cxx5BI .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-FQF24g8I56Cxx5BI .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-FQF24g8I56Cxx5BI .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-FQF24g8I56Cxx5BI .marker{fill:#333333;stroke:#333333;}#mermaid-svg-FQF24g8I56Cxx5BI .marker.cross{stroke:#333333;}#mermaid-svg-FQF24g8I56Cxx5BI svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-FQF24g8I56Cxx5BI p{margin:0;}#mermaid-svg-FQF24g8I56Cxx5BI .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-FQF24g8I56Cxx5BI .cluster-label text{fill:#333;}#mermaid-svg-FQF24g8I56Cxx5BI .cluster-label span{color:#333;}#mermaid-svg-FQF24g8I56Cxx5BI .cluster-label span p{background-color:transparent;}#mermaid-svg-FQF24g8I56Cxx5BI .label text,#mermaid-svg-FQF24g8I56Cxx5BI span{fill:#333;color:#333;}#mermaid-svg-FQF24g8I56Cxx5BI .node rect,#mermaid-svg-FQF24g8I56Cxx5BI .node circle,#mermaid-svg-FQF24g8I56Cxx5BI .node ellipse,#mermaid-svg-FQF24g8I56Cxx5BI .node polygon,#mermaid-svg-FQF24g8I56Cxx5BI .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-FQF24g8I56Cxx5BI .rough-node .label text,#mermaid-svg-FQF24g8I56Cxx5BI .node .label text,#mermaid-svg-FQF24g8I56Cxx5BI .image-shape .label,#mermaid-svg-FQF24g8I56Cxx5BI .icon-shape .label{text-anchor:middle;}#mermaid-svg-FQF24g8I56Cxx5BI .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-FQF24g8I56Cxx5BI .rough-node .label,#mermaid-svg-FQF24g8I56Cxx5BI .node .label,#mermaid-svg-FQF24g8I56Cxx5BI .image-shape .label,#mermaid-svg-FQF24g8I56Cxx5BI .icon-shape .label{text-align:center;}#mermaid-svg-FQF24g8I56Cxx5BI .node.clickable{cursor:pointer;}#mermaid-svg-FQF24g8I56Cxx5BI .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-FQF24g8I56Cxx5BI .arrowheadPath{fill:#333333;}#mermaid-svg-FQF24g8I56Cxx5BI .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-FQF24g8I56Cxx5BI .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-FQF24g8I56Cxx5BI .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-FQF24g8I56Cxx5BI .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-FQF24g8I56Cxx5BI .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-FQF24g8I56Cxx5BI .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-FQF24g8I56Cxx5BI .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-FQF24g8I56Cxx5BI .cluster text{fill:#333;}#mermaid-svg-FQF24g8I56Cxx5BI .cluster span{color:#333;}#mermaid-svg-FQF24g8I56Cxx5BI div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-FQF24g8I56Cxx5BI .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-FQF24g8I56Cxx5BI rect.text{fill:none;stroke-width:0;}#mermaid-svg-FQF24g8I56Cxx5BI .icon-shape,#mermaid-svg-FQF24g8I56Cxx5BI .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-FQF24g8I56Cxx5BI .icon-shape p,#mermaid-svg-FQF24g8I56Cxx5BI .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-FQF24g8I56Cxx5BI .icon-shape .label rect,#mermaid-svg-FQF24g8I56Cxx5BI .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-FQF24g8I56Cxx5BI .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-FQF24g8I56Cxx5BI .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-FQF24g8I56Cxx5BI :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
否
是
否
是
PHP 编译期解析 #[Group] / #[Route]
Attribute 元数据挂到类与方法上
AnnotationRoute::init() 是否调用
元数据无人读取,路由不注册
扫描器遍历 scan 命名空间
类是否在 scan 范围内
该控制器注解被跳过
反射读取 Attribute 参数
转换为路由规则写入路由表
命中请求并调用控制器方法
5. 多应用模式下的命名空间扫描配置
单应用模式下扩展通常能自动推断控制器命名空间,但多应用模式必须显式配置扫描范围,否则注解不会被识别。原因是多应用模式下控制器分散在 app/admin/controller、app/api/controller 等目录,扩展无法安全地猜测哪些命名空间需要扫描。
编辑 config/annotation.php:
<?php
// config/annotation.php
return [
// 需要扫描注解的命名空间列表
'scan' => [
'app\\admin\\controller',
'app\\api\\controller',
'app\\index\\controller',
],
];
注意几点:
- 列表里写的是命名空间,不是目录路径,写 app/admin/controller 是无效的;
- 每个应用都要单独列出,漏写哪个应用,哪个应用的注解就全部失效;
- 修改配置后必须清理缓存,否则读到的仍是旧配置;
- 命名空间末尾不要带反斜杠,app\\admin\\controller\\ 与 app\\admin\\controller 在部分版本中行为不一致。
5.1 扫描范围过大的性能影响
scan 列表中的每个命名空间都会被反射遍历。如果误写成 app 这种顶层命名空间,会把 model、service、validate 等全部类都扫一遍,启动耗时明显上升。建议精确到 controller 层,必要时再按应用拆分。
5.2 多应用配置示例
<?php
// config/annotation.php
return [
'scan' => [
'app\\admin\\controller',
'app\\api\\controller',
'app\\index\\controller',
'app\\mobile\\controller',
],
];
多应用模式下,scan 配置与各应用控制器的对应关系如下:
#mermaid-svg-9z7Ts5MzN6zbQCDF{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-9z7Ts5MzN6zbQCDF .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-9z7Ts5MzN6zbQCDF .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-9z7Ts5MzN6zbQCDF .error-icon{fill:#552222;}#mermaid-svg-9z7Ts5MzN6zbQCDF .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-9z7Ts5MzN6zbQCDF .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-9z7Ts5MzN6zbQCDF .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-9z7Ts5MzN6zbQCDF .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-9z7Ts5MzN6zbQCDF .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-9z7Ts5MzN6zbQCDF .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-9z7Ts5MzN6zbQCDF .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-9z7Ts5MzN6zbQCDF .marker{fill:#333333;stroke:#333333;}#mermaid-svg-9z7Ts5MzN6zbQCDF .marker.cross{stroke:#333333;}#mermaid-svg-9z7Ts5MzN6zbQCDF svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-9z7Ts5MzN6zbQCDF p{margin:0;}#mermaid-svg-9z7Ts5MzN6zbQCDF .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-9z7Ts5MzN6zbQCDF .cluster-label text{fill:#333;}#mermaid-svg-9z7Ts5MzN6zbQCDF .cluster-label span{color:#333;}#mermaid-svg-9z7Ts5MzN6zbQCDF .cluster-label span p{background-color:transparent;}#mermaid-svg-9z7Ts5MzN6zbQCDF .label text,#mermaid-svg-9z7Ts5MzN6zbQCDF span{fill:#333;color:#333;}#mermaid-svg-9z7Ts5MzN6zbQCDF .node rect,#mermaid-svg-9z7Ts5MzN6zbQCDF .node circle,#mermaid-svg-9z7Ts5MzN6zbQCDF .node ellipse,#mermaid-svg-9z7Ts5MzN6zbQCDF .node polygon,#mermaid-svg-9z7Ts5MzN6zbQCDF .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-9z7Ts5MzN6zbQCDF .rough-node .label text,#mermaid-svg-9z7Ts5MzN6zbQCDF .node .label text,#mermaid-svg-9z7Ts5MzN6zbQCDF .image-shape .label,#mermaid-svg-9z7Ts5MzN6zbQCDF .icon-shape .label{text-anchor:middle;}#mermaid-svg-9z7Ts5MzN6zbQCDF .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-9z7Ts5MzN6zbQCDF .rough-node .label,#mermaid-svg-9z7Ts5MzN6zbQCDF .node .label,#mermaid-svg-9z7Ts5MzN6zbQCDF .image-shape .label,#mermaid-svg-9z7Ts5MzN6zbQCDF .icon-shape .label{text-align:center;}#mermaid-svg-9z7Ts5MzN6zbQCDF .node.clickable{cursor:pointer;}#mermaid-svg-9z7Ts5MzN6zbQCDF .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-9z7Ts5MzN6zbQCDF .arrowheadPath{fill:#333333;}#mermaid-svg-9z7Ts5MzN6zbQCDF .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-9z7Ts5MzN6zbQCDF .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-9z7Ts5MzN6zbQCDF .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9z7Ts5MzN6zbQCDF .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-9z7Ts5MzN6zbQCDF .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9z7Ts5MzN6zbQCDF .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-9z7Ts5MzN6zbQCDF .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-9z7Ts5MzN6zbQCDF .cluster text{fill:#333;}#mermaid-svg-9z7Ts5MzN6zbQCDF .cluster span{color:#333;}#mermaid-svg-9z7Ts5MzN6zbQCDF div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-9z7Ts5MzN6zbQCDF .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-9z7Ts5MzN6zbQCDF rect.text{fill:none;stroke-width:0;}#mermaid-svg-9z7Ts5MzN6zbQCDF .icon-shape,#mermaid-svg-9z7Ts5MzN6zbQCDF .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9z7Ts5MzN6zbQCDF .icon-shape p,#mermaid-svg-9z7Ts5MzN6zbQCDF .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-9z7Ts5MzN6zbQCDF .icon-shape .label rect,#mermaid-svg-9z7Ts5MzN6zbQCDF .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9z7Ts5MzN6zbQCDF .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-9z7Ts5MzN6zbQCDF .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-9z7Ts5MzN6zbQCDF :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
config/annotation.php 的 scan 列表
app\\admin\\controller
app\\api\\controller
app\\index\\controller
admin 应用注解生效
api 应用注解生效
index 应用注解生效
未列入 scan 的命名空间
该应用注解全部失效
6. 缓存清理:别忘了 –all
注解解析结果会被缓存以提升性能,因此每次修改注解或扫描配置后都要清缓存:
php think clear –all
如果只清了路由缓存而没清注解缓存,会出现"代码明明改了、路由还是旧的"这种诡异现象。建议把这条命令写进部署脚本。
缓存与生效之间的关系可以这样理解——注解改动只有经过"清缓存"这一步才会真正进入运行时:
#mermaid-svg-JGlJ8iB8Cd1JKXtj{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-JGlJ8iB8Cd1JKXtj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-JGlJ8iB8Cd1JKXtj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-JGlJ8iB8Cd1JKXtj .error-icon{fill:#552222;}#mermaid-svg-JGlJ8iB8Cd1JKXtj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-JGlJ8iB8Cd1JKXtj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-JGlJ8iB8Cd1JKXtj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-JGlJ8iB8Cd1JKXtj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-JGlJ8iB8Cd1JKXtj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-JGlJ8iB8Cd1JKXtj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-JGlJ8iB8Cd1JKXtj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-JGlJ8iB8Cd1JKXtj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-JGlJ8iB8Cd1JKXtj .marker.cross{stroke:#333333;}#mermaid-svg-JGlJ8iB8Cd1JKXtj svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-JGlJ8iB8Cd1JKXtj p{margin:0;}#mermaid-svg-JGlJ8iB8Cd1JKXtj .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-JGlJ8iB8Cd1JKXtj .cluster-label text{fill:#333;}#mermaid-svg-JGlJ8iB8Cd1JKXtj .cluster-label span{color:#333;}#mermaid-svg-JGlJ8iB8Cd1JKXtj .cluster-label span p{background-color:transparent;}#mermaid-svg-JGlJ8iB8Cd1JKXtj .label text,#mermaid-svg-JGlJ8iB8Cd1JKXtj span{fill:#333;color:#333;}#mermaid-svg-JGlJ8iB8Cd1JKXtj .node rect,#mermaid-svg-JGlJ8iB8Cd1JKXtj .node circle,#mermaid-svg-JGlJ8iB8Cd1JKXtj .node ellipse,#mermaid-svg-JGlJ8iB8Cd1JKXtj .node polygon,#mermaid-svg-JGlJ8iB8Cd1JKXtj .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-JGlJ8iB8Cd1JKXtj .rough-node .label text,#mermaid-svg-JGlJ8iB8Cd1JKXtj .node .label text,#mermaid-svg-JGlJ8iB8Cd1JKXtj .image-shape .label,#mermaid-svg-JGlJ8iB8Cd1JKXtj .icon-shape .label{text-anchor:middle;}#mermaid-svg-JGlJ8iB8Cd1JKXtj .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-JGlJ8iB8Cd1JKXtj .rough-node .label,#mermaid-svg-JGlJ8iB8Cd1JKXtj .node .label,#mermaid-svg-JGlJ8iB8Cd1JKXtj .image-shape .label,#mermaid-svg-JGlJ8iB8Cd1JKXtj .icon-shape .label{text-align:center;}#mermaid-svg-JGlJ8iB8Cd1JKXtj .node.clickable{cursor:pointer;}#mermaid-svg-JGlJ8iB8Cd1JKXtj .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-JGlJ8iB8Cd1JKXtj .arrowheadPath{fill:#333333;}#mermaid-svg-JGlJ8iB8Cd1JKXtj .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-JGlJ8iB8Cd1JKXtj .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-JGlJ8iB8Cd1JKXtj .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-JGlJ8iB8Cd1JKXtj .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-JGlJ8iB8Cd1JKXtj .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-JGlJ8iB8Cd1JKXtj .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-JGlJ8iB8Cd1JKXtj .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-JGlJ8iB8Cd1JKXtj .cluster text{fill:#333;}#mermaid-svg-JGlJ8iB8Cd1JKXtj .cluster span{color:#333;}#mermaid-svg-JGlJ8iB8Cd1JKXtj div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-JGlJ8iB8Cd1JKXtj .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-JGlJ8iB8Cd1JKXtj rect.text{fill:none;stroke-width:0;}#mermaid-svg-JGlJ8iB8Cd1JKXtj .icon-shape,#mermaid-svg-JGlJ8iB8Cd1JKXtj .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-JGlJ8iB8Cd1JKXtj .icon-shape p,#mermaid-svg-JGlJ8iB8Cd1JKXtj .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-JGlJ8iB8Cd1JKXtj .icon-shape .label rect,#mermaid-svg-JGlJ8iB8Cd1JKXtj .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-JGlJ8iB8Cd1JKXtj .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-JGlJ8iB8Cd1JKXtj .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-JGlJ8iB8Cd1JKXtj :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
否
是
修改控制器 Attribute
注解缓存仍为旧结果
修改 config/annotation.php
是否执行 php think clear –all
路由表现与代码不一致
重新扫描并写入缓存
route:list 可见新路由
7. 完整开发流程闭环
把上面的步骤串起来,一个可复用的流程如下:
#mermaid-svg-sX6J50N0WBfdaiml{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-sX6J50N0WBfdaiml .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-sX6J50N0WBfdaiml .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-sX6J50N0WBfdaiml .error-icon{fill:#552222;}#mermaid-svg-sX6J50N0WBfdaiml .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-sX6J50N0WBfdaiml .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-sX6J50N0WBfdaiml .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-sX6J50N0WBfdaiml .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-sX6J50N0WBfdaiml .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-sX6J50N0WBfdaiml .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-sX6J50N0WBfdaiml .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-sX6J50N0WBfdaiml .marker{fill:#333333;stroke:#333333;}#mermaid-svg-sX6J50N0WBfdaiml .marker.cross{stroke:#333333;}#mermaid-svg-sX6J50N0WBfdaiml svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-sX6J50N0WBfdaiml p{margin:0;}#mermaid-svg-sX6J50N0WBfdaiml .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-sX6J50N0WBfdaiml .cluster-label text{fill:#333;}#mermaid-svg-sX6J50N0WBfdaiml .cluster-label span{color:#333;}#mermaid-svg-sX6J50N0WBfdaiml .cluster-label span p{background-color:transparent;}#mermaid-svg-sX6J50N0WBfdaiml .label text,#mermaid-svg-sX6J50N0WBfdaiml span{fill:#333;color:#333;}#mermaid-svg-sX6J50N0WBfdaiml .node rect,#mermaid-svg-sX6J50N0WBfdaiml .node circle,#mermaid-svg-sX6J50N0WBfdaiml .node ellipse,#mermaid-svg-sX6J50N0WBfdaiml .node polygon,#mermaid-svg-sX6J50N0WBfdaiml .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-sX6J50N0WBfdaiml .rough-node .label text,#mermaid-svg-sX6J50N0WBfdaiml .node .label text,#mermaid-svg-sX6J50N0WBfdaiml .image-shape .label,#mermaid-svg-sX6J50N0WBfdaiml .icon-shape .label{text-anchor:middle;}#mermaid-svg-sX6J50N0WBfdaiml .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-sX6J50N0WBfdaiml .rough-node .label,#mermaid-svg-sX6J50N0WBfdaiml .node .label,#mermaid-svg-sX6J50N0WBfdaiml .image-shape .label,#mermaid-svg-sX6J50N0WBfdaiml .icon-shape .label{text-align:center;}#mermaid-svg-sX6J50N0WBfdaiml .node.clickable{cursor:pointer;}#mermaid-svg-sX6J50N0WBfdaiml .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-sX6J50N0WBfdaiml .arrowheadPath{fill:#333333;}#mermaid-svg-sX6J50N0WBfdaiml .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-sX6J50N0WBfdaiml .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-sX6J50N0WBfdaiml .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-sX6J50N0WBfdaiml .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-sX6J50N0WBfdaiml .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-sX6J50N0WBfdaiml .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-sX6J50N0WBfdaiml .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-sX6J50N0WBfdaiml .cluster text{fill:#333;}#mermaid-svg-sX6J50N0WBfdaiml .cluster span{color:#333;}#mermaid-svg-sX6J50N0WBfdaiml div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-sX6J50N0WBfdaiml .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-sX6J50N0WBfdaiml rect.text{fill:none;stroke-width:0;}#mermaid-svg-sX6J50N0WBfdaiml .icon-shape,#mermaid-svg-sX6J50N0WBfdaiml .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-sX6J50N0WBfdaiml .icon-shape p,#mermaid-svg-sX6J50N0WBfdaiml .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-sX6J50N0WBfdaiml .icon-shape .label rect,#mermaid-svg-sX6J50N0WBfdaiml .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-sX6J50N0WBfdaiml .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-sX6J50N0WBfdaiml .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-sX6J50N0WBfdaiml :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
是
否
composer require topthink/think-annotation
route/app.php 调用 AnnotationRoute::init()
控制器使用 #[Group] / #[Route] 声明
是否多应用模式
config/annotation.php 配置 scan 命名空间
直接使用
php think clear –all
php think route:list 验证路由
访问接口联调
对应的命令序列:
# 1. 安装扩展
composer require topthink/think-annotation
# 2. 编写路由与控制器注解(见上文示例)
# 3. 清理全部缓存
php think clear –all
# 4. 查看路由是否注册成功
php think route:list
php think route:list 是排查注解路由问题最有效的手段:如果注解没生效,这里根本看不到对应路由;如果看到了但访问 404,问题多半在请求方法或中间件上。
8. 常见问题排查
| 注解完全不生效 | 未调用 AnnotationRoute::init() | 在路由文件首行调用 |
| 多应用下部分应用失效 | scan 未声明该应用命名空间 | 补全 config/annotation.php |
| 改了注解但路由没变 | 注解缓存未清理 | php think clear –all |
| 报 Attribute class not found | 扩展未安装或未自动注册 | 检查 composer 与 services 配置 |
| 路由存在但 404 | 请求方法不匹配 | 核对 method 参数 |
遇到问题时,可以按下面的决策路径快速定位原因:
#mermaid-svg-5bJ7QDkJ7n1QgKnv{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-5bJ7QDkJ7n1QgKnv .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-5bJ7QDkJ7n1QgKnv .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-5bJ7QDkJ7n1QgKnv .error-icon{fill:#552222;}#mermaid-svg-5bJ7QDkJ7n1QgKnv .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-5bJ7QDkJ7n1QgKnv .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-5bJ7QDkJ7n1QgKnv .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-5bJ7QDkJ7n1QgKnv .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-5bJ7QDkJ7n1QgKnv .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-5bJ7QDkJ7n1QgKnv .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-5bJ7QDkJ7n1QgKnv .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-5bJ7QDkJ7n1QgKnv .marker{fill:#333333;stroke:#333333;}#mermaid-svg-5bJ7QDkJ7n1QgKnv .marker.cross{stroke:#333333;}#mermaid-svg-5bJ7QDkJ7n1QgKnv svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-5bJ7QDkJ7n1QgKnv p{margin:0;}#mermaid-svg-5bJ7QDkJ7n1QgKnv .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-5bJ7QDkJ7n1QgKnv .cluster-label text{fill:#333;}#mermaid-svg-5bJ7QDkJ7n1QgKnv .cluster-label span{color:#333;}#mermaid-svg-5bJ7QDkJ7n1QgKnv .cluster-label span p{background-color:transparent;}#mermaid-svg-5bJ7QDkJ7n1QgKnv .label text,#mermaid-svg-5bJ7QDkJ7n1QgKnv span{fill:#333;color:#333;}#mermaid-svg-5bJ7QDkJ7n1QgKnv .node rect,#mermaid-svg-5bJ7QDkJ7n1QgKnv .node circle,#mermaid-svg-5bJ7QDkJ7n1QgKnv .node ellipse,#mermaid-svg-5bJ7QDkJ7n1QgKnv .node polygon,#mermaid-svg-5bJ7QDkJ7n1QgKnv .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-5bJ7QDkJ7n1QgKnv .rough-node .label text,#mermaid-svg-5bJ7QDkJ7n1QgKnv .node .label text,#mermaid-svg-5bJ7QDkJ7n1QgKnv .image-shape .label,#mermaid-svg-5bJ7QDkJ7n1QgKnv .icon-shape .label{text-anchor:middle;}#mermaid-svg-5bJ7QDkJ7n1QgKnv .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-5bJ7QDkJ7n1QgKnv .rough-node .label,#mermaid-svg-5bJ7QDkJ7n1QgKnv .node .label,#mermaid-svg-5bJ7QDkJ7n1QgKnv .image-shape .label,#mermaid-svg-5bJ7QDkJ7n1QgKnv .icon-shape .label{text-align:center;}#mermaid-svg-5bJ7QDkJ7n1QgKnv .node.clickable{cursor:pointer;}#mermaid-svg-5bJ7QDkJ7n1QgKnv .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-5bJ7QDkJ7n1QgKnv .arrowheadPath{fill:#333333;}#mermaid-svg-5bJ7QDkJ7n1QgKnv .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-5bJ7QDkJ7n1QgKnv .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-5bJ7QDkJ7n1QgKnv .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-5bJ7QDkJ7n1QgKnv .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-5bJ7QDkJ7n1QgKnv .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-5bJ7QDkJ7n1QgKnv .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-5bJ7QDkJ7n1QgKnv .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-5bJ7QDkJ7n1QgKnv .cluster text{fill:#333;}#mermaid-svg-5bJ7QDkJ7n1QgKnv .cluster span{color:#333;}#mermaid-svg-5bJ7QDkJ7n1QgKnv div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-5bJ7QDkJ7n1QgKnv .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-5bJ7QDkJ7n1QgKnv rect.text{fill:none;stroke-width:0;}#mermaid-svg-5bJ7QDkJ7n1QgKnv .icon-shape,#mermaid-svg-5bJ7QDkJ7n1QgKnv .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-5bJ7QDkJ7n1QgKnv .icon-shape p,#mermaid-svg-5bJ7QDkJ7n1QgKnv .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-5bJ7QDkJ7n1QgKnv .icon-shape .label rect,#mermaid-svg-5bJ7QDkJ7n1QgKnv .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-5bJ7QDkJ7n1QgKnv .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-5bJ7QDkJ7n1QgKnv .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-5bJ7QDkJ7n1QgKnv :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
否
是
是
否
是
否
否
是
否
是
是
否
注解路由不生效
route 文件是否调用 AnnotationRoute::init()
补上 init(),放在路由注册之前
是否多应用模式
scan 是否包含该应用命名空间
补全 config/annotation.php 的 scan
是否执行过 php think clear –all
清理全部缓存后重试
route:list 中是否存在该路由
检查 Attribute 类是否引入正确
访问是否 404
核对 method 与中间件配置
路由正常
9. 小结
TP8 的注解路由虽然被移出核心,但借助 topthink/think-annotation 与 PHP 8 原生 Attribute,实际体验比旧式文档块注释更好:语法更严格、IDE 更友好、错误更早暴露。需要记住的核心只有三点:
把这套流程固化到项目模板里,后续新增控制器就只需要专注写业务注解了。
网硕互联帮助中心



评论前必须登录!
注册