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

PHP 留言板完整實戰教程:從數據庫設計到核心功能實現

     編輯:PHP 相關知識

留言板是 PHP 入門經典項目,涵蓋表單處理、數據庫交互、增刪改查(CRUD)、安全防護等核心技能。本文基於原教程代碼,重構為現代化、安全、可維護的版本,採用 PDO 預處理語句、XSS 防護、錯誤處理等最佳實踐。

一、項目結構規劃

msgboard/
├── config.php          # 數據庫配置
├── functions.php       # 通用函數
├── index.php           # 留言列表 + 發布表單
├── delete.php          # 刪除留言
├── edit.php            # 編輯留言
└── style.css           # 簡單樣式

二、數據庫設計

CREATE TABLE `messages` (
    `id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    `username` VARCHAR(50) NOT NULL,
    `email` VARCHAR(100) DEFAULT NULL,
    `content` TEXT NOT NULL,
    `ip` VARCHAR(45) NOT NULL,           -- 支援 IPv6
    `user_agent` VARCHAR(255) DEFAULT NULL,
    `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    `updated_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX `idx_created` (`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

三、核心代碼實現

1. config.php - 數據庫連接(PDO + 異常模式)

<?php
// config.php
declare(strict_types=1);

const DB_HOST = '127.0.0.1';
const DB_NAME = 'msgboard';
const DB_USER = 'root';
const DB_PASS = 'your_secure_password';
const DB_CHARSET = 'utf8mb4';

try {
    $pdo = new PDO(
        "mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=" . DB_CHARSET,
        DB_USER,
        DB_PASS,
        [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_EMULATE_PREPARES => false, // 真實預處理
        ]
    );
} catch (PDOException $e) {
    // 生產環境記錄日誌,不直接輸出
    error_log("Database connection failed: " . $e->getMessage());
    die('系統維護中,請稍後再試。');
}
?>

2. functions.php - 通用工具函數

<?php
// functions.php
declare(strict_types=1);

/** 安全輸出:防 XSS */
function h(string $str): string {
    return htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
}

/** 獲取客戶端真實 IP */
function getClientIp(): string {
    $headers = ['HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP', 'REMOTE_ADDR'];
    foreach ($headers as $header) {
        if (!empty($_SERVER[$header])) {
            $ips = explode(',', $_SERVER[$header]);
            return trim($ips[0]);
        }
    }
    return 'unknown';
}

/** 簡單防刷:同 IP 60 秒內限制提交 */
function checkRateLimit(PDO $pdo, string $ip): bool {
    $stmt = $pdo->prepare("SELECT 1 FROM messages WHERE ip = ? AND created_at > DATE_SUB(NOW(), INTERVAL 60 SECOND) LIMIT 1");
    $stmt->execute([$ip]);
    return !$stmt->fetch();
}
?>

3. index.php - 留言列表與發布(單文件 MVC 風格)

<?php
// index.php
declare(strict_types=1);
require_once 'config.php';
require_once 'functions.php';

$errors = [];
$success = '';

// 處理發布留言
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action']) && $_POST['action'] === 'post') {
    // CSRF 簡易驗證(生產環境應用專用 Token)
    if (!isset($_POST['csrf']) || $_POST['csrf'] !== ($_SESSION['csrf'] ?? '')) {
        $errors[] = '請求無效,請刷新頁面重試。';
    } else {
        $username = trim($_POST['username'] ?? '');
        $email = trim($_POST['email'] ?? '');
        $content = trim($_POST['content'] ?? '');
        $ip = getClientIp();
        
        // 驗證
        if (mb_strlen($username) < 2 || mb_strlen($username) > 50) {
            $errors[] = '用戶名長度需 2-50 字符。';
        }
        if ($email !== '' && !filter_var($email, FILTER_VALIDATE_EMAIL)) {
            $errors[] = '郵箱格式不正確。';
        }
        if (mb_strlen($content) < 1 || mb_strlen($content) > 2000) {
            $errors[] = '留言內容 1-2000 字符。';
        }
        if (!checkRateLimit($pdo, $ip)) {
            $errors[] = '操作過於頻繁,請 60 秒後再試。';
        }
        
        if (empty($errors)) {
            try {
                $stmt = $pdo->prepare("INSERT INTO messages (username, email, content, ip, user_agent) VALUES (?, ?, ?, ?, ?)");
                $stmt->execute([$username, $email, $content, $ip, $_SERVER['HTTP_USER_AGENT'] ?? '']);
                $success = '留言發布成功!';
                // 重新生成 CSRF Token
                $_SESSION['csrf'] = bin2hex(random_bytes(32));
            } catch (PDOException $e) {
                error_log("Insert message failed: " . $e->getMessage());
                $errors[] = '系統錯誤,請稍後再試。';
            }
        }
    }
}

// 初始化 CSRF Token
if (empty($_SESSION['csrf'])) {
    $_SESSION['csrf'] = bin2hex(random_bytes(32));
}

// 獲取留言列表(分頁)
$page = max(1, (int)($_GET['page'] ?? 1));
$perPage = 10;
$offset = ($page - 1) * $perPage;

$totalStmt = $pdo->query("SELECT COUNT(*) FROM messages");
$total = (int)$totalStmt->fetchColumn();
$totalPages = ceil($total / $perPage);

$stmt = $pdo->prepare("SELECT * FROM messages ORDER BY created_at DESC LIMIT ? OFFSET ?");
$stmt->bindValue(1, $perPage, PDO::PARAM_INT);
$stmt->bindValue(2, $offset, PDO::PARAM_INT);
$stmt->execute();
$messages = $stmt->fetchAll();
?>



    
    PHP 留言板
    


📝 留言板

發布留言

最新留言 (共 條)

1): ?>

暫無留言,搶個沙發吧!

4. delete.php - 刪除留言

<?php
// delete.php
require_once 'config.php';
require_once 'functions.php';

$id = (int)($_GET['id'] ?? 0);
if ($id > 0) {
    $stmt = $pdo->prepare("DELETE FROM messages WHERE id = ?");
    $stmt->execute([$id]);
}
header('Location: index.php');
exit;

5. edit.php - 編輯留言(簡易版)

<?php
// edit.php
require_once 'config.php';
require_once 'functions.php';

$id = (int)($_GET['id'] ?? 0);
$msg = null;

if ($id > 0) {
    $stmt = $pdo->prepare("SELECT * FROM messages WHERE id = ?");
    $stmt->execute([$id]);
    $msg = $stmt->fetch();
}

if (!$msg) {
    die('留言不存在');
}

// 處理更新
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $content = trim($_POST['content'] ?? '');
    if (mb_strlen($content) >= 1 && mb_strlen($content) <= 2000) {
        $stmt = $pdo->prepare("UPDATE messages SET content = ?, updated_at = NOW() WHERE id = ?");
        $stmt->execute([$content, $id]);
        header('Location: index.php');
        exit;
    }
    $error = '內容長度需 1-2000 字符';
}
?>



    
    編輯留言
    


✏️ 編輯留言 #

用戶: | 時間:

取消

6. style.css - 簡潔樣式

/* style.css */
* { box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; line-height: 1.6; max-width: 800px; margin: 2rem auto; padding: 0 1rem; }
.container { background: #fff; border-radius: 8px; padding: 2rem; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }
h1, h2 { color: #333; border-bottom: 1px solid #eee; padding-bottom: 0.5rem; }
.field { margin-bottom: 1rem; }
label { display: block; margin-bottom: 0.3rem; font-weight: 500; }
.required { color: #e74c3c; }
input, textarea { width: 100%; padding: 0.75rem; border: 1px solid #ddd; border-radius: 4px; font-size: 1rem; }
button { background: #3498db; color: #fff; border: none; padding: 0.75rem 1.5rem; border-radius: 4px; cursor: pointer; font-size: 1rem; }
button:hover { background: #2980b9; }
.alert { padding: 1rem; border-radius: 4px; margin-bottom: 1rem; }
.alert.success { background: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
.alert.error { background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
.message { border: 1px solid #eee; border-radius: 6px; padding: 1rem; margin: 1rem 0; }
.message header { display: flex; justify-content: space-between; margin-bottom: 0.5rem; font-size: 0.9rem; color: #666; }
.message footer { margin-top: 0.5rem; font-size: 0.85rem; }
.message footer a { margin-right: 1rem; color: #3498db; text-decoration: none; }
.pagination { display: flex; justify-content: center; gap: 1rem; margin-top: 2rem; }
.pagination a { color: #3498db; text-decoration: none; }
.empty { text-align: center; color: #999; padding: 3rem; }

四、關鍵安全與最佳實踐總結

風險點原教程寫法本教程改進
SQL 注入直接拼接變數進 SQLPDO 預處理語句(參數綁定)
XSS 攻擊直接輸出用戶輸入htmlspecialchars() 統一轉義 + nl2br() 保留換行
CSRF 攻擊無防護Session 型 CSRF Token 驗證
暴力刷屏無限制IP 級 60 秒頻率限制
編碼問題latin1/utf8 混用全鏈路 utf8mb4
錯誤暴露直接 die(mysql_error())記錄日誌,用戶端顯示友好提示
資源洩漏未關閉連接PDO 自動管理,腳本結束自動釋放

五、擴展建議

  1. 分頁優化:大數據量使用游標分頁(WHERE id < last_id)替代 OFFSET。
  2. 富文本支援:引入 DOMPurify + 編輯器(如 Quill),後端白名單過濾標籤。
  3. 巢狀回覆:增加 parent_id 字段,遞歸渲染樹狀結構。
  4. 用戶系統:整合登錄/註冊,關聯 user_id,權限控制編輯/刪除。
  5. 緩存加速:Redis 緩存熱門留言頁面,失效策略更新。
  6. API 化:提供 RESTful API,支援前端框架(Vue/React)對接。

這個重構版留言板雖然功能基礎,但奠定了安全、規範、可擴展的代碼基因。從這裡起步,逐步引入框架(Laravel/Symfony)、ORM、測試、CI/CD,即可成長為企業級應用開發者。

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