文章詳情頁常需「上一篇」「下一篇」導航。基本思路:按 ID 排序,找比當前 ID 小的最大一條(上一篇),比當前 ID 大的最小一條(下一篇)。本文在原始代碼基礎上優化 SQL、防注入、兼容 PDO/MySQLi,並補充邊界處理與緩存建議。
<?php
function getAdjacentPosts(PDO $pdo, int $currentId): array {
$result = ['prev' => null, 'next' => null];
// 上一篇:ID < 當前,倒序取第一條
$stmt = $pdo->prepare("SELECT id, title FROM article WHERE id < ? ORDER BY id DESC LIMIT 1");
$stmt->execute([$currentId]);
$result['prev'] = $stmt->fetch(PDO::FETCH_ASSOC);
// 下一篇:ID > 當前,正序取第一條
$stmt = $pdo->prepare("SELECT id, title FROM article WHERE id > ? ORDER BY id ASC LIMIT 1");
$stmt->execute([$currentId]);
$result['next'] = $stmt->fetch(PDO::FETCH_ASSOC);
return $result;
}
// 使用示例
$pdo = new PDO("mysql:host=localhost;dbname=blog;charset=utf8", "user", "pass");
$adj = getAdjacentPosts($pdo, (int)$_GET['id']);
echo '<div class="post-nav">';
if ($adj['prev']) {
echo '<a href="?id=' . $adj['prev']['id'] . '" class="prev">← 上一篇:' . htmlspecialchars($adj['prev']['title']) . '</a>';
} else {
echo '<span class="disabled">← 沒有上一篇了</span>';
}
if ($adj['next']) {
echo '<a href="?id=' . $adj['next']['id'] . '" class="next">下一篇:' . htmlspecialchars($adj['next']['title']) . ' →</a>';
} else {
echo '<span class="disabled">沒有下一篇了 →</span>';
}
echo '</div>';
$id 極不安全。ORDER BY id DESC 取最近的較小 ID;下一篇 ORDER BY id ASC 取最近的較大 ID。原文下一篇缺少 ORDER BY,依賴默認順序不可靠。SELECT id, title 而非 SELECT *,減少 I/O。(int)$_GET['id'] 強制整數,雙重保險。htmlspecialchars() 防 XSS。id 為主鍵(聚簇索引),範圍查詢極快。adj:{id},Value: JSON),過期時間設為永久,發布/刪除文章時清理相關 Key。mysql_num_rows 判斷存在性,實際 fetch 直接返回 false 即可,省一次邏輯。若業務要求按 created_at 導航:
$stmt = $pdo->prepare("SELECT id, title FROM article WHERE created_at < (SELECT created_at FROM article WHERE id = ?) ORDER BY created_at DESC LIMIT 1");
$stmt->execute([$currentId]);
需在 created_at 上建索引,並處理同一秒發布多篇的邊界情況(可二級排序 id)。
「上一篇/下一篇」看似簡單,卻是 SQL 基礎、安全習慣、性能意識的綜合體現。養成寫優化版的肌肉記憶,受益無窮。