结论是ThinkPHP 7.0开启强制路由后未放行静态资源路径导致404,需在route/app.php顶部用Route::rule()显式匹配js/css等后缀并关闭自动补后缀,同时确保Web服务器正确将请求转发至/public/index.php。

直接说结论:不是资源文件丢了,是 ThinkPHP 7.0 开启强制路由后,默认把 /static/xxx.css 这类请求也当成“需要匹配路由”的动态路径,而你没显式放行,框架就直接返回 404 —— 它压根没去磁盘找文件。
为什么加了 /index.php 就能访问静态资源?
因为 /index.php/static/logo.png 是一个合法的 PHP 脚本请求,Web 服务器会交给 PHP 执行;而 ThinkPHP 在入口里对这种带 index.php 的路径有兜底逻辑(比如自动识别为静态转发),但纯路径 /static/logo.png 在强制路由下必须由你手动声明“这是静态的”。
- 现象本质:Nginx/Apache 已正确把请求转给了 index.php,但 ThinkPHP 路由层拦截了它
- 验证方式:curl -I http://yoursite/static/test.js,看响应头有没有
X-Powered-By: ThinkPHP。有 → 框架接管了;没有 → 问题在 Web 服务器重写配置(先别调路由) - 别被“public 目录有文件”骗了——文件存在 ≠ 框架允许你访问它
Route::rule() 显式匹配后缀是最稳的写法
比 Route::alias() 更可控,尤其适合 TP7 的模块化和多应用结构,避免路径映射和部署目录错位。
- 在
route/app.php最顶部(早于所有Route::get())、不要放在闭包或条件判断里 - 写法示例(支持常见前端资源):
Route::rule(':path^.*\.(js|css|png|jpg|jpeg|gif|svg|woff2|ttf|ico|webp)$', 'static/:path', 'GET', ['ext' => ''])
->pattern(['path' => '\S+']);-
['ext' => '']是关键:关闭 ThinkPHP 自动补全 URL 后缀(否则/a.js可能被当成/a.js.html) - 正则中的
\S+确保匹配含斜杠的子路径(如static/css/app.css) - 如果用了 CDN 或动静分离,这个规则仍生效,因为它是 URL 层匹配,不依赖物理目录结构
Web 服务器配置必须配合,否则路由规则根本没机会执行
哪怕路由写得再对,Nginx/Apache 没把请求转给 index.php,一切白搭。TP7 默认入口在 public/index.php,这点和 TP6 一致。
立即学习“PHP免费学习笔记(深入)”;
- Apache:
.htaccess必须存在且生效,内容含标准重写规则;同时确认对应<Directory>块中设置了AllowOverride All - Nginx:
location /块里必须有这行:try_files $uri $uri/ /public/index.php?$query_string;(注意是/public/index.php,不是/index.php) - 宝塔用户:选“ThinkPHP”伪静态模板后,**必须手动打开生成的配置文件,把
/index.php改成/public/index.php**,并确认网站「运行目录」设为/public - 删掉 Nginx 配置里任何
error_page 404或location ~* \.(js|css)$ { try_files $uri =404; }—— 这些会绕过 PHP,让路由规则彻底失效
真正卡住人的地方,往往不是哪条路由写错了,而是你不确定请求到底停在哪一层:是 Nginx 直接 404 了?还是进了 PHP 但被路由层拒了?还是进了路由但匹配失败?每次改完,清空 runtime/cache/,用 curl -I 看响应头,比刷浏览器快十倍。



















