<?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.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;
}
}
flock() 防止多程序同時寫入同一檔案導致損壞。.tmp 後 rename() 覆蓋,避免讀取到半寫入內容。<?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% 以上。