CarefreeCMS 文档CarefreeCMS 文档
指南
  • 内容管理
  • 多站点管理
  • AI文章生成
  • SEO优化
  • 静态化生成
API
  • FAQ
  • 更新日志
  • 贡献指南
  • v1.3.0
  • v1.2.0
  • v1.1.0
GitHub
指南
  • 内容管理
  • 多站点管理
  • AI文章生成
  • SEO优化
  • 静态化生成
API
  • FAQ
  • 更新日志
  • 贡献指南
  • v1.3.0
  • v1.2.0
  • v1.1.0
GitHub
  • 开始使用

    • 介绍
    • 安装指南
    • 快速开始
    • 系统配置
  • 基础功能

    • 文章管理
    • 分类管理
    • 标签管理
    • 单页管理
    • 媒体库
  • 高级功能

    • 模板开发
    • 静态化生成
    • 搜索功能
    • 权限管理
    • 用户管理
  • AI 功能

    • AI 服务商配置
    • AI 模型配置
    • 提示词工程
  • 系统管理

    • 定时任务
    • 日志管理
    • 安全指南
    • 性能优化

静态化生成

本文详细介绍 CarefreeCMS 的静态化生成功能。

什么是静态化

静态化是将动态网页(需要 PHP 和数据库)转换为静态 HTML 文件的过程。

优势

  • 性能提升:无需 PHP 解析,访问速度快 10-100 倍
  • 降低负载:减少数据库查询,服务器压力小
  • 更好的 SEO:搜索引擎更容易抓取和索引
  • CDN 友好:静态文件更适合 CDN 分发
  • 高并发支持:轻松应对大流量访问

原理

动态访问流程:
用户请求 → Nginx → PHP-FPM → 查询数据库 → 渲染模板 → 返回 HTML

静态访问流程:
用户请求 → Nginx → 直接返回 HTML 文件

配置静态化

基础配置

编辑 config/static.php:

return [
    // 静态文件存储路径
    'path' => root_path() . 'html/',

    // 生成规则
    'rules' => [
        // 首页
        'index' => [
            'template' => 'index',
            'file' => 'index.html',
        ],

        // 文章列表
        'article_list' => [
            'template' => 'article/list',
            'file' => 'article/index.html',
            'paginate' => true,
        ],

        // 文章详情
        'article_detail' => [
            'template' => 'article/detail',
            'file' => 'article/{id}.html',
        ],

        // 分类页
        'category' => [
            'template' => 'category/index',
            'file' => 'category/{id}.html',
        ],
    ],

    // 排除规则
    'exclude' => [
        '/admin/*',
        '/api/*',
        '/search',
    ],
];

Nginx 配置

配置静态优先访问:

server {
    listen 80;
    server_name example.com;
    root /var/www/cms/html;

    # 优先访问静态文件
    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    # PHP 处理
    location ~ \.php$ {
        fastcgi_pass unix:/var/run/php/php8.0-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
    }

    # 静态资源
    location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
        expires 30d;
        add_header Cache-Control "public, immutable";
    }
}

生成静态页

后台生成

进入后台 系统管理 → 静态化管理:

  1. 生成首页:点击"生成首页"按钮
  2. 生成文章列表:选择要生成的分类,点击"生成列表"
  3. 生成文章详情:选择文章,点击"生成详情"
  4. 全站生成:点击"全站生成",生成所有页面

命令行生成

# 生成首页
php think static:index

# 生成文章列表
php think static:article-list

# 生成指定文章
php think static:article 123

# 生成分类页
php think static:category 1

# 全站生成
php think static:all

# 指定数量
php think static:article-list --limit=100

自动生成

文章发布时自动生成

class ArticleController
{
    public function save()
    {
        $article = Article::create(request()->post());

        // 自动生成静态页
        event('ArticleCreated', $article);

        return json(['code' => 200, 'msg' => '保存成功']);
    }
}

// 事件监听器
class ArticleCreatedListener
{
    public function handle($article)
    {
        $static = new StaticService();

        // 生成文章详情页
        $static->generateArticle($article->id);

        // 更新首页
        $static->generateIndex();

        // 更新列表页
        $static->generateArticleList();
    }
}

定时生成

// 定时任务
class CronJob
{
    public function daily()
    {
        // 每天凌晨生成全站
        (new StaticService())->generateAll();
    }
}

Crontab 配置:

# 每天凌晨2点生成全站
0 2 * * * cd /var/www/cms && php think static:all >> /var/log/static.log 2>&1

生成逻辑

StaticService 实现

<?php
namespace app\service;

use think\facade\View;
use app\model\Article;
use app\model\Category;

class StaticService
{
    protected $basePath;

    public function __construct()
    {
        $this->basePath = config('static.path');
    }

    // 生成首页
    public function generateIndex()
    {
        $data = [
            'articles' => Article::where('status', 'published')
                ->order('create_time', 'desc')
                ->limit(10)
                ->select(),
            'categories' => Category::select(),
        ];

        $html = View::fetch('index', $data);
        $this->saveFile('index.html', $html);

        return true;
    }

    // 生成文章详情
    public function generateArticle($id)
    {
        $article = Article::find($id);

        if (!$article || $article->status !== 'published') {
            return false;
        }

        $data = ['article' => $article];
        $html = View::fetch('article/detail', $data);

        $file = "article/{$id}.html";
        $this->saveFile($file, $html);

        return true;
    }

    // 生成文章列表
    public function generateArticleList($page = 1, $pageSize = 20)
    {
        $articles = Article::where('status', 'published')
            ->order('create_time', 'desc')
            ->page($page, $pageSize)
            ->select();

        $total = Article::where('status', 'published')->count();
        $totalPages = ceil($total / $pageSize);

        $data = [
            'articles' => $articles,
            'page' => $page,
            'total_pages' => $totalPages,
        ];

        $html = View::fetch('article/list', $data);

        $file = $page === 1 ? 'article/index.html' : "article/page_{$page}.html";
        $this->saveFile($file, $html);

        // 递归生成其他页
        if ($page < $totalPages) {
            $this->generateArticleList($page + 1, $pageSize);
        }

        return true;
    }

    // 生成分类页
    public function generateCategory($id, $page = 1)
    {
        $category = Category::find($id);
        $articles = Article::where('category_id', $id)
            ->where('status', 'published')
            ->order('create_time', 'desc')
            ->page($page, 20)
            ->select();

        $data = [
            'category' => $category,
            'articles' => $articles,
        ];

        $html = View::fetch('category/index', $data);

        $file = "category/{$id}.html";
        if ($page > 1) {
            $file = "category/{$id}/page_{$page}.html";
        }

        $this->saveFile($file, $html);

        return true;
    }

    // 全站生成
    public function generateAll()
    {
        // 生成首页
        $this->generateIndex();

        // 生成所有文章
        $articles = Article::where('status', 'published')->column('id');
        foreach ($articles as $id) {
            $this->generateArticle($id);
        }

        // 生成文章列表
        $this->generateArticleList();

        // 生成所有分类
        $categories = Category::column('id');
        foreach ($categories as $id) {
            $this->generateCategory($id);
        }

        return true;
    }

    // 保存文件
    protected function saveFile($file, $content)
    {
        $filepath = $this->basePath . $file;
        $dir = dirname($filepath);

        // 创建目录
        if (!is_dir($dir)) {
            mkdir($dir, 0755, true);
        }

        // 写入文件
        file_put_contents($filepath, $content);

        Log::info('生成静态页', ['file' => $file]);
    }

    // 删除静态文件
    public function deleteFile($file)
    {
        $filepath = $this->basePath . $file;

        if (file_exists($filepath)) {
            unlink($filepath);
        }
    }

    // 删除文章静态页
    public function deleteArticle($id)
    {
        $this->deleteFile("article/{$id}.html");
    }

    // 清空所有静态文件
    public function clear()
    {
        $this->deleteDirectory($this->basePath);
    }

    protected function deleteDirectory($dir)
    {
        if (!is_dir($dir)) {
            return;
        }

        $files = glob($dir . '/*');
        foreach ($files as $file) {
            if (is_dir($file)) {
                $this->deleteDirectory($file);
            } else {
                unlink($file);
            }
        }

        rmdir($dir);
    }
}

增量更新

智能更新

只更新变化的内容:

class IncrementalStatic
{
    // 记录更新时间
    protected function markUpdated($type, $id)
    {
        StaticLog::create([
            'type' => $type,
            'item_id' => $id,
            'update_time' => time(),
        ]);
    }

    // 检查是否需要更新
    protected function needUpdate($type, $id)
    {
        $log = StaticLog::where('type', $type)
            ->where('item_id', $id)
            ->find();

        if (!$log) {
            return true;
        }

        // 检查内容是否有更新
        if ($type === 'article') {
            $article = Article::find($id);
            return $article->update_time > $log->update_time;
        }

        return false;
    }

    // 增量生成
    public function incrementalGenerate()
    {
        // 查找需要更新的文章
        $articles = Article::where('status', 'published')
            ->where('update_time', '>', $this->getLastGenerateTime())
            ->select();

        foreach ($articles as $article) {
            if ($this->needUpdate('article', $article->id)) {
                (new StaticService())->generateArticle($article->id);
                $this->markUpdated('article', $article->id);
            }
        }
    }

    protected function getLastGenerateTime()
    {
        return StaticLog::max('update_time') ?: 0;
    }
}

更新策略

内容更新时

// 文章更新后
$article->save();

// 删除旧静态文件
(new StaticService())->deleteArticle($article->id);

// 生成新静态文件
(new StaticService())->generateArticle($article->id);

批量更新

# 只更新最近7天的文章
php think static:incremental --days=7

# 更新指定分类
php think static:category 1 --recursive

高级功能

多语言静态化

public function generateMultiLanguage($id)
{
    $languages = ['zh-CN', 'en-US', 'ja-JP'];

    foreach ($languages as $lang) {
        app()->lang->setLangSet($lang);

        $article = Article::find($id);
        $html = View::fetch('article/detail', ['article' => $article]);

        $file = "{$lang}/article/{$id}.html";
        $this->saveFile($file, $html);
    }
}

移动端适配

public function generateResponsive($id)
{
    $article = Article::find($id);

    // PC 版本
    $html = View::fetch('article/detail', ['article' => $article]);
    $this->saveFile("article/{$id}.html", $html);

    // 移动版本
    $mobileHtml = View::fetch('mobile/article/detail', ['article' => $article]);
    $this->saveFile("m/article/{$id}.html", $mobileHtml);
}

预加载优化

在 HTML 中添加预加载:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>{$article.title}</title>

    <!-- DNS 预解析 -->
    <link rel="dns-prefetch" href="//cdn.example.com">

    <!-- 预加载关键资源 -->
    <link rel="preload" href="/css/style.css" as="style">
    <link rel="preload" href="/js/app.js" as="script">

    <!-- 预连接 -->
    <link rel="preconnect" href="https://api.example.com">
</head>
<body>
    <!-- 内容 -->
</body>
</html>

缓存策略

HTTP 缓存头

protected function saveFile($file, $content)
{
    // 添加 meta 标签
    $cacheHeaders = <<<HTML
<meta http-equiv="Cache-Control" content="max-age=3600">
<meta http-equiv="Expires" content="{date('r', time() + 3600)}">
HTML;

    $content = str_replace('</head>', $cacheHeaders . '</head>', $content);

    file_put_contents($this->basePath . $file, $content);
}

ETag 支持

public function generateWithEtag($file, $content)
{
    $etag = md5($content);

    // 保存 ETag 信息
    $meta = [
        'etag' => $etag,
        'size' => strlen($content),
        'time' => time(),
    ];

    file_put_contents($this->basePath . $file, $content);
    file_put_contents($this->basePath . $file . '.meta', json_encode($meta));
}

监控与维护

生成日志

class StaticLog extends Model
{
    public static function record($action, $data)
    {
        self::create([
            'action' => $action,
            'data' => json_encode($data),
            'create_time' => time(),
        ]);
    }
}

// 使用
StaticLog::record('generate_article', [
    'id' => $id,
    'file' => $file,
    'size' => filesize($filepath),
    'time' => microtime(true) - $startTime,
]);

完整性检查

# 检查静态文件完整性
php think static:check

# 修复缺失文件
php think static:repair
class StaticChecker
{
    public function check()
    {
        $missing = [];

        // 检查所有已发布文章
        $articles = Article::where('status', 'published')->select();

        foreach ($articles as $article) {
            $file = config('static.path') . "article/{$article->id}.html";

            if (!file_exists($file)) {
                $missing[] = $article->id;
            }
        }

        return $missing;
    }

    public function repair()
    {
        $missing = $this->check();

        foreach ($missing as $id) {
            (new StaticService())->generateArticle($id);
        }
    }
}

性能优化

并发生成

use Workerman\Worker;

class ParallelStatic
{
    public function generateAll()
    {
        $articles = Article::where('status', 'published')->column('id');

        // 分批处理
        $chunks = array_chunk($articles, 100);

        foreach ($chunks as $chunk) {
            // 使用多进程
            $this->processChunk($chunk);
        }
    }

    protected function processChunk($ids)
    {
        $worker = new Worker();
        $worker->count = 4; // 4个进程

        $worker->onWorkerStart = function($worker) use ($ids) {
            foreach ($ids as $id) {
                (new StaticService())->generateArticle($id);
            }
        };

        Worker::runAll();
    }
}

压缩优化

public function saveFile($file, $content)
{
    // HTML 压缩
    $content = $this->compressHtml($content);

    // Gzip 压缩
    $gzContent = gzencode($content, 9);

    // 同时保存普通版和压缩版
    file_put_contents($this->basePath . $file, $content);
    file_put_contents($this->basePath . $file . '.gz', $gzContent);
}

protected function compressHtml($html)
{
    // 删除注释
    $html = preg_replace('/<!--.*?-->/s', '', $html);

    // 删除多余空白
    $html = preg_replace('/\s+/', ' ', $html);

    return trim($html);
}

最佳实践

生成时机

  • 发布时生成:文章发布后立即生成
  • 定时生成:每天凌晨全量生成
  • 手动触发:重要更新时手动生成

目录结构

html/
├── index.html              # 首页
├── article/
│   ├── index.html         # 列表首页
│   ├── page_2.html        # 列表第2页
│   ├── 1.html             # 文章1
│   └── 2.html             # 文章2
├── category/
│   ├── 1.html             # 分类1
│   └── 2.html             # 分类2
└── tag/
    └── 1.html             # 标签1

备份策略

生成前备份旧文件:

public function backup()
{
    $backupDir = runtime_path() . 'static_backup/' . date('YmdHis') . '/';

    // 复制整个目录
    $this->copyDirectory($this->basePath, $backupDir);
}

故障排查

常见问题

问题:生成的文件无法访问

解决:

# 检查文件权限
chmod -R 755 /var/www/cms/html

# 检查所有者
chown -R www-data:www-data /var/www/cms/html

问题:静态文件未更新

解决:

# 清空所有静态文件
php think static:clear

# 重新生成
php think static:all

问题:生成速度慢

解决:

  • 使用并发生成
  • 优化模板渲染
  • 减少数据库查询

相关资源

  • 性能优化
  • 静态化功能
  • 文章管理
在 GitHub 上编辑此页
Prev
模板开发
Next
搜索功能