本文由 FlintHub 开发文档归档发布。原始文档日期见标题前缀,来源:8月13日备份/重写规则备忘.md。
SoSite 重写规则备忘
用途:SoSite 是前端控制器路由(
/thread/149、/blog/xxx等路径由index.php解析),需要在 Web 服务器配置"伪静态"规则,把非真实文件/目录的请求转发到
index.php。配置前请确认站点能正常访问
/index.php(PHP 解析正常),再配置重写规则。★ 核心原则(血泪教训):
1. 静态资源目录(assets、plugins)绝不能进安全隐藏规则——否则主题/插件资源全部 404,功能"看似失效"。
2. nginx 不要用多端口 upstream 轮询做"免配置切版本"——死端口会拖慢每个请求(实测 2.4s)。
3. 服务器级重写规则都是"治标":换环境(nginx/apache/IIS)就得重新配;真正"治本"是程序生成服务器无关的 URL(见 §六)。
一、原理:SoSite 路由与重写规则的关系
SoSite 所有动态页面(帖子、博客、搜索、用户中心、后台)都是前端控制器模式:
所有请求进入 index.php,由 Router 根据 $_SERVER['REQUEST_URI'] 解析路由。
服务器重写规则的作用只有一个:把"看起来像文件路径但不是真实文件"的请求(如 /thread/149)转交给 index.php。
- 真实存在的文件(
/assets/css/admin.css、/index.php等)→ 服务器直接返回,不经过重写 - 不存在的路径(
/thread/149、/blog/xxx)→ 重写规则转发给index.php
所以规则是否正确的判断标准:
- 动态页(
/thread/149)→ 200(被重写)✅ - 静态文件(
/assets/...)→ 200(直接返回,未被误伤)✅ - 核心目录(
/app/...、/protected/...)→ 403/404(被安全规则拦下)✅
二、Apache(通用,解压即用)
原理:Apache 支持目录级 .htaccess,把规则文件放到站点根目录即可自动生效,
无需改 httpd.conf(需 AllowOverride All,绝大多数虚拟主机默认开启)。
做法:在站点根目录创建/覆盖 .htaccess:
# ============================================================
# SoSite 伪静态规则(Apache)
# ============================================================
RewriteEngine On
# 核心:非真实文件/目录的请求统一转发到 index.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php [L,QSA]
注意事项:
- 子目录部署(如
/sosite/):.htaccess放子目录即可,RewriteRule目标index.php为相对路径自动解析,无需额外处理。 - HTTPS 强制跳转(可选,加在
RewriteEngine On之后):
```apache
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
```
- 安全说明:Apache 下不需要额外隐藏目录规则——
app/、protected/里的.php被直接访问时会执行但无入口输出;如需加固可加:
```apache
# 禁止直接访问核心目录(可选加固)
RewriteRule ^(app|protected|update)(/|$) - [F,L]
```
三、IIS(通用,需 URL Rewrite 模块)
原理:IIS 使用目录级 web.config 配置 URL Rewrite(需已安装 IIS URL Rewrite 模块,绝大多数 Windows 主机自带)。
做法:在站点根目录创建/覆盖 web.config(以下为完整版,含安全规则):
注意事项:
- 前提:IIS "处理程序映射" 中必须已配置 PHP FastCGI(
.php→php-cgi.exe),否则.php会被当作静态文件直接下载,config.php等敏感文件会泄露,规则拦不住这一步。 - 子目录部署(如
/sosite/):web.config放子目录即可,规则自动适用于该目录。 - hiddenSegments 与 rewrite 403 的区别:hiddenSegments 返回 404(隐藏存在性,更隐蔽);rewrite 规则返回 403(明示拒绝)。两者对核心目录做了双保险,是合理的。
四、Nginx(规则通用,接入方式因环境而异)
规则本身是通用的(和 Apache/IIS 等价),但 nginx 没有目录级配置文件(不支持 .htaccess / web.config 机制),
必须把规则放进站点 server 配置里,且不同环境接入位置不同——所以 nginx 需要使用者按自己环境配置。
4.1 规则片段(通用,二选一)
写法 A(if + rewrite,与 Apache 语义最接近):
location / {
index index.php index.html;
# SoSite 伪静态:非真实文件/目录请求转发到 index.php
if (!-e $request_filename) {
rewrite ^/(.*)$ /index.php/$1 last;
}
...
}写法 B(try_files,官方推荐、避免 if 指令):
location / {
index index.php index.html;
try_files $uri $uri/ /index.php?$query_string;
...
}4.2 各环境接入位置
| 环境 | 配置位置 |
|---|---|
| 宝塔面板 | 站点 → 设置 → 伪静态 → 选择/粘贴规则(保存后自动生效) |
| LNMP 一键包 | /usr/local/nginx/conf/vhost/域名.conf 的 location / 内 |
| phpstudy | Extensions/Nginx1.15.11/conf/vhosts/站点.conf 的 location / 内;或参考程序根目录 nginx-server.conf |
| 自编译/Docker nginx | 站点 server 块的 location / 内 |
4.3 PHP 处理(必须配套)
nginx 下 PHP 由 fastcgi_pass 处理,需同时配置 PHP location(端口/方式因环境不同):
location ~ \.php(.*)$ {
fastcgi_pass 127.0.0.1:9000; # ← 端口按环境实际 PHP 版本/套接字改
fastcgi_index index.php;
fastcgi_split_path_info ^((?U).+\.php)(/?.+)$;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
include fastcgi_params;
}- 宝塔/LNMP:
fastcgi_pass通常为unix:/tmp/php-cgi-xx.sock或127.0.0.1:9000,以环境默认值为准。 - phpstudy:端口映射固定
9000→php7.3 / 9001→php8.0.2 / 9002→php8.2.9(见COM/xp.ini),
切换 PHP 版本后若 502,把端口改成对应版本即可。
- 子目录部署:
root指向站点目录,规则不变;若部署在二级目录需调整try_files/rewrite前缀。
4.4 ★ 避坑:不要用多端口 upstream 轮询做"免配置切版本"
错误示范(曾真实采用并踩坑,见 §五.2):
# ✗ 错误:三端口轮询想实现"切 PHP 版本免配置",实测每个请求被死端口拖慢
upstream sosite_php_backend {
server 127.0.0.1:9000 max_fails=1 fail_timeout=5s; # 死端口(未在跑)
server 127.0.0.1:9001 max_fails=1 fail_timeout=5s; # 唯一活着的端口
server 127.0.0.1:9002 max_fails=1 fail_timeout=5s; # 死端口
}
...
location ~ \.php(.*)$ {
fastcgi_pass sosite_php_backend; # ← 轮询到死端口 → 连接失败重试 → 延迟
}正确做法:单端口直连当前 PHP 版本:
location ~ \.php(.*)$ {
fastcgi_pass 127.0.0.1:9001; # ✓ 当前 PHP 8.0.2 端口,无轮询、无延迟
...
}切换 PHP 版本后 502,改一行端口即可(注释写明 9000/9001/9002 对应关系)。
如需真正"免配置切版本",正确方向见 §六(程序级方案)。
五、实战踩坑记录(重要)
5.1 IIS:hiddenSegments 误藏 assets/plugins → 主题切换"失效"
现象:服务器上用 web.config 后,后台切换主题"看似保存了但没变化"。
根因:web.config 的 hiddenSegments 里多加了 assets(还有 plugins):
- 主题变体 CSS 在
/assets/css/themes/modern-xxx.css,全站静态资源都在/assets/下 hiddenSegments是 IIS 请求过滤,命中即 404,且在 rewrite 之前拦截- 结果:主题切换后变体样式 404 加载不出来 → 看起来"切换失效"
- 而主路由规则里还写了排除 assets(想放行),与 hiddenSegments 自相矛盾
教训:静态资源目录(assets、plugins)绝不能进任何安全隐藏规则。
hiddenSegments 只隐藏核心代码目录(app / protected / update)。
plugins 同理——插件资源 /plugins/{id}/assets/... 是直连的,隐藏 plugins 连插件样式一起 404。
5.2 Nginx:upstream 多端口轮询 → 死端口拖慢每个请求
现象:本地改用 nginx-server.conf(含三端口 upstream)后,页面加载有明显延迟(实测 /thread/149 2.4s),服务器上正常。
根因:
- phpstudy 固定端口映射 9000/9001/9002,但同时只有当前 PHP 版本一个端口在监听(如只有 9001)
- upstream 轮询把请求轮流发给三个端口,命中 9000/9002 死端口时连接失败 → nginx 重试下一个 → 每次请求平均多撞 1-2 次死端口 → 延迟数倍
- 服务器上是单端口
fastcgi_pass直连,不走轮询,所以没延迟
修复:去掉 upstream,改回单端口直连(见 §4.4),延迟从 2.4s 降到 0.34s。
教训:upstream 轮询只适用于"多个后端都在提供服务"的场景(如负载均衡);对"多版本 PHP 但同一时刻只有一版在跑"的场景,轮询= 撞死端口 = 延迟。要"免配置切版本"应该走程序级方案(§六),而不是服务器级投机。
5.3 共性结论
| 教训 | 本质 | 正确做法 |
|---|---|---|
| hiddenSegments 藏 assets | 安全规则误伤静态资源 | 只藏核心目录,永不藏 assets/plugins |
| upstream 轮询死端口 | 负载均衡用错场景 | 单端口直连;切版本改端口 |
| 服务器级规则换环境要重配 | 治标不治本 | 程序生成服务器无关 URL(§六) |
六、治本方向:程序生成服务器无关的 URL(免任何服务器配置)
为什么说是治本:服务器级重写规则(.htaccess / web.config / nginx 配置)每换一个环境(nginx、apache、IIS、宝塔、LNMP、cPanel…)都要重新配置——这是"治标"。
参考实现(xiunox 同款思路):
xiunox 的 url() 函数默认 url_rewrite_on=0,所有链接生成 index.php?xxx.htm 查询参数格式——查询参数是服务器无关的,nginx/apache/IIS/Windows 通吃,装完即用,不需要任何重写规则。伪静态(url_rewrite_on=1+)只是可选增强,开启才需要服务器规则。
SoSite 已有基础:Router.php 已内置 ?r= fallback(index.php?r=/forum → 路由解析为 /forum)。
若实施程序级方案,改动集中在:
TemplateCompiler::url()加开关:off 时生成index.php?r=/forum格式Controller::redirect()同步生成- Router
?r=fallback 放开 admin 限制(有 isAdmin + 二次验证保护,风险可控) - 前端硬编码路径(
/api/vote、/api/upload等)动态化
何时做:当"换环境零配置"成为硬需求(分发安装包给他人、多服务器迁移频繁)时实施;
本地/单一环境用服务器级规则(§二~§四)即可,简单直接。
七、验证清单
配置完成后,Ctrl+F5 强刷,逐项检查:
- 动态页:访问
/thread/149(或任一帖子)→ 正常显示(非 404)→ 重写规则生效 - 静态文件:访问
/assets/css/admin.css→ 正常返回 → 重写规则未误伤静态资源 - PHP 入口:访问
/index.php→ 正常 → PHP 解析正常 - 核心目录(IIS):访问
/app/、/protected/→ 403/404 → 安全规则生效 - 敏感文件(IIS):访问
/config.php→ 403 → 防泄露规则生效 - 主题切换:后台切主题 → 前台样式跟随变化 → assets 未被误伤(若失效查 hiddenSegments)
- 性能:
curl -w "%{time_total}"测/thread/149→ 应 < 1s(若数秒延迟,查是否 upstream 轮询死端口)
故障速查:
| 现象 | 原因 | 处理 |
|---|---|---|
| 所有页面 404 | 重写规则未生效/未加载 | 检查规则是否在正确位置、是否 reload |
| 静态文件 404 | 规则误伤静态资源 | 检查 !-e/!-f 条件、hiddenSegments 是否藏了 assets |
| 动态页 502 | fastcgi/PHP 配置错误 | 检查 fastcgi_pass 端口、PHP 是否运行 |
| 页面慢(数秒) | upstream 轮询死端口 | 改单端口直连(§4.4) |
| 主题切换没反应 | IIS hiddenSegments 藏了 assets | 从 hiddenSegments 移除 assets(§5.1) |
| 换服务器后伪静态失效 | 服务器级规则需重配 | 用程序级方案(§六) |
本备忘由 SoSite 项目维护,规则与 SoSite V5.0.0 前端控制器路由配套。
更新记录:2026-08-11 重写——新增 §五 实战踩坑(IIS hiddenSegments 误藏 assets、Nginx upstream 轮询死端口延迟)、§六 治本方向、§七 验证与故障速查。