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

PHP MySQL 聊天室實作教學:從資料表設計到即時通訊

     編輯:PHP 相關知識

PHP MySQL 聊天室完整實作指南

聊天室是 Web 即時通訊的經典入門專案。本文以傳統「iframe 輪詢」架構為基礎,完整呈現資料庫設計、後端 PHP 邏輯、前端框架結構,並探討現代 WebSocket/SSE 替代方案。

一、系統架構規劃

  • 資料匯集:所有用戶發言寫入 MySQL chat 表。
  • 資料分發:顯示頁面定期輪詢 (或長連線) 讀取最新訊息。
  • 頁框結構:上方顯示區 (自動重新整理) + 下方輸入區。

二、資料庫設計

CREATE DATABASE `chatroom` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE `chatroom`;

CREATE TABLE `chat` (
    `id`       INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    `nickname` VARCHAR(20)  NOT NULL COMMENT '暱稱',
    `content`  VARCHAR(500) NOT NULL COMMENT '發言內容',
    `created`  TIMESTAMP    NOT NULL DEFAULT CURRENT_TIMESTAMP,
    INDEX `idx_created` (`created`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

三、核心 PHP 程式碼

1. 登入頁 (login.php) - 設定暱稱 Cookie

<?php
// login.php
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['nickname'])) {
    $nick = trim($_POST['nickname']);
    // 簡單過濾
    $nick = htmlspecialchars($nick, ENT_QUOTES, 'UTF-8');
    setcookie('chat_nick', $nick, time() + 86400, '/'); // 1 天有效
    header('Location: main.php');
    exit;
}
?>
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>聊天室登入</title></head>
<body>
    <h2>請輸入暱稱進入聊天室</h2>
    <form method="post">
        <input type="text" name="nickname" maxlength="20" required placeholder="暱稱">
        <button type="submit">登入</button>
    </form>
</body></html>

2. 主框架頁 (main.php) - 雙 iframe 結構

<?php
// main.php
if (empty($_COOKIE['chat_nick'])) {
    header('Location: login.php');
    exit;
}
?>
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>PHP 聊天室</title>
<style>
    html, body { height: 100%; margin: 0; overflow: hidden; }
    frameset { border: none; }
</style></head>
<frameset rows="80%, *" cols="*">
    <frame src="display.php" name="display">
    <frame src="speak.php" name="speak">
</frameset>
</html>

3. 顯示頁 (display.php) - 輪詢讀取最新 20 則訊息

<?php
// display.php
header('Content-Type: text/html; charset=utf-8');
header('Cache-Control: no-cache, must-revalidate');
header('Expires: Sat, 26 Jul 1997 05:00:00 GMT');

$pdo = new PDO('mysql:host=localhost;dbname=chatroom;charset=utf8mb4', 'root', '');

// 取得最新 20 筆 (按時間倒序,再反轉顯示)
$stmt = $pdo->query("
    SELECT nickname, content, created 
    FROM (
        SELECT * FROM chat ORDER BY created DESC LIMIT 20
    ) t ORDER BY created ASC
");
$messages = $stmt->fetchAll(PDO::FETCH_ASSOC);

// 清理舊資料 (保留最近 100 筆)
$pdo->exec("DELETE FROM chat WHERE id NOT IN (SELECT id FROM (SELECT id FROM chat ORDER BY created DESC LIMIT 100) t)");
?>
<!DOCTYPE html>
<html><head><meta charset="utf-8">
<meta http-equiv="refresh" content="3"> <!-- 3 秒自動重新整理 -->
<style>
    body { font-family: sans-serif; padding: 10px; background: #f9f9f9; }
    .msg { margin: 5px 0; padding: 5px; background: #fff; border-radius: 4px; box-shadow: 0 1px 2px rgba(0,0,0,.1); }
    .nick { color: #0066cc; font-weight: bold; }
    .time { color: #999; font-size: 0.85em; margin-left: 8px; }
</style></head>
<body>
    <div id="chat-area">
        <?php foreach ($messages as $m): ?>
            <div class="msg">
                <span class="nick"><?php echo htmlspecialchars($m['nickname']); ?></span>
                <span class="time"><?php echo date('H:i:s', strtotime($m['created'])); ?></span>
                <br><?php echo nl2br(htmlspecialchars($m['content'])); ?>
            </div>
        <?php endforeach; ?>
    </div>
    <script>
        // 自動捲動到底部
        window.onload = function() { window.scrollTo(0, document.body.scrollHeight); };
    </script>
</body></html>

4. 發言頁 (speak.php) - 處理表單提交寫入資料庫

<?php
// speak.php
session_start(); // 可選:用 session 驗證防灌水
header('Content-Type: text/html; charset=utf-8');

$pdo = new PDO('mysql:host=localhost;dbname=chatroom;charset=utf8mb4', 'root', '');

if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['content'])) {
    $nick = isset($_COOKIE['chat_nick']) ? $_COOKIE['chat_nick'] : '匿名';
    $content = trim($_POST['content']);
    $content = mb_substr($content, 0, 500); // 限制長度
    
    $stmt = $pdo->prepare("INSERT INTO chat (nickname, content) VALUES (?, ?)");
    $stmt->execute([$nick, $content]);
    
    // 重導回 speak.php 避免重複提交 (PRG 模式)
    header('Location: speak.php');
    exit;
}
?>
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>發言</title>
<style>
    body { padding: 10px; margin: 0; background: #eee; }
    form { display: flex; gap: 5px; }
    input[type=text] { flex: 1; padding: 8px; border: 1px solid #ccc; border-radius: 4px; }
    button { padding: 8px 16px; background: #0066cc; color: #fff; border: none; border-radius: 4px; cursor: pointer; }
    button:hover { background: #0055aa; }
</style></head>
<body>
    <form method="post">
        <input type="text" name="content" placeholder="輸入訊息..." maxlength="500" required autofocus>
        <button type="submit">發送</button>
    </form>
</body></html>

四、關鍵技術點與優化

問題解決方案
iframe 輪詢造成閃爍/歷史紀錄污染使用 meta refresh 或 JavaScript setInterval + AJAX 局部更新 (單頁應用模式)。
訊息延遲 (3 秒輪詢間隔)採用 長輪詢 或 Server-Sent Events (SSE) 實現準即時推送。
資料表無限增大定期清理 (如保留最近 1000 筆)、分表、或啟用 MySQL 事件排程自動歸檔。
XSS 攻擊輸出時 htmlspecialchars(),儲存前 strip_tags() 或使用 HTMLPurifier。
灌水/頻率限制Session/IP 記錄最後發言時間,限制最小間隔 (如 2 秒)。

五、現代化重構方向

  1. 移除 Frameset:採用單頁面 (SPA) 架構,上方訊息區 + 下方輸入區,AJAX 輪詢或 SSE 更新。
  2. WebSocket 伺服器:使用 Ratchet (PHP)、Socket.IO (Node.js) 或 Swoole (PHP 協程) 建立持久連線,實現真正即時雙向通訊。
  3. 資料庫優化:引入 Redis 快取最新訊息、使用 MySQL 8.0 CTE 遞迴查詢、或改用 MongoDB 儲存非結構化聊天記錄。
  4. 功能擴充:私聊、群組、表情符號、圖片/檔案上傳、訊息已讀狀態、用戶在線狀態。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved