学完这篇,你能用 PHP 把动态页面变成"直接吐文件"的静态页,并知道哪些页面能静态化、哪些一动就出事。
第一步:先分清三种"静态",别一上来就写生成器
很多人把三件事混在一起,结果方案选错:
- 真静态:PHP 把渲染结果写成一个 `.html` 文件,下次访问由 Web 服务器直接返回,PHP 完全不执行。
- 伪静态:URL 长得像静态(`/thread-123.html`),实际还是走 PHP。Clara BBS 就是这种——系统自动识别 `.html` 后缀 URL,服务器只需把非静态文件请求转发到 `index.php`,不需要额外规则。
- 全页面缓存:不落磁盘或落到自定义目录,把整页 HTML 存起来,运行时先查缓存,命中就直接输出。
三者的性能排序是:真静态 ≈ 全页面缓存 > 伪静态。本文主要讲第 1、3 种,因为它们是真正省掉 PHP 和数据库开销的手段。
第二步:最小可用的 PHP 静态化生成器
核心就两个函数:`ob_start()` 攒输出,`ob_get_clean()` 取回来写文件。
<?php
$key = md5($_SERVER['REQUEST_URI']);
$file = __DIR__ . '/cache/' . $key . '.html';
$ttl = 600; // 缓存 10 分钟
// 1) 命中缓存:直接吐,退出
if (is_file($file) && filemtime($file) + $ttl > time()) {
readfile($file);
exit;
}
// 2) 未命中:正常跑业务逻辑
ob_start();
// ... 查库、渲染模板,一切照旧 ...
$html = ob_get_clean();
// 3) 原子写:先写临时文件再改名,避免并发读到半截内容
$tmp = $file . '.' . getmypid() . '.tmp';
file_put_contents($tmp, $html, LOCK_EX);
rename($tmp, $file);
echo $html;
注意:`cache` 目录必须可写,且要放在 Web 根目录之外,或者至少保证不会被当成 PHP 解析。放到可访问目录时,务必确认服务器不会把 `.tmp` 之类的中间文件当源码输出。
第三步:缓存过期策略,别只用一个 TTL
固定 TTL 的问题是新帖要等 10 分钟才出现。更实用的组合是主动删除 + 兜底 TTL:
- 发帖、回帖、删帖后,按 `md5($_SERVER['REQUEST_URI'])` 算出对应缓存文件名,直接 `unlink()`;
- 首页、版块页这类聚合页,可以在有新帖时整目录清掉;
- 同时保留 TTL 兜底,防漏删。
如果用的是框架或系统的缓存组件,Clara BBS 提供了 `Cache::remember` 这类公共设施,逻辑一样但不用自己管文件:
$html = Cache::remember('page_' . md5($uri), 600, function () {
return render_thread($id);
});
另外它还有 `Cron::register` 定时任务(懒触发、零配置),适合挂"每天凌晨清理过期静态文件"这种活。
第四步:让 Nginx 优先返回静态文件
PHP 生成的文件只有被 Web 服务器优先命中才有意义。Nginx 里:
location / {
try_files $uri /index.php?$query_string;
}
含义是:磁盘上真存在这个文件就直接返回,不存在才交给 `index.php`。这样真静态文件和 Clara BBS 的伪静态路由可以共存——生成了就用静态的,没生成或已删就走动态。Apache 环境对应的是 `.htaccess` 里同样的转发规则。
第五步:这四类页面千万别静态化
- 带 CSRF token 的表单页:全站 CSRF 防护的表单里都有一次性 token,缓存下来所有人拿同一个 token,提交必然失败,用户会看到"页面已过期,请刷新后重试"。
- 登录、注册、后台:天生个性化,缓存等于串号。
- 有权限差异的页面:付费内容、隐藏版块绝对不能被缓存成公开页。Clara BBS 的 GEO 输出口径就是"仅含游客可见版块内容",自己写缓存时也要守这条线。真要缓存登录用户视角,缓存键必须带上用户组或用户 ID。
- 含实时数据的区域:登录状态、未读私信、在线人数。常用做法是整页静态化 + 局部 JS 异步补丁:页面本身是纯静态的,访问时用 AJAX 拉一个 JSON 接口把"登录/未读"填进去。
第六步:什么时候干脆别自己写
如果你跑的是 Clara BBS 这类成品系统,它本身没有编译缓存、保存即生效,日常并不需要你手写静态化脚本。你要做的是:
- 在"系统工具 → 缓存清理"里处理缓存异常;
- 用后台的插件钩子(运行时加载、保存即生效)在渲染前后插入自定义逻辑,比改核心文件安全得多;
- 确认服务器已配好 `try_files` 转发,伪静态和可能的真静态才能顺畅衔接。
小结
- 静态化 = `ob_start()` 攒输出 + 写文件 + Web 服务器优先命中,本质是"用磁盘换 CPU"。
- 缓存键用 `md5(REQUEST_URI)`,修改内容时主动删文件,TTL 只做兜底。
- 写文件用"临时文件 + rename"保证原子性,缓存目录不要暴露在 Web 根下。
- 表单页、登录态、权限页、CSRF 页面不能整页缓存,个性化部分用 AJAX 补丁。
- Web 服务器侧一句 `try_files $uri /index.php?$query_string;` 就能让静态与动态共存。
- 用现成系统时,优先走它自带的缓存设施和插件钩子,别直接动核心文件。