萬盛學電腦網

您现在的位置: 萬盛學電腦網 >> 程式設計 >  >> 網路程式設計 >> PHP 程式設計

PHP 靜態頁面生成:fopen/fwrite 檔案寫入與目錄管理實戰

核心生成函數

<?php
/**
 * 生成靜態 HTML 檔案
 *
 * @param string $content   完整 HTML 內容
 * @param string $dirPath   目標目錄路徑 (相對或絕對)
 * @param string $fileName  檔案名稱
 * @return bool             成功與否
 */
function makeHtml(string $content, string $dirPath, string $fileName): bool {
    // 1. 確保目錄存在 (遞迴建立、權限 0755)
    if (!is_dir($dirPath)) {
        if (!mkdir($dirPath, 0755, true) && !is_dir($dirPath)) {
            error_log("建立目錄失敗: $dirPath");
            return false;
        }
    }
    
    // 2. 組合完整路徑 (統一使用 DIRECTORY_SEPARATOR)
    $filePath = $dirPath . DIRECTORY_SEPARATOR . $fileName;
    
    // 3. 以獨佔鎖定寫入 (防止並發損壞)
    $handle = fopen($filePath, 'wb');
    if (!$handle) {
        error_log("無法開啟檔案寫入: $filePath");
        return false;
    }
    
    if (flock($handle, LOCK_EX)) {
        $bytes = fwrite($handle, $content);
        flock($handle, LOCK_UN);
    } else {
        error_log("檔案鎖定失敗: $filePath");
        fclose($handle);
        return false;
    }
    
    fclose($handle);
    return $bytes !== false;
}
?>

檔案命名策略

<?php
/**
 * 產生唯一、不可預測、安全的檔案名稱
 */
function generateSafeFileName(string $prefix = '', string $ext = 'html'): string {
    $random = bin2hex(random_bytes(8));  // 16 字元十六進位
    $timestamp = date('YmdHis');
    return $prefix . $timestamp . '_' . $random . '.' . $ext;
}

// 範例:article_20240115103045_a1b2c3d4.html
?>

批次生成流程控制

<?php
function batchGenerateStaticPages(PDO $pdo, int $batchSize = 50, int $startId = 0): array {
    $results = ['success' => 0, 'failed' => 0, 'errors' => []];
    
    // 1. 取得待生成文章 (游標分頁避免記憶體問題)
    $stmt = $pdo->prepare('SELECT id, title, content, category, slug FROM articles WHERE is_published = 1 AND id > ? ORDER BY id ASC LIMIT ?');
    $stmt->execute([$startId, $batchSize]);
    $articles = $stmt->fetchAll();
    
    if (!$articles) {
        return $results + ['completed' => true];
    }
    
    $lastId = 0;
    foreach ($articles as $article) {
        $lastId = $article['id'];
        
        // 2. 渲染模板 (此處簡化,實際建議用 Twig/Blade)
        $html = renderArticleTemplate($article);
        
        // 3. 目標路徑:/var/www/html/static/category/slug.html
        $dirPath = '/var/www/html/static/' . $article['category'];
        $fileName = $article['slug'] . '.html';
        
        // 4. 生成
        if (makeHtml($html, $dirPath, $fileName)) {
            $results['success']++;
        } else {
            $results['failed']++;
            $results['errors'][] = "ID {$article['id']}: {$article['title']}";
        }
    }
    
    // 5. 回傳下一批次起始 ID
    return $results + ['completed' => false, 'next_start_id' => $lastId];
}

/** 簡易模板渲染 */
function renderArticleTemplate(array $article): string {
    ob_start();
    include __DIR__ . '/templates/article.php';
    return ob_get_clean();
}
?>

前端輪詢觸發批次生成

<!-- generate.html -->
<button id="startBtn" onclick="startGeneration()">開始生成

Nginx 靜態檔案優化設定

# nginx.conf
location /static/ {
    expires 30d;
    add_header Cache-Control "public, immutable";
    # Gzip 預壓縮
    gzip_static on;
    # 嘗試 WebP 圖片
    location ~* \.(jpg|jpeg|png)$ {
        add_header Vary Accept;
        try_files $uri$webp_suffix $uri =404;
    }
}

注意事項與最佳實踐

  1. 檔案鎖定:flock() 防止多程序同時寫入同一檔案導致損壞。
  2. 原子性寫入:寫入暫存檔 .tmp 後 rename() 覆蓋,避免讀取到半寫入內容。
  3. 權限管理:Web 使用者 (www-data) 需有目錄寫入權,生成腳本建議由 CLI 執行而非 Web 請求。
  4. 清理機制:定期刪除舊版本靜態檔,或部署時整體替換目錄。
  5. CDN 整合:生成後觸發 CDN 清除快取 API。

原子性寫入改良版

<?php
function makeHtmlAtomic(string $content, string $dirPath, string $fileName): bool {
    $finalPath = $dirPath . DIRECTORY_SEPARATOR . $fileName;
    $tmpPath = $finalPath . '.tmp.' . bin2hex(random_bytes(4));
    
    if (!is_dir($dirPath) && !mkdir($dirPath, 0755, true)) return false;
    
    $bytes = file_put_contents($tmpPath, $content, LOCK_EX);
    if ($bytes === false) return false;
    
    // rename 在同一檔案系統上是原子操作
    return @rename($tmpPath, $finalPath);
}
?>

靜態頁面生成是高流量網站的關鍵優化手段,配合原子寫入、批次處理、CDN 快取,能將伺服器負載降低 90% 以上。

copyright © 萬盛學電腦網 all rights reserved