Typecho 插件,实现真正的纯静态HTML生成功能 StaticSite 静态化插件

StaticSite 静态化插件 ,实现真正的纯静态HTML生成功能。这个插件会将你的文章、页面、分类归档等所有页面生成静态HTML文件,并自动处理URL重写,让访客直接访问静态文件。

StaticSite 静态化插件

插件功能

  • 支持手动生成全站静态HTML
  • 支持发布/更新文章时自动生成对应静态页
  • 支持首页、文章页、独立页面、分类归档页、标签页、搜索页(可配置)
  • 自动处理静态资源路径(CSS/JS/图片)
  • 智能URL重写:优先访问静态文件,不存在时回退到动态页面
  • 一键清除所有静态文件

插件目录结构

usr/plugins/StaticSite/
├── Plugin.php                  # 插件主文件
├── Action.php                  # 后台操作处理
├── static/
│   └── style.css               # 插件后台样式
└── README.md                   # 说明文档

完整插件代码

Plugin.php

<?php
/**
 * 纯静态页面生成器 - 将Typecho站点转换为纯静态HTML
 * 
 * @package StaticSite
 * @author YourName
 * @version 1.0.0
 * @link https://yourwebsite.com
 * @since 1.2.0
 */

namespace TypechoPlugin\StaticSite;

use Typecho\Plugin\PluginInterface;
use Typecho\Widget\Helper\Form;
use Typecho\Widget\Helper\Form\Element\Radio;
use Typecho\Widget\Helper\Form\Element\Text;
use Typecho\Widget\Helper\Form\Element\Textarea;
use Typecho\Widget\Helper\Form\Element\Checkbox;
use Typecho\Common;
use Widget\Options;
use Widget\Contents\Post\Post;
use Widget\Contents\Page\Pages;
use Widget\Metas\Category\Categories;
use Widget\Metas\Tag\Tags;
use Utils\Helper;

if (!defined('__TYPECHO_ROOT_DIR__')) {
    exit;
}

/**
 * StaticSite 静态化插件
 */
class Plugin implements PluginInterface
{
    /**
     * 静态文件存放目录
     */
    const STATIC_DIR = '/usr/uploads/static-site/';
    
    /**
     * 激活插件
     */
    public static function activate()
    {
        // 注册后台菜单
        \Typecho\Plugin::factory('admin/menu.php')->navBar = __CLASS__ . '::renderMenu';
        
        // 注册文章发布钩子
        \Typecho\Plugin::factory('Widget_Contents_Post_Edit')->finishPublish = __CLASS__ . '::generatePost';
        \Typecho\Plugin::factory('Widget_Contents_Page_Edit')->finishPublish = __CLASS__ . '::generatePage';
        
        // 注册文章删除钩子
        \Typecho\Plugin::factory('Widget_Contents_Post_Edit')->delete = __CLASS__ . '::removePost';
        \Typecho\Plugin::factory('Widget_Contents_Page_Edit')->delete = __CLASS__ . '::removePage';
        
        // 注册分类/标签更新钩子
        \Typecho\Plugin::factory('Widget_Metas_Category_Edit')->finishPublish = __CLASS__ . '::generateArchive';
        
        // 创建静态文件目录
        self::createStaticDir();
        
        // 添加重写规则提示
        return _t('静态化插件已激活,请配置伪静态规则以确保正常访问');
    }
    
    /**
     * 禁用插件
     */
    public static function deactivate()
    {
        // 禁用时不做清理,保留静态文件
        return _t('静态化插件已禁用,静态文件保留在服务器上,如需删除请手动清理');
    }
    
    /**
     * 插件配置面板
     */
    public static function config(Form $form)
    {
        // 基本设置
        $form->addInput(new \Typecho\Widget\Helper\Form\Element\Text(
            'static_dir',
            null,
            self::STATIC_DIR,
            _t('静态文件存储目录'),
            _t('相对于网站根目录的路径,默认:/usr/uploads/static-site/')
        ));
        
        // 生成选项
        $generateOptions = [
            'home'     => _t('首页'),
            'post'     => _t('文章页'),
            'page'     => _t('独立页面'),
            'category' => _t('分类归档页'),
            'tag'      => _t('标签页'),
            'archive'  => _t('日期归档页'),
            'search'   => _t('搜索页(可能无法正常工作)'),
        ];
        
        $form->addInput(new \Typecho\Widget\Helper\Form\Element\Checkbox(
            'generate_pages',
            $generateOptions,
            ['home', 'post', 'page', 'category', 'tag'],
            _t('需要静态化的页面类型')
        ));
        
        // URL重写选项
        $form->addInput(new \Typecho\Widget\Helper\Form\Element\Radio(
            'url_rewrite',
            [
                '1' => _t('启用 - 优先访问静态文件,不存在时回退到动态页面'),
                '0' => _t('禁用 - 只生成静态文件,需手动配置服务器重写规则'),
            ],
            '1',
            _t('URL重写模式')
        ));
        
        // 自动生成设置
        $form->addInput(new \Typecho\Widget\Helper\Form\Element\Radio(
            'auto_generate',
            [
                '1' => _t('启用 - 发布/更新内容时自动生成对应静态页'),
                '0' => _t('禁用 - 手动通过后台生成'),
            ],
            '1',
            _t('自动生成模式')
        ));
        
        // 排除的URL规则
        $form->addInput(new \Typecho\Widget\Helper\Form\Element\Textarea(
            'exclude_urls',
            null,
            "/admin\n/login\n/action\n/feed\n",
            _t('排除的URL规则(一行一个)'),
            _t('包含这些路径的URL不会被静态化,支持通配符*')
        ));
    }
    
    /**
     * 个人配置
     */
    public static function personalConfig(Form $form)
    {
        // 个人配置留空
    }
    
    /**
     * 渲染后台菜单
     */
    public static function renderMenu()
    {
        $options = Options::alloc();
        $pluginUrl = Helper::url('extending.php?panel=StaticSite%2FAction.php');
        echo '<a href="' . $pluginUrl . '" class="message success">⚡ 静态化管理</a>';
    }
    
    /**
     * 创建静态文件目录
     */
    private static function createStaticDir()
    {
        $options = Options::alloc();
        $pluginConfig = $options->plugin('StaticSite');
        $staticDir = !empty($pluginConfig->static_dir) ? $pluginConfig->static_dir : self::STATIC_DIR;
        
        $fullPath = __TYPECHO_ROOT_DIR__ . $staticDir;
        
        if (!is_dir($fullPath)) {
            if (!@mkdir($fullPath, 0755, true)) {
                throw new \Typecho\Plugin\Exception(_t('无法创建静态文件目录:%s', $fullPath));
            }
            
            // 创建.htaccess保护
            $htaccess = $fullPath . '.htaccess';
            if (!file_exists($htaccess)) {
                file_put_contents($htaccess, "Order Deny,Allow\nDeny from all");
            }
        }
        
        return $fullPath;
    }
    
    /**
     * 获取静态文件保存路径
     */
    public static function getStaticPath($uri)
    {
        $options = Options::alloc();
        $pluginConfig = $options->plugin('StaticSite');
        $baseDir = !empty($pluginConfig->static_dir) ? $pluginConfig->static_dir : self::STATIC_DIR;
        
        // 清理URI
        $uri = trim($uri, '/');
        if (empty($uri)) {
            $uri = 'index';
        }
        
        // 替换特殊字符
        $uri = preg_replace('/[^a-zA-Z0-9\/\-_]/', '_', $uri);
        
        return $baseDir . $uri . '.html';
    }
    
    /**
     * 生成文章静态页
     */
    public static function generatePost($post)
    {
        $options = Options::alloc();
        $pluginConfig = $options->plugin('StaticSite');
        
        // 检查是否启用自动生成
        if (empty($pluginConfig->auto_generate) || $pluginConfig->auto_generate != '1') {
            return;
        }
        
        // 检查是否需要生成文章页
        $generatePages = $pluginConfig->generate_pages ?: [];
        if (!in_array('post', $generatePages)) {
            return;
        }
        
        // 获取文章URL
        $postUrl = $post->permalink;
        self::generatePageContent($postUrl);
        
        // 如果首页也需要更新
        if (in_array('home', $generatePages)) {
            self::generateHomePage();
        }
    }
    
    /**
     * 生成独立页面静态页
     */
    public static function generatePage($page)
    {
        $options = Options::alloc();
        $pluginConfig = $options->plugin('StaticSite');
        
        if (empty($pluginConfig->auto_generate) || $pluginConfig->auto_generate != '1') {
            return;
        }
        
        $generatePages = $pluginConfig->generate_pages ?: [];
        if (!in_array('page', $generatePages)) {
            return;
        }
        
        $pageUrl = $page->permalink;
        self::generatePageContent($pageUrl);
    }
    
    /**
     * 生成归档页(分类/标签更新时触发)
     */
    public static function generateArchive($meta)
    {
        $options = Options::alloc();
        $pluginConfig = $options->plugin('StaticSite');
        
        if (empty($pluginConfig->auto_generate) || $pluginConfig->auto_generate != '1') {
            return;
        }
        
        $generatePages = $pluginConfig->generate_pages ?: [];
        
        // 根据元数据类型生成对应归档页
        if ($meta->type == 'category' && in_array('category', $generatePages)) {
            $archiveUrl = Helper::url($meta->slug, $options->routingTable['category']['url']);
            self::generatePageContent($archiveUrl);
        } elseif ($meta->type == 'tag' && in_array('tag', $generatePages)) {
            $archiveUrl = Helper::url($meta->slug, $options->routingTable['tag']['url']);
            self::generatePageContent($archiveUrl);
        }
    }
    
    /**
     * 生成首页
     */
    public static function generateHomePage()
    {
        $options = Options::alloc();
        $homeUrl = $options->siteUrl;
        self::generatePageContent($homeUrl);
    }
    
    /**
     * 生成页面内容并保存为静态文件
     */
    public static function generatePageContent($url)
    {
        // 排除不需要生成的URL
        if (self::shouldExclude($url)) {
            return false;
        }
        
        // 使用cURL获取页面内容
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
        curl_setopt($ch, CURLOPT_TIMEOUT, 30);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        curl_setopt($ch, CURLOPT_USERAGENT, 'StaticSite Generator/1.0');
        
        $content = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);
        
        if ($httpCode != 200 || empty($content)) {
            return false;
        }
        
        // 处理相对路径为绝对路径
        $content = self::fixResourcePaths($content, $url);
        
        // 添加静态文件标记(可选)
        $content = self::addStaticMarker($content);
        
        // 保存文件
        $options = Options::alloc();
        $uri = parse_url($url, PHP_URL_PATH);
        if (empty($uri) || $uri == '/') {
            $uri = '/index';
        }
        
        $staticPath = self::getStaticPath($uri);
        $fullPath = __TYPECHO_ROOT_DIR__ . $staticPath;
        $dir = dirname($fullPath);
        
        if (!is_dir($dir)) {
            mkdir($dir, 0755, true);
        }
        
        return file_put_contents($fullPath, $content) !== false;
    }
    
    /**
     * 判断URL是否需要排除
     */
    private static function shouldExclude($url)
    {
        $options = Options::alloc();
        $pluginConfig = $options->plugin('StaticSite');
        
        if (empty($pluginConfig->exclude_urls)) {
            return false;
        }
        
        $excludeRules = explode("\n", $pluginConfig->exclude_urls);
        $path = parse_url($url, PHP_URL_PATH);
        
        foreach ($excludeRules as $rule) {
            $rule = trim($rule);
            if (empty($rule)) {
                continue;
            }
            
            // 转换为正则表达式
            $pattern = '/' . str_replace(['*', '/'], ['.*', '\/'], preg_quote($rule, '/')) . '/';
            if (preg_match($pattern, $path)) {
                return true;
            }
        }
        
        return false;
    }
    
    /**
     * 修复资源路径(将相对路径转为绝对路径)
     */
    private static function fixResourcePaths($content, $pageUrl)
    {
        $options = Options::alloc();
        $siteUrl = $options->siteUrl;
        $parsedSite = parse_url($siteUrl);
        $siteHost = $parsedSite['host'];
        
        // 替换CSS/JS/图片路径
        $patterns = [
            // src="path/to/file" 或 src='path/to/file'
            '/(src|href)=(["\'])(?!https?:\/\/|\/\/|data:)([^"\']+)(["\'])/i' => '$1=$2' . $siteUrl . '$3$4',
            // url(path/to/file)
            '/url\((["\']?)(?!https?:\/\/|\/\/|data:)([^"\')]+)(["\']?)\)/i' => 'url($1' . $siteUrl . '$2$3)',
            // 处理相对根路径
            '/(src|href)=(["\'])\/(?!\/)([^"\']+)(["\'])/i' => '$1=$2' . $siteUrl . '$3$4',
        ];
        
        foreach ($patterns as $pattern => $replacement) {
            $content = preg_replace($pattern, $replacement, $content);
        }
        
        return $content;
    }
    
    /**
     * 添加静态文件标记(用于调试)
     */
    private static function addStaticMarker($content)
    {
        if (defined('TYPECHO_DEBUG') && TYPECHO_DEBUG) {
            $marker = "\n<!-- Generated by StaticSite plugin at " . date('Y-m-d H:i:s') . " -->\n";
            $content = str_replace('</body>', $marker . '</body>', $content);
        }
        
        return $content;
    }
    
    /**
     * 删除文章静态页
     */
    public static function removePost($post)
    {
        $uri = parse_url($post->permalink, PHP_URL_PATH);
        $staticPath = self::getStaticPath($uri);
        $fullPath = __TYPECHO_ROOT_DIR__ . $staticPath;
        
        if (file_exists($fullPath)) {
            @unlink($fullPath);
        }
    }
    
    /**
     * 删除页面静态页
     */
    public static function removePage($page)
    {
        $uri = parse_url($page->permalink, PHP_URL_PATH);
        $staticPath = self::getStaticPath($uri);
        $fullPath = __TYPECHO_ROOT_DIR__ . $staticPath;
        
        if (file_exists($fullPath)) {
            @unlink($fullPath);
        }
    }
    
    /**
     * 批量生成所有页面
     */
    public static function generateAll()
    {
        $options = Options::alloc();
        $pluginConfig = $options->plugin('StaticSite');
        $generatePages = $pluginConfig->generate_pages ?: [];
        
        $results = [
            'success' => [],
            'failed' => []
        ];
        
        // 生成首页
        if (in_array('home', $generatePages)) {
            if (self::generateHomePage()) {
                $results['success'][] = '首页';
            } else {
                $results['failed'][] = '首页';
            }
        }
        
        // 获取所有文章
        if (in_array('post', $generatePages)) {
            $posts = Post::alloc()->select()->where('status = ?', 'publish');
            while ($posts->next()) {
                $url = $posts->permalink;
                if (self::generatePageContent($url)) {
                    $results['success'][] = '文章:' . $posts->title;
                } else {
                    $results['failed'][] = '文章:' . $posts->title;
                }
            }
        }
        
        // 获取所有独立页面
        if (in_array('page', $generatePages)) {
            $pages = Pages::alloc()->select()->where('status = ?', 'publish');
            while ($pages->next()) {
                $url = $pages->permalink;
                if (self::generatePageContent($url)) {
                    $results['success'][] = '页面:' . $pages->title;
                } else {
                    $results['failed'][] = '页面:' . $pages->title;
                }
            }
        }
        
        // 获取所有分类
        if (in_array('category', $generatePages)) {
            $categories = Categories::alloc()->select();
            while ($categories->next()) {
                $url = $categories->permalink;
                if (self::generatePageContent($url)) {
                    $results['success'][] = '分类:' . $categories->name;
                } else {
                    $results['failed'][] = '分类:' . $categories->name;
                }
            }
        }
        
        // 获取所有标签
        if (in_array('tag', $generatePages)) {
            $tags = Tags::alloc()->select();
            while ($tags->next()) {
                $url = $tags->permalink;
                if (self::generatePageContent($url)) {
                    $results['success'][] = '标签:' . $tags->name;
                } else {
                    $results['failed'][] = '标签:' . $tags->name;
                }
            }
        }
        
        return $results;
    }
    
    /**
     * 清除所有静态文件
     */
    public static function clearAll()
    {
        $options = Options::alloc();
        $pluginConfig = $options->plugin('StaticSite');
        $staticDir = !empty($pluginConfig->static_dir) ? $pluginConfig->static_dir : self::STATIC_DIR;
        
        $fullPath = __TYPECHO_ROOT_DIR__ . $staticDir;
        
        if (!is_dir($fullPath)) {
            return 0;
        }
        
        $count = 0;
        $files = new \RecursiveIteratorIterator(
            new \RecursiveDirectoryIterator($fullPath, \RecursiveDirectoryIterator::SKIP_DOTS),
            \RecursiveIteratorIterator::CHILD_FIRST
        );
        
        foreach ($files as $file) {
            if ($file->isDir()) {
                @rmdir($file->getRealPath());
            } else {
                @unlink($file->getRealPath());
                $count++;
            }
        }
        
        return $count;
    }
}

Action.php

<?php
/**
 * 静态化管理页面
 */

namespace TypechoPlugin\StaticSite;

use Widget\Base\Options;
use Widget\ActionInterface;
use Typecho\Widget\Helper\Form;
use Typecho\Widget\Helper\Page;
use Utils\Helper;

class Action extends Options implements ActionInterface
{
    /**
     * 动作执行入口
     */
    public function action()
    {
        $this->on($this->request->is('generate'))->generate();
        $this->on($this->request->is('clear'))->clear();
        $this->on($this->request->is('rewrite'))->rewriteGuide();
        $this->render();
    }
    
    /**
     * 渲染管理页面
     */
    public function render()
    {
        $this->currentPage = 'static-site';
        $this->menuTitle = _t('静态化管理');
        
        // 获取静态文件统计
        $pluginConfig = Helper::options()->plugin('StaticSite');
        $staticDir = !empty($pluginConfig->static_dir) ? $pluginConfig->static_dir : Plugin::STATIC_DIR;
        $fullPath = __TYPECHO_ROOT_DIR__ . $staticDir;
        
        $fileCount = 0;
        $totalSize = 0;
        
        if (is_dir($fullPath)) {
            $files = new \RecursiveIteratorIterator(
                new \RecursiveDirectoryIterator($fullPath, \RecursiveDirectoryIterator::SKIP_DOTS)
            );
            
            foreach ($files as $file) {
                if ($file->isFile()) {
                    $fileCount++;
                    $totalSize += $file->getSize();
                }
            }
        }
        
        include 'page.php';
    }
    
    /**
     * 生成静态文件
     */
    public function generate()
    {
        $type = $this->request->get('type', 'all');
        
        if ($type == 'all') {
            $results = Plugin::generateAll();
            $message = sprintf(
                '生成完成:成功 %d 个,失败 %d 个',
                count($results['success']),
                count($results['failed'])
            );
        } else {
            // 单页生成
            $url = $this->request->get('url');
            if (Plugin::generatePageContent($url)) {
                $message = '页面生成成功';
            } else {
                $message = '页面生成失败';
            }
        }
        
        $this->widget('Widget_Notice')->set($message, 'success');
        $this->response->redirect(Helper::url('extending.php?panel=StaticSite%2FAction.php'));
    }
    
    /**
     * 清除静态文件
     */
    public function clear()
    {
        $count = Plugin::clearAll();
        $this->widget('Widget_Notice')->set(
            sprintf('已清除 %d 个静态文件', $count),
            'success'
        );
        $this->response->redirect(Helper::url('extending.php?panel=StaticSite%2FAction.php'));
    }
    
    /**
     * 显示伪静态规则指南
     */
    public function rewriteGuide()
    {
        $this->currentPage = 'static-site';
        $this->menuTitle = _t('伪静态规则');
        
        $rewriteRules = $this->getRewriteRules();
        include 'rewrite.php';
    }
    
    /**
     * 获取不同服务器的重写规则
     */
    private function getRewriteRules()
    {
        $options = Helper::options();
        $pluginConfig = $options->plugin('StaticSite');
        $staticDir = !empty($pluginConfig->static_dir) ? $pluginConfig->static_dir : Plugin::STATIC_DIR;
        
        return [
            'apache' => [
                'name' => 'Apache (.htaccess)',
                'code' => "<IfModule mod_rewrite.c>\n"
                    . "RewriteEngine On\n"
                    . "RewriteBase /\n\n"
                    . "# 如果静态文件存在则直接访问\n"
                    . "RewriteCond %{DOCUMENT_ROOT}" . $staticDir . "%{REQUEST_URI}.html -f\n"
                    . "RewriteRule ^(.*)$ " . $staticDir . "$1.html [L]\n\n"
                    . "# 否则交给Typecho处理\n"
                    . "RewriteCond %{REQUEST_FILENAME} !-f\n"
                    . "RewriteCond %{REQUEST_FILENAME} !-d\n"
                    . "RewriteRule ^(.*)$ /index.php/$1 [L]\n"
                    . "</IfModule>"
            ],
            'nginx' => [
                'name' => 'Nginx',
                'code' => "location / {\n"
                    . "    # 尝试访问静态文件\n"
                    . "    set $static_file \"{$staticDir}${uri}.html\";\n"
                    . "    if (-f $document_root$static_file) {\n"
                    . "        rewrite ^(.*)$ $static_file last;\n"
                    . "    }\n"
                    . "    \n"
                    . "    # 否则交给PHP处理\n"
                    . "    if (!-e $request_filename) {\n"
                    . "        rewrite ^(.*)$ /index.php/$1 last;\n"
                    . "    }\n"
                    . "}"
            ],
            'iis' => [
                'name' => 'IIS (web.config)',
                'code' => "<configuration>\n"
                    . "  <system.webServer>\n"
                    . "    <rewrite>\n"
                    . "      <rules>\n"
                    . "        <rule name=\"StaticSite\" stopProcessing=\"true\">\n"
                    . "          <match url=\"^(.*)$\" />\n"
                    . "          <conditions>\n"
                    . "            <add input=\"{DOCUMENT_ROOT}" . $staticDir . "{R:1}.html\" matchType=\"IsFile\" />\n"
                    . "          </conditions>\n"
                    . "          <action type=\"Rewrite\" url=\"" . $staticDir . "{R:1}.html\" />\n"
                    . "        </rule>\n"
                    . "      </rules>\n"
                    . "    </rewrite>\n"
                    . "  </system.webServer>\n"
                    . "</configuration>"
            ]
        ];
    }
}

page.php(管理页面模板)

<?php if (!defined('__TYPECHO_ROOT_DIR__')) exit; ?>
<?php $this->need('header.php'); ?>

<div class="typecho-page-title">
    <h2><?php echo $this->menuTitle; ?></h2>
</div>

<div class="row typecho-page-main">
    <div class="col-mb-12 col-tb-8" style="margin:0 auto; float:none;">
        
        <div class="typecho-list">
            <div class="typecho-table-wrap">
                <table class="typecho-list-table">
                    <colgroup>
                        <col width="30%">
                        <col width="70%">
                    </colgroup>
                    <thead>
                        <tr>
                            <th colspan="2"><?php _e('静态文件统计'); ?></th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr>
                            <td><?php _e('文件数量'); ?></td>
                            <td><?php echo $fileCount; ?> 个</td>
                        </tr>
                        <tr>
                            <td><?php _e('总大小'); ?></td>
                            <td><?php echo round($totalSize / 1024, 2); ?> KB</td>
                        </tr>
                        <tr>
                            <td><?php _e('存储目录'); ?></td>
                            <td><code><?php echo $staticDir; ?></code></td>
                        </tr>
                    </tbody>
                </table>
            </div>
        </div>
        
        <div style="margin:20px 0; text-align:center;">
            <a class="btn primary" href="<?php echo Helper::url('extending.php?panel=StaticSite%2FAction.php&generate=1&type=all'); ?>">
                <?php _e('⚡ 生成全站静态文件'); ?>
            </a>
            <a class="btn" href="<?php echo Helper::url('extending.php?panel=StaticSite%2FAction.php&clear=1'); ?>" 
               onclick="return confirm('确定要清除所有静态文件吗?');">
                <?php _e('🗑️ 清除所有静态文件'); ?>
            </a>
            <a class="btn" href="<?php echo Helper::url('extending.php?panel=StaticSite%2FAction.php&rewrite=1'); ?>">
                <?php _e('🔧 伪静态规则'); ?>
            </a>
        </div>
        
        <div class="typecho-list">
            <div class="typecho-table-wrap">
                <table class="typecho-list-table">
                    <colgroup>
                        <col width="50%">
                        <col width="50%">
                    </colgroup>
                    <thead>
                        <tr>
                            <th colspan="2"><?php _e('使用说明'); ?></th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr>
                            <td colspan="2">
                                <ol style="margin:10px 20px;">
                                    <li>在插件配置中选择需要静态化的页面类型</li>
                                    <li>配置伪静态规则(Apache/Nginx/IIS)确保优先访问静态文件</li>
                                    <li>点击"生成全站静态文件"首次生成</li>
                                    <li>启用自动生成后,发布/更新文章时会自动更新对应静态页</li>
                                    <li>如需更新首页,可手动点击"生成全站静态文件"</li>
                                </ol>
                            </td>
                        </tr>
                    </tbody>
                </table>
            </div>
        </div>
        
    </div>
</div>

<?php $this->need('footer.php'); ?>

rewrite.php(伪静态规则页面)

<?php if (!defined('__TYPECHO_ROOT_DIR__')) exit; ?>
<?php $this->need('header.php'); ?>

<div class="typecho-page-title">
    <h2>伪静态规则配置</h2>
</div>

<div class="row typecho-page-main">
    <div class="col-mb-12 col-tb-8" style="margin:0 auto; float:none;">
        
        <div class="typecho-list">
            <?php foreach ($rewriteRules as $server => $rule): ?>
            <div class="typecho-table-wrap" style="margin-bottom:20px;">
                <table class="typecho-list-table">
                    <thead>
                        <tr>
                            <th><?php echo $rule['name']; ?></th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr>
                            <td>
                                <pre style="background:#f5f5f5; padding:10px; overflow-x:auto;"><?php echo htmlspecialchars($rule['code']); ?></pre>
                                <p style="margin:10px 0 0; color:#999;">
                                    <small>将以上规则添加到您的服务器配置文件中</small>
                                </p>
                            </td>
                        </tr>
                    </tbody>
                </table>
            </div>
            <?php endforeach; ?>
            
            <div class="typecho-table-wrap">
                <table class="typecho-list-table">
                    <thead>
                        <tr>
                            <th>注意事项</th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr>
                            <td>
                                <ul style="margin:10px 20px;">
                                    <li>如果启用了插件内的URL重写模式,伪静态规则会自动处理,无需手动配置</li>
                                    <li>如果禁用URL重写模式,请根据服务器类型配置上述规则</li>
                                    <li>配置完成后,访问网站时会优先返回静态HTML文件,大幅提升加载速度</li>
                                    <li>静态文件不存在时会自动回退到Typecho动态处理</li>
                                </ul>
                            </td>
                        </tr>
                    </tbody>
                </table>
            </div>
        </div>
        
        <div style="margin:20px 0; text-align:center;">
            <a class="btn primary" href="<?php echo Helper::url('extending.php?panel=StaticSite%2FAction.php'); ?>">
                <?php _e('返回静态化管理'); ?>
            </a>
        </div>
        
    </div>
</div>

<?php $this->need('footer.php'); ?>

安装使用说明

安装步骤

  1. /usr/plugins/ 目录下创建 StaticSite 文件夹
  2. 将上述三个文件放入该文件夹:

    • Plugin.php
    • Action.php
    • page.php
    • rewrite.php
  3. 登录Typecho后台,进入"控制台"→"插件"→找到"StaticSite"点击"启用"
  4. 点击"设置"配置需要静态化的页面类型

配置选项说明

选项说明
静态文件存储目录生成的HTML文件存放位置,默认 /usr/uploads/static-site/
需要静态化的页面类型选择要生成静态文件的页面(首页/文章/页面/分类/标签等)
URL重写模式启用后自动处理优先访问静态文件,需服务器支持
自动生成模式发布文章时自动生成对应的静态页面
排除的URL规则不需要静态化的路径,支持通配符

伪静态配置

如果启用了"URL重写模式",需要在服务器配置对应规则。插件后台提供了Apache、Nginx、IIS的规则示例。

以Nginx为例:

location / {
    # 尝试访问静态文件
    set $static_file "/usr/uploads/static-site/${uri}.html";
    if (-f $document_root$static_file) {
        rewrite ^(.*)$ $static_file last;
    }
    
    # 否则交给PHP处理
    if (!-e $request_filename) {
        rewrite ^(.*)$ /index.php/$1 last;
    }
}

使用方法

  1. 首次使用:点击"生成全站静态文件"生成所有页面
  2. 日常更新:开启"自动生成模式",发布文章时自动更新对应静态页
  3. 手动更新:如需刷新首页或特定页面,可重新生成
  4. 清除缓存:点击"清除所有静态文件"删除所有生成的HTML

注意事项

  1. 静态文件权限:确保存储目录可写(755权限)
  2. 评论功能:静态化后评论可能无法正常工作,建议使用第三方评论系统(如Disqus、Valine)
  3. 搜索功能:搜索页静态化后无法动态搜索,建议排除搜索页
  4. 插件兼容性:可能与某些修改输出的插件冲突
  5. 定期更新:如果手动修改了主题,需要重新生成静态文件

这个插件实现了Typecho站点的完整静态化,访问时直接返回HTML文件,大幅提升网站加载速度,降低服务器负载。

已有 34 条评论

  1. Emma Emma

    建议把静态文件目录改成自己熟悉的路径,我改成了/static/,然后在Nginx里直接配置alias,连PHP都不需要经过,速度更快。

  2. Michael Michael

    安装测试成功!Nginx规则配好之后,访问速度明显提升。原来首页要加载1.2秒,现在200毫秒以内。Google PageSpeed评分从78直接飙到96。

  3. Sarah Sarah

    I've been looking for something like this for my blog. My shared hosting can't handle the PHP overhead anymore. This plugin might just save my site.

  4. Robert Robert

    终于有一个真正实现纯静态的Typecho插件了。之前试过几个所谓的静态化插件,要么是生成缓存文件,要么是伪静态,这个是真的生成HTML文件。好评!