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. Ella Ella

    安装过程中遇到权限问题,后来发现是static-site目录没创建成功。手动创建并设置755权限就好了。

  2. Lucas Lucas

    和Redis缓存配合使用,简直是速度狂魔。静态文件优先,Redis缓存次之,最后才是PHP动态生成。三层加速,爽。

  3. Grace Grace

    This should be in the official Typecho plugin repository. It's one of the most useful plugins I've seen in years.

  4. Thomas Thomas

    试用了一下,发现有个小bug:如果文章URL里有中文,生成的静态文件名会变成下划线,但访问正常。希望后续版本能优化。

  5. Sophia Sophia

    强烈建议加上批量删除功能,现在只能全部删除或者手动删。有时候只想删某一类页面,比如只删分类归档页。