伪静态 URL 重写
Discuz、织梦等存量 CMS 的伪静态规则:语义对齐 Nginx rewrite,现有规则可一对一机械转换。
PHP 站点内置一套轻量 URL 重写模块,用来支撑 Discuz、织梦(DedeCMS)等存量 CMS 的伪静态。规则语义对齐 Nginx rewrite,现有的 Nginx 伪静态规则可以一对一机械转换。前端控制器型的 WordPress / ThinkPHP 由 try_files 自动覆盖,不需要写重写规则。
#配置格式
sites:
- host: "bbs.example.com"
fastcgi:
address: "unix:/run/php/php-fpm.sock"
root: "/var/www/discuz"
index: "index.php"
rewrite:
- pattern: "^/thread-(\\d+)-(\\d+)-(\\d+)\\.html$"
replacement: "/forum.php?mod=viewthread&tid=$1&extra=page%3D$3&page=$2"
type: "internal" # 可省略,默认 internal| 字段 / 控件 | 默认 / 示例 | 说明 |
|---|---|---|
pattern | (必填) | RE2 正则(Go 标准库),匹配 URL 路径(不含 query)。线性时间、无回溯灾难,没有 ReDoS 风险。 |
replacement | (必填) | 目标路径 + 可选 ?query;$1 / $2 引用捕获组,后面紧跟数字或字母时写 ${1}。 |
type | internal | internal(≈ Nginx last,改写后重新走路由)| redirect(302)| permanent(301)。 |
#与 Nginx 的语义对照
| 行为 | Nginx | 墨守 |
|---|---|---|
| 内部重写、重新匹配 | rewrite ... last; | type: internal(默认) |
| 外部跳转 | rewrite ... redirect; / permanent; | type: redirect / permanent |
| 捕获组引用 | $1 $2 | $1 $2(歧义时 ${1}) |
| query 保留 | replacement 无 ? → 原 query 保留 | 同 |
| query 追加 | replacement 带 ?args → 原 query 追加其后 | 同 |
| query 丢弃 | replacement 以 ? 结尾 → 丢弃原 query | 同 |
REQUEST_URI | 原始 URL | 原始 URL(PHP 的 $_SERVER["REQUEST_URI"] 仍是美化后的地址) |
QUERY_STRING | 重写后 | 重写后(PHP 的 $_GET 来自此) |
| 检测层看到的 URL | —— | 始终是原始 URL:重写在检测之后执行,不影响语义引擎的输入 |
循环保护:内部重写最多 10 轮,成环即停,绝不挂死;单站规则数上限 64 条。
#Apache flag 对照
| Apache | 墨守 |
|---|---|
[L] | type: internal(默认) |
[R=301] | type: permanent |
[R] / [R=302] | type: redirect |
[QSA] | replacement 带 ? 即自动追加原 query(默认行为) |
#转换示例
原 Nginx 规则
location / {
rewrite ^/thread-(\d+)-(\d+)-(\d+)\.html$ /forum.php?mod=viewthread&tid=$1&extra=page%3D$3&page=$2 last;
rewrite ^/forum-(\w+)-(\d+)\.html$ /forum.php?mod=forumdisplay&fid=$1&page=$2 last;
}转换后的墨守规则
rewrite:
- {pattern: "^/thread-(\\d+)-(\\d+)-(\\d+)\\.html$", replacement: "/forum.php?mod=viewthread&tid=$1&extra=page%3D$3&page=$2"}
- {pattern: "^/forum-(\\w+)-(\\d+)\\.html$", replacement: "/forum.php?mod=forumdisplay&fid=$1&page=$2"}#Discuz X 伪静态整套规则
rewrite:
- {pattern: "^/archiver/(?:tid|fid)-(\\d+)\\.html$", replacement: "/archiver/index.php?action=$1"}
- {pattern: "^/forum-(\\w+)-(\\d+)\\.html$", replacement: "/forum.php?mod=forumdisplay&fid=$1&page=$2"}
- {pattern: "^/thread-(\\d+)-(\\d+)-(\\d+)\\.html$", replacement: "/forum.php?mod=viewthread&tid=$1&extra=page%3D$3&page=$2"}
- {pattern: "^/group-(\\d+)-(\\d+)\\.html$", replacement: "/forum.php?mod=group&fid=$1&page=$2"}
- {pattern: "^/space-(?:uid|username)-(.+?)\\.html$", replacement: "/home.php?mod=space&$1"}
- {pattern: "^/blog-(\\d+)-(\\d+)\\.html$", replacement: "/home.php?mod=space&uid=$1&do=blog&id=$2"}与官方 Discuz Nginx 伪静态一致。站点若部署在子目录,请给
pattern 与 replacement 统一加上子目录前缀。#注意事项
- 路径段里的注入照样被检测:语义引擎把原始
URL.Path当作独立输入检测,/thread-1' or '1.html这类构造在重写之前就已进入检测面。 - 规则尽量锚定
^...$:锚定即整段路径替换(伪静态惯用);非锚定只替换匹配片段、保留前后文(与 Nginx 一致)。 - 目标可以是静态文件:若 replacement 指向真实存在的静态文件,则走本地静态发送,不进 php-fpm。
