静态化生成
CarefreeCMS 的静态化功能可以将动态页面生成为纯静态 HTML 文件,大幅提升访问速度和 SEO 效果。
为什么要静态化?
优势
⚡ 极速访问
- 无需 PHP 解析
- 无需数据库查询
- 直接读取 HTML 文件
- 响应时间 < 10ms
📈 SEO 友好
- 搜索引擎更容易抓取
- 页面加载速度快
- 降低跳出率
- 提高排名
💰 降低成本
- 减少服务器负载
- 降低带宽消耗
- 可使用廉价 CDN
- 节省服务器资源
🔒 安全性高
- 无 SQL 注入风险
- 无 PHP 代码执行风险
- 防止恶意攻击
- 数据更安全
📱 易于分发
- 可部署到任何 Web 服务器
- 支持 CDN 加速
- 方便备份和迁移
- 离线也能访问
劣势
🔄 实时性差
- 内容更新需要重新生成
- 动态功能受限
- 评论等交互功能需特殊处理
💾 存储空间
- 需要额外存储空间
- 大型网站文件数量多
⏰ 生成时间
- 首次生成耗时较长
- 批量更新需要时间
静态化流程
工作原理
1. 读取数据库数据
↓
2. 加载模板文件
↓
3. 渲染模板(填充数据)
↓
4. 生成 HTML 文件
↓
5. 保存到指定目录
目录结构
backend/html/ # 静态文件根目录
├── index.html # 首页
├── article/ # 文章目录
│ ├── 1.html
│ ├── 2.html
│ └── ...
├── category/ # 分类目录
│ ├── tech.html
│ ├── tech_2.html # 分页
│ └── ...
├── tag/ # 标签目录
│ ├── 1.html
│ └── ...
├── page/ # 单页目录
│ ├── about.html
│ └── ...
├── assets/ # 静态资源
│ ├── css/
│ ├── js/
│ └── images/
└── sitemap.xml # 网站地图
模板系统
模板文件
模板位于 backend/templates/ 目录:
backend/templates/
└── default/ # 默认主题
├── index.html # 首页模板
├── article.html # 文章详情模板
├── category.html # 分类列表模板
├── tag.html # 标签列表模板
├── page.html # 单页模板
├── search.html # 搜索结果模板
└── 404.html # 404 页面模板
模板语法
CarefreeCMS 使用 Twig 模板引擎:
变量输出
{# 输出文章标题 #}
{{ article.title }}
{# 输出带默认值 #}
{{ article.author|default('匿名') }}
{# 转义 HTML #}
{{ article.content|raw }}
条件判断
{% if article.is_top %}
<span class="badge">置顶</span>
{% endif %}
{% if article.view_count > 1000 %}
<span class="hot">热门</span>
{% else %}
<span class="normal">普通</span>
{% endif %}
循环遍历
{# 遍历文章列表 #}
{% for article in articles %}
<div class="article-item">
<h2>{{ article.title }}</h2>
<p>{{ article.summary }}</p>
</div>
{% endfor %}
{# 空列表处理 #}
{% for article in articles %}
...
{% else %}
<p>暂无文章</p>
{% endfor %}
包含其他模板
{# 包含头部 #}
{% include 'header.html' %}
{# 包含侧边栏并传递参数 #}
{% include 'sidebar.html' with {'type': 'hot'} %}
模板继承
{# 基础模板 base.html #}
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}默认标题{% endblock %}</title>
</head>
<body>
{% block content %}{% endblock %}
</body>
</html>
{# 子模板 article.html #}
{% extends 'base.html' %}
{% block title %}{{ article.title }}{% endblock %}
{% block content %}
<article>
<h1>{{ article.title }}</h1>
<div>{{ article.content|raw }}</div>
</article>
{% endblock %}
常用变量
全局变量
{{ site.name }} # 网站名称
{{ site.url }} # 网站URL
{{ site.keywords }} # 网站关键词
{{ site.description }} # 网站描述
{{ site.logo }} # 网站Logo
文章变量
{{ article.id }} # 文章ID
{{ article.title }} # 标题
{{ article.content }} # 内容
{{ article.summary }} # 摘要
{{ article.cover }} # 封面图
{{ article.author }} # 作者
{{ article.create_time}} # 发布时间
{{ article.view_count }} # 浏览次数
{{ article.category }} # 分类
{{ article.tags }} # 标签数组
分类变量
{{ category.name }} # 分类名称
{{ category.alias }} # 分类别名
{{ category.description}}# 分类描述
{{ category.articles }} # 文章列表
生成方式
单个生成
生成文章
- 在文章列表点击 生成静态页
- 或编辑文章后点击 发布并生成
- 生成完成显示文件路径
生成分类
- 在分类管理点击 生成静态页
- 生成该分类的列表页和分页
生成标签
- 在标签管理点击 生成静态页
- 生成该标签的文章列表页
批量生成
进入 系统管理 → 静态化管理
生成首页
操作: 生成首页
耗时: 约 1-2 秒
生成文件: html/index.html
生成所有文章
操作: 生成所有文章
数量: 显示文章总数
耗时: 约 N 秒(N = 文章数 / 10)
进度: 显示实时进度条
生成所有分类
操作: 生成所有分类
包含: 分类列表页 + 分页
生成文件: category/*.html
生成所有标签
操作: 生成所有标签
包含: 标签文章列表
生成文件: tag/*.html
生成全站
操作: 一键生成全站
包含:
- 首页
- 所有文章
- 所有分类
- 所有标签
- 所有单页
- Sitemap
定时生成
设置定时任务
进入 系统管理 → 定时任务
每日凌晨生成
任务名称: 每日静态化
Cron表达式: 0 2 * * *
执行方法: app\task\StaticTask@generateAll
状态: 启用
每小时生成新文章
任务名称: 增量静态化
Cron表达式: 0 * * * *
执行方法: app\task\StaticTask@generateNew
参数: {"hours": 1}
状态: 启用
自动生成
发布时自动生成
在文章发布时自动生成静态页:
编辑 backend/app/controller/api/Article.php:
public function save()
{
// 保存文章
$article = Article::create($data);
// 自动生成静态页
if ($article->status == 1) {
$this->generateStatic($article->id);
}
return Response::success('发布成功');
}
更新时自动生成
文章更新时重新生成:
public function update($id)
{
$article = Article::find($id);
$article->save($data);
// 重新生成静态页
$this->generateStatic($id);
return Response::success('更新成功');
}
访问配置
Nginx 配置
server {
listen 80;
server_name www.example.com;
# 静态文件目录
root /var/www/cms/backend/html;
index index.html;
# 优先访问静态文件
location / {
try_files $uri $uri/ /index.html;
}
# 静态资源缓存
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
# Gzip 压缩
gzip on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
gzip_min_length 1000;
}
Apache 配置
<VirtualHost *:80>
ServerName www.example.com
DocumentRoot /var/www/cms/backend/html
<Directory /var/www/cms/backend/html>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
# URL 重写
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.html [L]
</Directory>
# 开启压缩
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css
AddOutputFilterByType DEFLATE application/javascript application/json
</IfModule>
# 缓存设置
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType image/jpeg "access plus 1 month"
ExpiresByType text/css "access plus 1 week"
ExpiresByType application/javascript "access plus 1 week"
</IfModule>
</VirtualHost>
高级功能
增量生成
只生成新增或修改的内容:
// 获取最近1小时更新的文章
$articles = Article::where('update_time', '>', date('Y-m-d H:i:s', time() - 3600))
->select();
// 逐个生成
foreach ($articles as $article) {
$this->generateArticle($article);
}
多模板支持
为不同分类使用不同模板:
$category = Category::find($id);
// 根据分类选择模板
$template = $category->template ?: 'default/category.html';
// 生成静态页
$html = $this->render($template, $data);
URL 自定义
自定义静态文件的 URL 结构:
'url_rules' => [
// 文章: /article/年/月/ID.html
'article' => '/article/{year}/{month}/{id}.html',
// 分类: /分类别名/页码.html
'category' => '/{alias}/{page}.html',
// 标签: /tag/标签ID.html
'tag' => '/tag/{id}.html',
],
条件生成
根据条件决定是否生成:
// 只生成已发布且未过期的文章
if ($article->status == 1 && $article->expire_time > time()) {
$this->generateArticle($article);
}
// 只生成浏览量超过100的文章
if ($article->view_count >= 100) {
$this->generateArticle($article);
}
性能优化
生成优化
分批生成
// 每次生成100篇
$articles = Article::limit(100)->select();
foreach ($articles as $article) {
$this->generateArticle($article);
// 避免内存溢出
unset($article);
}
异步生成
// 使用队列异步生成
Queue::push('StaticJob', [
'type' => 'article',
'id' => $articleId,
]);
缓存模板
// 缓存编译后的模板
$twig = new \Twig\Environment($loader, [
'cache' => '/path/to/cache',
]);
访问优化
CDN 加速
# 使用 CDN 域名
location ~* \.(jpg|jpeg|png|gif|css|js)$ {
proxy_pass https://cdn.example.com;
}
浏览器缓存
# 设置缓存时间
location ~* \.(html)$ {
expires 1h;
add_header Cache-Control "public";
}
HTTP/2
listen 443 ssl http2;
常见问题
生成的页面没有样式?
原因:资源路径不对
解决:
- 检查模板中的资源路径是否正确
- 使用绝对路径或相对于根目录的路径
- 同步模板资源到静态目录
生成速度太慢?
优化方案:
- 使用分批生成
- 启用异步队列
- 优化数据库查询
- 增加服务器配置
如何实现动态功能?
方案一:Ajax 加载
<!-- 静态页面中加载评论 -->
<div id="comments"></div>
<script>
$.get('/api/comments?article_id=1', function(data) {
$('#comments').html(renderComments(data));
});
</script>
方案二:使用 SSI
<!--#include virtual="/dynamic/comments.php?id=1" -->
方案三:使用 ESI
<esi:include src="/api/comments?id=1" />
如何更新已生成的页面?
方式一:重新生成 编辑内容后点击"生成静态页"
方式二:定时更新 设置定时任务定期重新生成
方式三:按需更新 内容修改时自动触发生成
最佳实践
合理规划
- 确定哪些页面需要静态化
- 动态内容用 Ajax 加载
- 合理设置更新频率
优化模板
- 简洁高效的模板代码
- 减少不必要的查询
- 使用模板缓存
定时任务
- 设置在访问低峰期生成
- 增量更新优于全量更新
- 监控生成状态
混合部署
- 静态页面 + 动态接口
- 充分利用两者优势
- 提供最佳用户体验
