專案架構概覽
guestbook/
├── config.php # 資料庫配置
├── functions.php # 共用函數
├── header.php # 頁首模板
├── footer.php # 頁尾模板
├── index.php # 留言列表(首頁)
├── post.php # 發表留言
├── view.php # 留言詳情(可選)
├── admin/
│ ├── login.php # 管理員登入
│ ├── logout.php # 登出
│ ├── dashboard.php # 管理後台
│ └── delete.php # 刪除留言
├── css/
│ └── style.css
└── sql/
└── schema.sql
資料庫結構
-- 資料庫:lyb
CREATE TABLE `admin` (
`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`username` VARCHAR(50) NOT NULL UNIQUE,
`password_hash` VARCHAR(255) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE `messages` (
`id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`username` VARCHAR(50) NOT NULL,
`sex` ENUM('M','F') DEFAULT NULL,
`qq` VARCHAR(20) DEFAULT NULL,
`email` VARCHAR(100) DEFAULT NULL,
`content` TEXT NOT NULL,
`ip` VARCHAR(45) DEFAULT NULL,
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
核心檔案實作
config.php - 統一配置
<?php
define('DB_HOST', 'localhost');
define('DB_NAME', 'lyb');
define('DB_USER', 'root');
define('DB_PASS', '');
define('DB_CHARSET', 'utf8mb4');
function pdo() {
static $pdo = null;
if ($pdo === null) {
$dsn = "mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=" . DB_CHARSET;
$pdo = new PDO($dsn, DB_USER, DB_PASS, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]);
}
return $pdo;
}
// CSRF Token
function csrf_token() {
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
function csrf_verify($token) {
return isset($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token);
}
?>
header.php - 共用頁首(含導航與 Session)
<?php session_start(); ?>
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<title>留言板系統</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<header>
<nav>
<ul>
<li><a href="index.php">查看留言</a></li>
<li><a href="post.php">發表留言</a></li>
<?php if (!empty($_SESSION['admin_id'])): ?>
<li><a href="admin/dashboard.php">管理後台</a></li>
<li><a href="admin/logout.php">登出</a></li>
<?php else: ?>
<li><a href="admin/login.php">管理登入</a></li>
<?php endif; ?>
</ul>
</nav>
</header>
<main>
post.php - 發表留言(含驗證與 CSRF)
<?php
require_once 'config.php';
require_once 'header.php';
$errors = [];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (!csrf_verify($_POST['csrf_token'] ?? '')) {
$errors[] = '無效的請求,請重新整理頁面再試。';
} else {
$username = trim($_POST['username'] ?? '');
$content = trim($_POST['content'] ?? '');
$email = trim($_POST['email'] ?? '');
$sex = $_POST['sex'] ?? null;
$qq = trim($_POST['qq'] ?? '');
if ($username === '') $errors[] = '請輸入暱稱';
if ($content === '') $errors[] = '請輸入留言內容';
if ($email && !filter_var($email, FILTER_VALIDATE_EMAIL)) $errors[] = 'Email 格式不正確';
if (empty($errors)) {
$stmt = pdo()->prepare('INSERT INTO messages (username, sex, qq, email, content, ip) VALUES (?, ?, ?, ?, ?, ?)');
$stmt->execute([$username, $sex, $qq, $email, $content, $_SERVER['REMOTE_ADDR'] ?? 'unknown']);
header('Location: index.php?success=1');
exit;
}
}
}
?>
<h2>發表留言</h2>
<?php if ($errors): ?>
<div class="alert"><ul><?php foreach ($errors as $e) echo "<li>" . htmlspecialchars($e) . "</li>"; ?></ul></div>
<?php endif; ?>
<form method="POST">
<input type="hidden" name="csrf_token" value="<?= csrf_token() ?>">
<div><label>暱稱:<input type="text" name="username" required></label></div>
<div><label>性別:<select name="sex"><option value="">不公開</option><option value="M">男</option><option value="F">女</option></select></label></div>
<div><label>QQ:<input type="text" name="qq"></label></div>
<div><label>Email:<input type="email" name="email"></label></div>
<div><label>留言:<textarea name="content" rows="5" required></textarea></label></div>
<button type="submit">送出</button>
</form>
<?php require_once 'footer.php'; ?>
admin/login.php - 管理員登入(密碼雜湊)
<?php
require_once '../config.php';
session_start();
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$username = $_POST['username'] ?? '';
$password = $_POST['password'] ?? '';
$stmt = pdo()->prepare('SELECT id, password_hash FROM admin WHERE username = ?');
$stmt->execute([$username]);
$admin = $stmt->fetch();
if ($admin && password_verify($password, $admin['password_hash'])) {
$_SESSION['admin_id'] = $admin['id'];
$_SESSION['admin_user'] = $username;
header('Location: dashboard.php');
exit;
}
$error = '帳號或密碼錯誤';
}
?>
<!DOCTYPE html>
<html><head><meta charset="UTF-8"><title>管理登入</title></head><body>
<h2>管理員登入</h2>
<?php if (!empty($error)) echo "<p class='error'>" . htmlspecialchars($error) . "</p>"; ?>
<form method="POST">
<input type="text" name="username" placeholder="帳號" required><br>
<input type="password" name="password" placeholder="密碼" required><br>
<button type="submit">登入</button>
</form>
</body></html>
關鍵改進點
- 密碼安全:使用
password_hash() / password_verify(),不再明文儲存
- CSRF 保護:所有表單採用 Token 驗證
- 輸入驗證與輸出轉義:
filter_var、htmlspecialchars 全方位防護
- PDO 單例模式:避免重複建立連接
- Session 管理:登入狀態集中控制
- 關注點分離:配置、函數、模板、業務邏輯分層