首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

社區論壇 PHP & MySQL 實戰(三):版面列表、展開/收合回文與分頁核心邏輯

     編輯:PHP 綜合知識

前言

論壇首頁與版面列表頁是流量最大的入口,其核心邏輯包含:版面資訊顯示、主題列表分頁、回文數統計、以及經典的展開/收合回文 (Threaded View) 互動。本文將這些邏輯現代化重構,修正原始碼中的 N+1 查詢問題、HTML 輸出耦合、全域變數濫用等架構缺陷。

核心工具函式

時間格式化 — sdatetime()

function formatDateTime(string $datetime): string
{
    // 原邏輯:將 'YYYY-MM-DD HH:MM:SS' 轉為 'MM/DD HH:MM'
    try {
        $dt = new DateTime($datetime);
        return $dt->format('m/d H:i');
    } catch (Exception $e) {
        return 'Invalid Date';
    }
}

回文數統計 — reAnum() (優化版:避免 N+1)

原始碼在列表迴圈中對每個主題查詢一次 COUNT(*),造成嚴重 N+1 問題。現代做法:單次查詢聚合 或 快取欄位。

// 方案 A:單次查詢取得指定版面所有主題的回文數 (陣列映射)
function getReplyCountsMap(PDO $pdo, int $aid): array
{
    $stmt = $pdo->prepare("SELECT sid, COUNT(*) as cnt FROM bbs_docs WHERE aid = :aid GROUP BY sid");
    $stmt->execute([':aid' => $aid]);
    return $stmt->fetchAll(PDO::FETCH_KEY_PAIR); // 回傳 [sid => count, ...]
}

// 方案 B:資料表新增 reply_count 欄位,發文時同步更新 (推薦生產環境)
// ALTER TABLE bbs_subj ADD COLUMN reply_count INT UNSIGNED NOT NULL DEFAULT 0;
// 發文時:UPDATE bbs_subj SET reply_count = reply_count + 1 WHERE id = :sid

核心模組:版面列表渲染 — area()

function renderAreaList(PDO $pdo, string $selfUrl, int $aid, int $pgno, int $pageSize = 20): array
{
    // 1. 取得版面資訊
    $stmt = $pdo->prepare("SELECT banner, body, master FROM bbs_area WHERE id = :aid");
    $stmt->execute([':aid' => $aid]);
    $area = $stmt->fetch();
    if (!$area) throw new Exception('版面不存在');

    // 2. 計算總頁數
    $totalStmt = $pdo->prepare("SELECT COUNT(*) FROM bbs_subj WHERE aid = :aid");
    $totalStmt->execute([':aid' => $aid]);
    $total = (int)$totalStmt->fetchColumn();
    $totalPages = (int)ceil($total / $pageSize);

    // 3. 修正頁碼
    $pgno = max(1, min($pgno, max(1, $totalPages)));
    $offset = ($pgno - 1) * $pageSize;

    // 4. 撈取當頁主題 (含 JOIN 版面可省查詢,此處簡化)
    $stmt = $pdo->prepare("
        SELECT id, banner, poster, views, wdate
        FROM bbs_subj
        WHERE aid = :aid
        ORDER BY wdate DESC
        LIMIT :offset, :limit
    ");
    $stmt->bindValue(':aid', $aid, PDO::PARAM_INT);
    $stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
    $stmt->bindValue(':limit', $pageSize, PDO::PARAM_INT);
    $stmt->execute();
    $subjects = $stmt->fetchAll();

    // 5. 批次取得回文數 (避免 N+1)
    $replyCounts = getReplyCountsMap($pdo, $aid);

    // 6. 組裝視圖資料
    $rows = [];
    foreach ($subjects as $subj) {
        $rows[] = [
            'id'      => $subj['id'],
            'banner'  => htmlspecialchars($subj['banner']),
            'poster'  => htmlspecialchars($subj['poster']),
            'views'   => $subj['views'],
            'wdate'   => formatDateTime($subj['wdate']),
            'replies' => $replyCounts[$subj['id']] ?? 0,
            'is_current' => false, // 列表頁通常無當前主題高亮
        ];
    }

    // 7. 分頁導航 HTML
    $pagebar = renderPagebar($selfUrl, $aid, $pgno, $totalPages);

    return [
        'area_banner' => htmlspecialchars($area['banner']),
        'area_body'   => nl2br(htmlspecialchars($area['body'])),
        'rows'        => $rows,
        'pagebar'     => $pagebar,
        'pgno'        => $pgno,
        'total_pages' => $totalPages,
        'show_search' => true,
    ];
}

展開/收合回文邏輯 — openlist() / Cookie 控制

原始碼使用 Cookie OPEN_LIST (1=展開, 0=收合) 決定列表頁是否預設展開各主題下的回文。

function toggleOpenList(string $selfUrl, int $aid, int $pgno, bool $open): void
{
    $value = $open ? '1' : '0';
    // Cookie 有效期 1 年,Path 為網站根目錄
    setcookie('OPEN_LIST', $value, time() + 31536000, '/', '', false, true); // Secure=true 僅限 HTTPS
    header("Location: {$selfUrl}?aid={$aid}&pgno={$pgno}");
    exit;
}

// 列表頁渲染時判斷
$openList = isset($_COOKIE['OPEN_LIST']) && $_COOKIE['OPEN_LIST'] === '1';

if ($openList) {
    // 方案 A:AJAX 載入 (推薦,不阻塞首屏)
    // 前端 JS 讀取 data-sid 屬性,點擊展開圖示時 fetch 回文片段
    // 方案 B:伺服器端預渲染 (原始做法,資料量大時極慢)
    // 需對每個主題查詢回文,極易導致查詢數爆炸
}

架構建議: 「展開回文」屬於次要內容,**強烈建議採用 AJAX 非同步載入**。列表頁只渲染主題骨架,前端按需請求 /api/subject/{sid}/replies。這能:

  • 大幅降低首頁 TTFB (Time To First Byte)
  • 減少資料庫連線佔用
  • 支援無限滾動/分頁載入回文

搜尋表單整合

版面列表頁頂部通常內建搜尋框:

<form action="<?= $selfUrl ?>" method="get" class="search-form">
    <input type="hidden" name="aid" value="<?= $aid ?>">
    <input type="hidden" name="proc" value="search">
    <input type="text" name="key" placeholder="搜尋主題..." value="<?= htmlspecialchars($_GET['key'] ?? '') ?>">
    <button type="submit">搜尋</button>
</form>

Controller 整合範例

// area.php
$aid = (int)($_GET['aid'] ?? 1);
$pgno = (int)($_GET['pgno'] ?? 1);
$proc = $_GET['proc'] ?? 'area';

$pdo = getPdoConnection();

switch ($proc) {
    case 'colloff':
        toggleOpenList($_SERVER['PHP_SELF'], $aid, $pgno, false);
        break;
    case 'collon':
        toggleOpenList($_SERVER['PHP_SELF'], $aid, $pgno, true);
        break;
    case 'search':
        // 呼叫 search() 邏輯...
        break;
    default: // 'area'
        $data = renderAreaList($pdo, $_SERVER['PHP_SELF'], $aid, $pgno);
        include 'templates/area.php';
        break;
}

小結

本文重構了版面列表的核心邏輯:分頁查詢優化 (LIMIT/OFFSET)、回文數批次聚合解決 N+1、展開/收合狀態由 Cookie 控制並建議 AJAX 化、時間格式化使用 DateTime 物件。這些改進將原本脆弱、難維護的程序碼轉化為清晰、可測試、可擴充的服務層函式,為高流量論壇奠定效能基礎。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved