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

PHP + AJAX 即時聊天室完整實作:檔案儲存、線上用戶、表情符號、私有訊息

     編輯:PHP 相關知識

PHP + AJAX 即時聊天室完整實作指南

傳統輪詢雖非 WebSocket 等級的即時性,但無需額外服務、部署簡單、相容性極佳,適合中小型聊天室、客服系統、內部通訊工具。本文實作一個功能完整的單檔案聊天室,支援:多房間、按日分檔儲存、線上用戶偵測、表情符號、字體樣式、私有訊息、敏感詞過濾、XSS 防護。

架構設計

  • 後端:單一 chat.php 處理讀/寫/線上狀態,資料存儲於 rooms/YYYY-MM-DD.dat.php(含 PHP 死結構防直接存取)。
  • 前端:原生 JavaScript + setInterval 輪詢(預設 2 秒),jQuery 可選。
  • 儲存格式:每行 時間戳|發送者:內容(含HTML),線上用戶檔 rooms/YYYY-MM-DD.php 記錄 時間戳|最後活動|用戶名|IP。

完整後端核心代碼

<?php
// chat.php - 完整聊天室後端
header('Content-Type: application/json; charset=UTF-8');

// ===== 配置區 =====
const ROOM_DIR = __DIR__ . '/rooms/';
const MAX_DISPLAY = 200;        // 客戶端最大顯示條數
const LEAST_NUM = 30;           // 初次載入歷史條數
const ONLINE_TIMEOUT = 20;      // 線上判定秒數
const CLEANUP_INTERVAL = 1800;  // 檔案清理間隔(秒)
const CHARSET = 'UTF-8';

// 敏感詞過濾
const BAN_WORDS = ['法輪功', '共產黨', '李洪志', 'fuck', '叼', '你媽的', '他媽的'];

// 表情映射
const EMOTICONS = [
    '[:ani_wink:]' => '😉', '[:big_eyes:]' => '😳', '[:cool:]' => '😎',
    '[:cry:]' => '😭', '[:eye_roll:]' => '🙄', '[:grin:]' => '😁',
    '[:happy:]' => '😊', '[:not_impressed:]' => '😒', '[:smile:]' => '🙂',
    '[:smile_eyes:]' => '😍', '[:stickout:]' => '😛', '[:straight:]' => '😐',
    '[:surprised:]' => '😲', '[:unhappy:]' => '😞', '[:wink:]' => '😉',
];

// ===== 工具函數 =====
function getRoomFile(string $room): string {
    $room = preg_replace('/[^a-zA-Z0-9_\-]/', '', $room);
    return ROOM_DIR . $room . '.dat.php';
}

function getOnlineFile(string $room): string {
    $room = preg_replace('/[^a-zA-Z0-9_\-]/', '', $room);
    return ROOM_DIR . $room . '.php';
}

function getClientIp(): string {
    $headers = ['HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP', 'REMOTE_ADDR'];
    foreach ($headers as $h) {
        if (!empty($_SERVER[$h])) {
            $ips = explode(',', $_SERVER[$h]);
            return trim($ips[0]);
        }
    }
    return 'unknown';
}

function sanitize(string $str): string {
    return htmlspecialchars($str, ENT_QUOTES | ENT_HTML5, 'UTF-8');
}

function ensureRoomDir(): void {
    if (!is_dir(ROOM_DIR)) {
        mkdir(ROOM_DIR, 0755, true);
    }
}

function initRoomFiles(string $room): void {
    $dataFile = getRoomFile($room);
    $onlineFile = getOnlineFile($room);
    
    if (!file_exists($dataFile)) {
        file_put_contents($dataFile, "\n" . time() . "|歡迎來到聊天室!\n");
    }
    if (!file_exists($onlineFile)) {
        file_put_contents($onlineFile, "\n");
    }
}

// ===== 請求路由 =====
$action = $_GET['action'] ?? '';
$room = $_GET['room'] ?? date('Y-m-d');
$room = preg_replace('/[^a-zA-Z0-9_\-]/', '', $room);

ensureRoomDir();
initRoomFiles($room);

$dataFile = getRoomFile($room);
$onlineFile = getOnlineFile($room);

switch ($action) {
    case 'write':
        handleWrite($dataFile, $onlineFile);
        break;
    case 'read':
        handleRead($dataFile, $onlineFile);
        break;
    default:
        // 首頁輸出 HTML
        outputHtml($room);
        break;
}

// ===== 寫入處理 =====
function handleWrite(string $dataFile, string $onlineFile): void {
    $input = file('php://input');
    if (count($input) < 2) {
        jsonOut(['ok' => false, 'msg' => '無內容']);
    }

    $name = sanitize(trim($input[0]));
    $ip = getClientIp();

    if (!$name) {
        jsonOut(['ok' => false, 'msg' => '請輸入暱稱']);
    }

    // 線上用戶註冊
    $onlineData = file_get_contents($onlineFile);
    $marker = "|{$name}|{$ip}|";
    if (strpos($onlineData, $marker) === false) {
        if (strpos($onlineData, "|{$name}|") !== false) {
            jsonOut(['ok' => false, 'msg' => '暱稱已被使用']);
        }
        $line = time() . "|" . time() . $marker . "\n";
        file_put_contents($onlineFile, $line, FILE_APPEND | LOCK_EX);
    }

    // 處理消息內容
    $style = buildStyleFromInput();
    $messages = [];
    for ($i = 1; $i < count($input); $i++) {
        $content = sanitize(trim($input[$i]));
        if ($content === '') continue;
        
        // 圖片標籤處理
        $content = preg_replace('!!i', '[img $1/]', $content);
        $content = str_replace(['<', '>'], ['<', '>'], $content);
        $content = preg_replace('!\[img (.*?)/\]!i', '', $content);
        
        // 敏感詞過濾
        $content = str_ireplace(BAN_WORDS, '***', $content);
        
        // 表情符號替換
        $content = str_replace(array_keys(EMOTICONS), array_values(EMOTICONS), $content);
        
        if ($style) {
            $content = "{$content}";
        }
        
        $messages[] = time() . "|{$name}:{$content}";
    }

    if (empty($messages)) {
        jsonOut(['ok' => false, 'msg' => '無有效內容']);
    }

    $content = implode("\n", $messages) . "\n";
    file_put_contents($dataFile, $content, FILE_APPEND | LOCK_EX);
    jsonOut(['ok' => true]);
}

function buildStyleFromInput(): string {
    $style = '';
    $font = $_GET['font'] ?? '';
    $fontMap = ['songti' => 'SimSun', 'heiti' => 'SimHei', 'kaiti' => 'KaiTi'];
    if (isset($fontMap[$font])) {
        $style .= "font-family:{$fontMap[$font]};";
    }
    if (!empty($_GET['bold'])) $style .= 'font-weight:bold;';
    $color = $_GET['color'] ?? '';
    if (preg_match('/^[0-9a-fA-F]{6}$/', $color)) $style .= "color:#{$color};";
    $size = (int)($_GET['size'] ?? 0);
    if ($size >= 10 && $size <= 48) $style .= "font-size:{$size}px;";
    return $style;
}

// ===== 讀取處理 =====
function handleRead(string $dataFile, string $onlineFile): void {
    $first = isset($_GET['first']);
    $lastMod = (int)($_GET['lastmod'] ?? 0);
    $currentMod = @filemtime($dataFile);
    $name = sanitize(trim(file_get_contents('php://input')));
    $ip = getClientIp();

    $response = [
        'lastmod' => $currentMod,
        'lines' => [],
        'onlines' => [],
    ];

    // 新消息
    if ($currentMod > $lastMod && !$first) {
        $lines = file($dataFile);
        foreach ($lines as $line) {
            $line = trim($line);
            if (!$line || strpos($line, '|') === false) continue;
            [$ts, $msg] = explode('|', $line, 2);
            if ((int)$ts > $lastMod) {
                $response['lines'][] = [
                    'time' => date('H:i:s', (int)$ts),
                    'word' => stripslashes($msg),
                ];
            }
        }
    }
    // 首次載入歷史
    elseif ($first) {
        $lines = file($dataFile);
        $total = count($lines);
        $start = max(0, $total - LEAST_NUM);
        $history = [];
        for ($i = $start; $i < $total; $i++) {
            $line = trim($lines[$i]);
            if (!$line || strpos($line, '|') === false) continue;
            [$ts, $msg] = explode('|', $line, 2);
            $history[] = [
                'time' => (date('m-d') == date('m-d', (int)$ts)) ? date('H:i:s', (int)$ts) : date('m-d H:i', (int)$ts),
                'word' => stripslashes($msg),
            ];
        }
        $response['lines'] = array_reverse($history);
    }

    // 更新線上用戶
    $now = time();
    $onlineLines = file($onlineFile);
    $newOnlineContent = '';
    $onlines = [];
    foreach ($onlineLines as $line) {
        $line = trim($line);
        if (!$line || strpos($line, '|') === false) continue;
        [$ts, $lastActive, $uname, $uip] = explode('|', $line);
        if ($now - (int)$lastActive < ONLINE_TIMEOUT * 2) {
            if ($uname === $name && $uip === $ip) {
                $newOnlineContent .= "{$ts}|{$now}|{$uname}|{$uip}|\n";
            } else {
                $newOnlineContent .= $line . "\n";
            }
            $onlines[] = $uname;
        }
    }
    file_put_contents($onlineFile, $newOnlineContent, LOCK_EX);
    $response['onlines'] = $onlines;

    jsonOut($response);
}

function jsonOut(array $data): void {
    echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    exit;
}

// ===== HTML 前端 =====
function outputHtml(string $room): void {
    $roomJs = json_encode($room, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_HEX_AMP);
    ?>



    
    PHP AJAX 聊天室
    


💬 PHP AJAX 聊天室

😀

線上用戶 0

    部署與運行

    1. 建立目錄結構:chat.php + rooms/(可寫權限)
    2. 瀏覽器開啟 chat.php 即可使用
    3. 多用戶測試:不同瀏覽器/無痕模式輸入不同暱稱

    擴展方向

    功能實作方向
    WebSocket 升級引入 Workerman/Swoole,替換輪詢為長連線
    私聊/群組房間機制擴展,增加 @用戶 解析與推送
    訊息持久化改用 SQLite/MySQL,支援搜尋、匯出、審計
    檔案/圖片上傳前端 FormData 上傳,後端驗證類型/大小,生成縮圖
    管理員功能踢人、禁言、公告、日誌查看

    結語

    本文實作的聊天室雖基於簡單的檔案儲存與輪詢,但涵蓋了即時通訊的核心要素:狀態同步、併發控制(LOCK_EX)、XSS 防護、敏感詞過濾、表情符號、樣式自定義、線上偵測。代碼結構清晰,可作為學習 PHP 即時通訊基礎、快速原型或內部工具的起點。生產環境建議遷移至資料庫儲存並引入 WebSocket 以獲得更佳體驗。

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