【.htaccess】URL重写、静态资源缓存、安全防护等

在 Apache 服务器环境中,.htaccess("Hypertext Access" 的缩写)是一个分布式配置文件 。它允许你在不修改服务器主配置文件(httpd.conf)的情况下,对当前目录及其所有子目录施加行为控制。

在 PHP 项目中,它的主要用途可以归纳为以下几类:

1. URL 重写(最核心的用途)

绝大多数现代 PHP 框架(Laravel、ThinkPHP、Symfony 等)都依赖它实现单一入口美化 URL

  • 隐藏入口文件 :把 index.php/controller/action 变成 /controller/action

  • 伪静态 :将 .html 结尾的 URL 实际交给 PHP 处理

2. 重定向与跳转

  • 强制 HTTP → HTTPS

  • 强制带 www 或不带 www 的域名统一

  • 网站改版时做 301 永久重定向

示例(强制 HTTPS):

apache

复制代码
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]

3. 安全与访问控制

  • 禁止直接访问敏感文件 :比如 .env、数据库配置、日志文件。

  • IP 黑白名单:只允许特定 IP 访问后台或开发工具。

  • 目录浏览保护:防止用户看到文件列表。

  • HTTP 认证:给某些目录加弹出密码。

示例(禁止访问 .env):

4. 自定义错误页面

让访问者看到你设计的 404 / 500 页面,而不是服务器默认的白屏。

apache

复制

下载

复制代码
ErrorDocument 404 /errors/404.html
ErrorDocument 500 /errors/500.php

5. 性能优化

  • 启用 Gzip 压缩:压缩 HTML、CSS、JS 等文本内容,减小传输体积。

  • 设置缓存头(Expires / Cache-Control):让浏览器缓存静态资源,加快二次访问速度。

  • 禁用图片防盗链

6. 调整 PHP 配置(视运行模式而定)

仅在 PHP 作为 Apache 模块(mod_php)运行时有效。如果是 PHP-FPM 或 FastCGI 模式,这些指令通常会报 500 错误。

4. 部署前必须检查的 Apache 配置

在 Windows 的 Apache(XAMPP/Wamp/Laragon 等)里,确保以下操作已完成:

① 开启必要模块

打开 httpd.conf(通常在 apache/conf/ 下),取消下面这些行的注释(去掉前面的 #):

apache

复制

下载

复制代码
LoadModule rewrite_module modules/mod_rewrite.so
LoadModule deflate_module modules/mod_deflate.so
LoadModule expires_module modules/mod_expires.so
LoadModule headers_module modules/mod_headers.so   # 虽然案例没用到,但建议开

② 允许 .htaccess 覆盖权限

httpd.conf 中,找到你网站对应的 <Directory> 配置(或全局 AllowOverride 设置),确保是 All

apache

复制

下载

复制代码
<Directory "C:/xampp/htdocs">
    AllowOverride All
    # ...
</Directory>

修改后重启 Apache

5. 测试验收

  1. 把整个 myproject 文件夹放到你的 Apache 文档根目录(例如 htdocs)。

  2. 浏览器访问:

    • http://localhost/myproject/home → 看到首页

    • http://localhost/myproject/about → 看到关于页

    • http://localhost/myproject/.env → 看到 403 Forbidden 或被拒绝访问

    • http://localhost/myproject/abc → 看到自定义 404 页面

    • 尝试直接访问 http://localhost/myproject/ 如果目录列表没有开启,会显示首页(因为根路径映射到 home),或者也可能被重写到 index.php 处理,实际效果是首页。

  3. F12 打开开发者工具 → 网络(Network),刷新并查看 style.cssapp.js 的响应头,你会看到:

    • Content-Encoding: gzip (压缩生效)

    • Cache-Control: max-age=...Expires: ... (缓存生效)

如果某个功能不生效,请检查对应模块是否已启用、.htaccess 文件名是否正确(没有额外后缀)、文件编码是否为 UTF-8 无 BOM。

  • 如果你的 Apache 没有配置默认索引文件找到 index.php,访问根路径可能需要手动加 /home,或者你可以在 .htaccess 里添加 DirectoryIndex index.php

这几行是 PHP 项目实现「单一入口」和「美化 URL」最经典的核心配置。下面逐行拆解,并告诉你以后如果需求变了,应该怎么改、为什么这样改。


一、逐行详解

apache

复制代码
RewriteEngine On

开启重写引擎。没有这一行,后面所有重写规则全部无效。


apache

复制代码
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d

这两行是重写的条件 ,必须同时满足才会执行下一条 RewriteRule

  • %{REQUEST_FILENAME}:当前请求在服务器上对应的物理文件路径

    比如你访问 https://aliw.cn/home,Apache 会拼出类似 /home/用户名/public_html/home 这样的路径。

  • !-f不是一个真实存在的文件。

  • !-d不是一个真实存在的目录。

为什么要加这两个条件?

如果你访问的是一个真实存在的图片、CSS、JS 或某个目录,就不要重写给 PHP,直接让 Apache 返回真实文件。否则,连静态资源都会走 index.php,不仅浪费资源,还可能显示乱码。


apache

复制代码
RewriteRule ^(.*)$ index.php?/$1 [QSA,L]

这是重写规则本身

  • ^(.*)$:一个正则表达式,匹配请求 URI 的路径部分(去掉域名和查询字符串后剩下的)。

    • ^ 表示行首,$ 表示行尾,(.*) 捕获所有字符。

    • 例如 /home 会捕获到 home/about/team 会捕获到 about/team

  • index.php?/$1重写目标

    • $1 代表前面 (.*) 捕获到的内容。

    • 你访问 /home,实际服务器内部处理为 index.php?/home

    • 这里故意加了一个 ?,将路径作为查询字符串的一部分传递,而不是使用 index.php/home 的形式。

  • [QSA,L]标志位

    • QSA(Query String Append):如果原始请求带了查询参数(如 ?page=2),会附加到新 URL 后面,而不是被丢弃。

    • L(Last):如果这条规则匹配成功,停止执行后续的重写规则。


二、为什么要用 index.php?/$1 而不是 index.php/$1

你的线上环境正是因为这个差异,才从 500 错误变成正常。

  • index.php/$1 依赖 Apache 的 PATH_INFO 功能,会将 /home 作为 PATH_INFO 变量传给 PHP。但不少虚拟主机或 PHP-FPM 模式下,PATH_INFO 可能被禁用或处理异常,导致 500 错误。

  • index.php?/$1 把路径伪装成了查询字符串的一部分 (非常巧妙的 trick)。

    在 PHP 的 $_SERVER['REQUEST_URI'] 中仍然能正确获取到 /home,但服务器只把它当作普通的 GET 参数处理,完全不依赖 PATH_INFO,兼容性极好。

因此,这个 ? 是你线上稳定的关键,不要随意去掉。


三、常见修改场景及对应的改法

理解了上面每一部分的作用,以后你想改功能就能对症下药了。

1. 想把入口文件从 index.php 改成 admin.php

将重写目标改为:

apache

复制代码
RewriteRule ^(.*)$ admin.php?/$1 [QSA,L]

逻辑不变,只是换了一个 PHP 文件来处理。

2. 项目不在网站根目录,而在子目录(如 myapp)

如果你把项目放在 public_html/myapp/,访问时是 https://aliw.cn/myapp/home,那么需要让重写路径相对 于当前目录。可以在规则前加上 RewriteBase

apache

复制代码
RewriteEngine On
RewriteBase /myapp/
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?/$1 [QSA,L]

这样 $1 就不会包含 myapp 前缀,路由解析更干净。

3. 排除某些文件类型不做重写(保护敏感文件)

比如你想禁止访问所有 .env.log 文件,并直接返回 403,可以在条件前面加:

apache

复制代码
RewriteRule \.env$ - [R=403,L]
RewriteRule \.log$ - [R=403,L]

- 表示"不做任何替换,直接按标志处理",这里给了 403 状态码并停止。

4. 强制全站 HTTPS

RewriteEngine On 下面增加:

apache

复制代码
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]

注意:若服务器前面有 CDN,可能需要用 %{HTTP:X-Forwarded-Proto} 而非 %{HTTPS},否则会死循环(之前线上 500 可能也与此有关)。

5. 移除 QSA,避免参数叠加

有时候你希望完全控制新的查询参数,不希望原始参数保留,就去掉 QSA

apache

复制代码
RewriteRule ^(.*)$ index.php?/$1 [L]

这样原始请求 ?page=2 会被丢弃。通常保留 QSA 更合理。


四、修改逻辑总结

当你需要调整路由行为时,按下面的逻辑改对应部位:

需求 修改点
换一个入口文件 RewriteRule 目标文件
项目在子目录 RewriteBase
排除某些路径不重写 RewriteRule 前加带 - 的拦截规则
添加或去掉查询字符串 使用或移除 QSA 标志
强制 HTTPS 增加 HTTPS 判断规则(注意代理)
让重写停止 保留 L 标志,后续规则不执行
解决 PATH_INFO 报错 使用 index.php?/$1,确保那个 ? 存在

方案一:访问根域名自动跳转到 /myapp4/(推荐,最清晰)

在根目录的 .htaccess 中写入:

apache

复制代码
RewriteEngine On
# 只对根路径进行 301 重定向
RewriteRule ^$ /myapp4/ [R=301,L]

效果

  • 访问 http://example.com/ → 浏览器地址栏变成 http://example.com/myapp4/,然后由 myapp4/.htaccess 内的规则(DirectoryIndex admin.php)接管,正常显示首页。

  • 访问 http://example.com/myapp1/ 等,不受影响,各自独立。

优点

路径清晰,静态资源路径无需任何调整,所有链接自动基于 /myapp4/

在方案一中(只重定向根路径)

方案一的 .htaccess 只有一行跳转:

apache

复制代码
RewriteRule ^$ /myapp4/ [R=301,L]

这条规则只匹配完全为空的请求路径 (就是访问 http://example.com/ 的时候)。

对于任何其他路径(比如 /about/abc/myapp1/),这条规则根本不会触发,Apache 会按照默认方式处理:

  • 如果路径是一个真实存在的文件或目录 → 正常返回文件或进入目录索引。

  • 如果路径不存在 ,而且根目录的 .htaccess 里没有别的重写规则 → 返回 404 Not Found (由主机默认的 404 页面或主配置处理)。

  • 不会自动交给 myapp4 处理,也不会尝试去子目录里找。

所以,方案一非常干净:根域名一跳转,其他路径各安天命,子项目互不干扰。


如果你希望不存在的路径也由 myapp4 接管

那才需要方案二 的思路,也就是你问题里写的那些复杂条件。那时访问 /about(不存在)会被内部重写到 myapp4/admin.php?/about,让 myapp4 的路由来决定显示什么(可能是首页,也可能是它自己的 404)。
但如之前所说,这样会面临静态资源路径错乱的问题,需要配合 <base> 标签或全路径。


一句话总结

方案一:

  • 访问根域名 → 跳到 /myapp4/

  • 访问 /abc(不存在) → 直接 404,和 myapp4 无关

  • 访问 /myapp1/ → 进入 myapp1 子项目。

  • 不需要处理你问题里的"不以 myapp1/2/3/4 开头"的情况,因为根本没这些判断。

如果你实际想要的是"不存在的路径自动转给 myapp4",那才需要走方案二,并解决路径问题。

你只需要在根目录的 .htaccess 里增加几条规则,就可以让"不存在的路径"显示你自己的错误页面,而不会回到主机商默认的白屏 404。

因为根目录你只做了 RewriteRule ^$ /myapp4/,其他路径本来要么存在,要么触发 Apache 默认 404。现在我们要接管这些"不存在路径",同时不能误伤 myapp1 ~ myapp4 这些子项目,也不能影响真实存在的文件和目录。

最终根目录 .htaccess(方案一增强版)

apache

复制代码
RewriteEngine On

# 1. 访问根路径时,301 跳转到 myapp4
RewriteRule ^$ /myapp4/ [R=301,L]

# 2. 对于不存在的路径(且不是子项目),显示自定义 404 页面
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !^/(myapp1|myapp2|myapp3|myapp4)/
RewriteRule ^(.*)$ /404.php [QSA,L]

逐条解释

  • RewriteCond %{REQUEST_FILENAME} !-f → 不是真实文件

  • RewriteCond %{REQUEST_FILENAME} !-d → 不是真实目录

  • RewriteCond %{REQUEST_URI} !^/(myapp1|myapp2|myapp3|myapp4)/ → 请求路径不是/myapp1//myapp2/ 等开头(保护子项目)

  • RewriteRule ^(.*)$ /404.php [QSA,L] → 满足条件时,内部重写到根目录下的 404.php,不会改变浏览器地址栏(看不到 /404.php


你需要做的:新建一个 404.php 放在网站根目录

内容如下:

复制代码
<?php
http_response_code(404);
?>
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>404 - 页面未找到</title>
    <style>
        body { font-family: sans-serif; padding: 50px; text-align: center; }
        h1 { font-size: 2em; color: #c00; }
    </style>
</head>
<body>
    <h1>404</h1>
    <p>抱歉,您访问的页面不存在。</p>
    <p><a href="/myapp4/">返回首页</a></p>
</body>
</html>

这样做之后,各种访问的结果是:

访问的 URL 结果
http://example.com/ 301 跳转到 /myapp4/
http://example.com/myapp4/ 正常进入 myapp4 首页
http://example.com/myapp4/home 正常进入 myapp4 的 home 路由
http://example.com/myapp1/ 正常进入 myapp1 子项目
http://example.com/existing-file.txt 返回真实文件(不触发 404)
http://example.com/abc 显示你自定义的 404 页面,状态码 404
http://example.com/myapp5/ 因为 myapp5 不在保护列表里,如果目录不存在且也不是文件,就会走 404

注意事项

  1. 如果你的子项目不止 myapp1myapp4,记得把列表更新到 RewriteCond 里,用 | 分隔即可。

  2. 404.php 必须放在网站根目录 (和这个 .htaccess 同级),且内容确保没有任何语法错误。

  3. 如果哪天你想把"不存在的路径"都直接丢给 myapp4 的统一路由处理,那就需要回到方案二,但需要处理静态资源路径问题。目前这样各项目独立,且统一了 404 外观,已经是最稳妥的做法。

你现在可以直接用这个增强版 .htaccess 替换根目录原有的,上传后访问一个随便的 /test404,就能看到自己设计的错误页了。

访问 http://example.com/ → 301 跳转到 /myapp4/,但 /myapp4/ 却显示了"页面被吃掉了(404)",说明跳转本身成功了,但是 myapp4 子项目没能正确输出首页

这个问题跟根目录的 .htaccess 无关,跟 myapp4 自身的 .htaccess 和入口文件 admin.php 的路由逻辑有关。


1. 先快速确认两个基本点

  • myapp4/ 目录下确实存在 admin.php 这个文件。

  • myapp4/.htaccess 确实包含 DirectoryIndex admin.php(建议放在文件顶部)。

如果这两点都满足,还是 404,那就是路由没处理空路径的问题。


2. 为什么会出现"页面被吃掉了"?

当浏览器访问 /myapp4/ 时:

  1. Apache 看到请求的是目录 myapp4/

  2. 由于 myapp4/.htaccess 的重写条件是 !-d(不是目录),所以重写规则不触发

  3. 但是 DirectoryIndex admin.php 生效,Apache 会自动将请求交给 admin.php 处理。

  4. admin.php 运行,此时 $_SERVER['REQUEST_URI'] 一般是 /myapp4//myapp4

  5. 你的路由代码很可能像这样:

    path = parse_url(request_uri, PHP_URL_PATH);
    path = trim(path, '/');

/myapp4/ 去掉首尾斜杠后就变成了 myapp4,而不是空字符串。

如果你的 switch 里只匹配了 '''home',那 myapp4 会掉进 default 分支,于是显示 404。

这就是跳转后看到 404 的根源。


3. 解决办法

方法一:修改 admin.php 的路由逻辑(推荐)

admin.php 里,把子目录名称也视为首页路由。比如:

复制代码
switch ($path) {
    case '':
    case 'myapp4':      // 这一行新增
    case 'home':
        require __DIR__ . '/pages/home.php';
        break;
    case 'about':
        require __DIR__ . '/pages/about.php';
        break;
    default:
        http_response_code(404);
        require __DIR__ . '/errors/404.html';
        break;
}

这样,/myapp4/ 会被正确识别为首页,显示正常内容。

方法二:调整重写规则,让目录请求也走重写

myapp4/.htaccess 里,把 !-d 条件去掉(或者用其他方式),使根目录请求也触发重写。但不建议,因为这会破坏对真实静态资源的正常访问。更安全的方式是用方法一。


4. 如何验证和测试

  • 直接访问 http://example.com/myapp4/admin.php,如果能正常显示首页,则说明文件存在且可执行。

  • 访问 http://example.com/myapp4/,看是否还 404。改完 admin.php 后刷新即可。


5. 额外提醒

你的自定义 404 页面里可能有"页面被吃掉了"的文案。现在 /myapp4/ 显示的就是这个,说明 admin.phpdefault 分支确实被触发了。

所以,你只需要让 myapp4 也被识别为首页,问题就解决。


总结 :根目录 301 正常,myapp4 首页 404 是因为路径解析出了 myapp4,而路由没有匹配。在 admin.php 路由开关里加上 case 'myapp4': 即可。

对,就是这么简单。你已经把 myapp4 加进了首页路由,跳转后就能正常显示,而不是被误判为 404。

现在可以再验证一下全流程:

  1. 访问 http://example.com/ → 自动跳到 /myapp4/,地址栏变了,页面是你的首页。

  2. 直接在地址栏输入 /myapp4/home/myapp4/about 等 → 正常走 admin.php 路由,显示对应页面。

  3. 输入 /myapp1//myapp2/ 等 → 各自子项目独立运行,互不干扰。

  4. 输入一个不存在的路径(比如 /abc)→ 显示你在根目录 404.php 里设计的错误页面。

如果以后你把其他子项目(比如 myapp1)的入口也改成了 admin.php,并且想支持直接访问 http://example.com/myapp1/,也别忘了在各自的 admin.php 里加上 case 'myapp1':,或者在各自的 .htaccess 里加上 DirectoryIndex admin.php,再配合路由处理空路径即可。

现在整个结构清晰、安全,也预留了自定义错误页,可以放心用了。

不需要调整其他 case ,比如 case 'home':case 'about':,它们继续正常工作,一点也不用改。


为什么不用改?

关键在于你的 myapp4/.htaccess 里的重写规则,它是写在子目录内的:

apache

复制代码
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ admin.php?/$1 [QSA,L]

当你在浏览器访问 http://example.com/myapp4/home 时,Apache 的处理过程是这样的:

  1. 请求 URI 是 /myapp4/home

  2. Apache 进入 myapp4 目录,发现里面有 .htaccess,启用该文件内的规则。

  3. 此时,重写规则匹配的路径已经去掉了目录前缀 myapp4/ ,只针对当前目录的"相对路径"进行匹配。所以 ^(.*)$ 捕获到的 $1home,而不是 myapp4/home

  4. 于是请求被内部重写为 admin.php?/home$_SERVER['REQUEST_URI'] 仍然是 /myapp4/home(对外不变),但路由脚本拿来做 trim 时,去掉首尾斜杠后得到的就是 home

所以,你的 switch 里:

  • case 'home': --- 匹配成功 ✅

  • case 'about': --- 匹配成功 ✅

  • 所有原有的路由分支,都原封不动继续工作。

唯一多做的就是加了一个 case 'myapp4':,用来处理直接访问子目录根路径 (如 /myapp4//myapp4)时,路径解析出来是 myapp4 的特殊情况。这个情况只有当你用 DirectoryIndex 而不走重写时才会出现,所以需要单独处理。


简单总结

  • 原有的路由逻辑无需任何调整 ,访问 /myapp4/home/myapp4/about 等一切照旧。

  • 添加的 case 'myapp4': 仅仅是为了解决目录根路径DirectoryIndex 导向 admin.php 时,路径字符串为 myapp4 的识别问题。

这样,你的 admin.php 结构清晰,以后无论项目放在哪个子目录,只要在文件里加一个与目录名对应的 case 就能兼容根路径访问,其他路由不受任何影响。

明白了!问题根源找到了:你的 .htaccess 重写规则用了 admin.php?/1,路径实际上是作为**查询字符串** 传给 PHP 的,但你的 admin.php 还是按旧方式从 _SERVER['REQUEST_URI'] 直接取路径,没处理掉子目录前缀,所以 /myapp4/home 被解析成了 myapp4/home,当然匹配不到 home。


修复方法:调整 admin.php 的路径获取逻辑

找到你 admin.php 里这段代码(原先的):

php

复制代码
$request_uri = $_SERVER['REQUEST_URI'] ?? '/';
$path = parse_url($request_uri, PHP_URL_PATH);
$path = trim($path, '/');

替换为下面这段,优先从查询字符串读取路径:

php

复制代码
// 1. 优先从查询字符串中获取路径(适配 admin.php?/... 模式)
if (isset($_SERVER['QUERY_STRING']) && $_SERVER['QUERY_STRING'] !== '' && $_SERVER['QUERY_STRING'][0] === '/') {
    // 查询字符串以 / 开头,说明是重写规则传过来的路径
    $path = trim($_SERVER['QUERY_STRING'], '/');
} else {
    // 2. 否则从 REQUEST_URI 中获取(兼容直接访问、DirectoryIndex 等情况)
    $request_uri = $_SERVER['REQUEST_URI'] ?? '/';
    $path = parse_url($request_uri, PHP_URL_PATH);
    // 去掉最前面的 /,以及如果有子目录前缀,也会保留在 path 里(如 myapp4)
    $path = trim($path, '/');
}

这样修改后:

  • 访问 /myapp4/home → 查询字符串是 /home$path = 'home' → ✅ 匹配 case 'home':

  • 访问 /myapp4/about → 查询字符串是 /about$path = 'about' → ✅ 匹配

  • 访问 /myapp4/ → 没有查询字符串,走 else 分支,$path = 'myapp4' → ✅ 匹配 case 'myapp4':(之前加的)

  • 访问 /myapp4/abc → 查询字符串是 /abc$path = 'abc' → 进入 default → 显示 404


为什么之前 https://aliw.cn/home 可以,子目录就不行?

  • 之前线上测试时,项目直接在根目录 ,访问 /home$_SERVER['QUERY_STRING']/home,但走 else 分支时 $path 也是 home,两种方式结果相同。

  • 现在项目在子目录 myapp4,用 else 分支时 $path 会是 myapp4/home,所以必须用查询字符串方式才能正确提取。


补充检查(如果修改后还有问题)

  1. 确保 myapp4/.htaccess 内有且只保留稳定版规则(不要被多余东西干扰):

    apache

    复制代码
    DirectoryIndex admin.php
    RewriteEngine On
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule ^(.*)$ admin.php?/$1 [QSA,L]
  2. 确认 myapp4/admin.php 存在且路径大小写正确(Linux 严格区分大小写)。

  3. 如果其他子项目也使用相同的路由方式,记得把这份 admin.php.htaccess 同步修改,并且按需添加各自的 case '子目录名':

现在去改 admin.php,刷新页面就能看到首页了。

需要修改。

因为你现在的项目跑在子目录 /myapp4/ 下,所有以 / 开头的路径都会被浏览器解析为网站根目录 ,而不是 myapp4 目录。


问题分析

当前 home.php 里的链接:

复制代码
<a href="/about">关于我们</a>
<link rel="stylesheet" href="/assets/css/style.css">
<script src="/assets/js/app.js"></script>

浏览器实际请求的地址是:

  • http://example.com/about

  • http://example.com/assets/css/style.css

  • http://example.com/assets/js/app.js

但你真正的页面和资源都在 myapp4 下面:

  • http://example.com/myapp4/about

  • http://example.com/myapp4/assets/css/style.css

  • http://example.com/myapp4/assets/js/app.js

所以点击"关于我们"会跳到 http://example.com/about,而这个路径如果根目录没有对应处理,就会触发你设置的自定义 404 页面(或根目录的默认 404);CSS/JS 也会加载失败,页面样式全无。


三种修改方案,按需选择

方案一:全部改成绝对路径(带子目录前缀)

复制代码
<a href="/myapp4/about">关于我们</a>
<link rel="stylesheet" href="/myapp4/assets/css/style.css">
<script src="/myapp4/assets/js/app.js"></script>

✅ 简单直接,无论页面在哪个子目录都能正确引用。

❌ 如果以后换目录(比如从 myapp4 改为 app),所有链接都要批量修改。


方案二:使用 标签(推荐)

<head> 最前面加入:

复制代码
<base href="/myapp4/">

然后所有资源链接仍然可以写成:

复制代码
<a href="about">关于我们</a>          <!-- 注意:去掉了前面的 / -->
<link rel="stylesheet" href="assets/css/style.css">
<script src="assets/js/app.js"></script>

✅ 修改量最小,迁移项目只需改一个 <base> 标签。

✅ 路径看起来更干净,像在根目录一样。

⚠️ 注意<base> 会影响页面内所有相对路径(包括锚点),使用时务必用目录结尾的斜杠/myapp4/),而不是 /myapp4(缺少结尾斜杠可能导致浏览器解析错误)。


方案三:全部改用相对路径

复制代码
<a href="../myapp4/about">关于我们</a>   <!-- 太麻烦,不推荐 -->
<link rel="stylesheet" href="../myapp4/assets/css/style.css">

❌ 不推荐,因为不同页面层级深度不同,维护困难。


我的建议

直接用方案二 ,在你的 home.php(以及所有其他页面如 about.php404.php)的 <head> 里添加:

复制代码
<base href="/myapp4/">

然后把原来以 / 开头的链接和资源路径前的 / 去掉,变成相对路径:

复制代码
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <base href="/myapp4/">
    <title>首页</title>
    <link rel="stylesheet" href="assets/css/style.css">
</head>
<body>
    <h1>🏠 欢迎来到首页</h1>
    <p><a href="about">关于我们</a></p>
    <script src="assets/js/app.js"></script>
</body>
</html>

现在点击"关于我们"会正确跳转到 /myapp4/about,静态资源也会从 /myapp4/assets/ 加载,一切正常。


额外提醒

如果你其他子项目(myapp1、myapp2...)也有类似的前端资源,也需要做同样处理,避免互相干扰。每个子项目用自己对应的 <base href="/子目录名/"> 即可。

相关推荐
两个人的幸福2 天前
Windows 桌面应用自研 PHP 队列(下):完整代码与六大工程化优化
php
BingoGo4 天前
PHP 泛型之殇 泛型 RFC 提案被拒绝
后端·php
JaguarJack4 天前
PHP 泛型之殇 泛型 RFC 提案被拒绝
后端·php
用户3074596982075 天前
PHP 扩展——从入门到理解
php
鹏仔先生6 天前
拷贝漫画APP下载页PHP程序,后台带免费AI写作
php
云水一下6 天前
从零开始学 PHP 系列(一):PHP 的前世今生与开发环境搭建
开发语言·php
xingpanvip6 天前
星盘接口开发文档:本命盘接口指南
android·开发语言·css·php·lua
酉鬼女又兒6 天前
零基础入门计算机网络运输层:端到端通信核心作用、端口号分类规则、复用分用工作机制及UDP与TCP协议全方位对比详解
网络·网络协议·tcp/ip·计算机网络·考研·udp·php
dog2506 天前
不要再继续优化 TCP
网络协议·tcp/ip·php
Channing Lewis6 天前
PHP 解析 Excel 的那些坑:一次“行号错位”引发的数据丢失
开发语言·php·excel