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

PHP 與 Access 打造聊天室系列(三):輸入面板核心邏輯——消息處理、表情、悄悄話與指令系統

     編輯:PHP 綜合知識

模塊定位:cinput.php3

本文為系列第三部分,對應 cinput.php3 —— 聊天室底部「輸入面板」的完整邏輯。該文件承擔:消息發送入口、內容過濾與轉義、表情/顏色/悄悄話附加屬性、指令解析(/t 想、/: 動作、/w 找人、// 系統動作)、管理員廣播、刷新頻率設置、退出入口。代碼採用「大量輔助函數 + 主邏輯分支」風格,典型的早期 PHP 過程式編程。

輔助函數庫解析

函數功能關鍵點
suiji($max)生成 0~$max-1 隨機整數用 srand((double)microtime()*1e6) 播種,rand()/getrandmax() 歸一化
StrOccurs($sStr, $sFind)統計子串出現次數邏輯錯誤:if (strstr($sTemp, $sFind)) break; 應為 !strstr,導致永遠返回 0
StrDupl($sStr, $iCnt)重複字符串簡單循環拼接
DelQuot($sStr)轉義特殊字符將 | ' " 替換為 HTML 實體 ¦ ´ "
DelTag($sStr)過濾危險 HTML 標籤黑名單模式:檢測 <TABLE> <SCRIPT> <BODY> 等 30+ 標籤,若無則 <→< >→>;繞過風險極高
AddLost($sStr)補全未閉合標籤統計開閉標籤差值,補全 >、</FONT>、</B> 等 20+ 標籤;僅處理已知標籤,無法應對任意嵌套

主業務邏輯流程

  1. 管理員模式($id=="1" && $ps=="superldz"):
    • 讀取/更新 RefRate(刷新間隔)。
    • 發送系統廣播:cmdSend=="送出" 時,將 txtSend 經 DelQuot(htmlspecialchars()) 處理後,以灰色系統消息格式寫入 ChtCont(RoomID=0 全域可見)。
  2. 普通用戶模式:
    • 驗證密碼與心跳(LstTime >= time()-1800)。
    • 讀取用戶屬性:Secret(悄悄話默認)、RefRate、ToID/ToName(私聊目標)、RoomID。
    • 發送消息分支(cmdSend=="送出"):
      1. 基礎處理:$sSend = DelQuot(htmlspecialchars($txtSend))(註釋掉的 AddLost(DelTag()) 更嚴格但有 Bug)。
      2. 悄悄話標記:chkSecret=="Yes" → $sSecret="True"、$sTalk="悄悄說:"。
      3. 更新用戶狀態:EmotionID、ColorID、Secret、RefRate、LstTime。
      4. 指令解析(優先於普通消息):
        • /t ... → 藍色斜體「%m想...」,$bCht=False、$sSecret=False。
        • /: ... → 紅色「%m...」,$bCht=False。
        • /w 名字 → 查詢用戶所在房間,返回系統消息「XXX目前在房間N」,$bToMe=True(僅自己可見)、$sSecret=True。
        • // 動作ID → 查詢 Action 表獲取動作內容,紅色顯示,$bCht=False。
      5. 普通消息構建:
        • 若有表情(Emotion 表),隨機選一條模板「%m對%g{說/悄悄說}內容」。
        • 悄悄話綠色、普通消息用用戶選定顏色 $sColor。
      6. 寫入 ChtCont:私聊($bToMe)寫 sIDFrom=0, sIDTo=$id;公開寫 sIDFrom=$id, sIDTo=$sToID。
  3. 輸出表單:隱藏字段傳遞 sEmotion、sColor、id、ps;勾選框「悄悄話」;輸入框 txtSend;提交按鈕;刷新間隔輸入框;退出按鈕(跳轉 leave.php3)。

關鍵代碼片段:指令系統

if (substr($sSend,0,3)=="/t ") {
  $sChtCont = "<font color=blue><i>%m想" . substr($sSend,3) . "</i></font>";
  $sSecret = "False"; $bCht = "False";
} elseif (substr($sSend,0,3)=="/: ") {
  $sChtCont = "<font color=red>%m" . substr($sSend,3) . "</font>";
  $sSecret = "False"; $bCht = "False";
} elseif (strtolower(substr($sSend,0,3))=="/w ") {
  $targetName = trim(substr($sSend,3));
  $result = @odbc_exec($ConnID, "SELECT RoomID FROM User WHERE UserName='$targetName'");
  if (@odbc_fetch_into($result,0,$rArr)) {
    if ($rArr[0] > 0) {
      $result = @odbc_exec($ConnID, "SELECT RoomName FROM Room WHERE RoomID=".$rArr[0]);
      if (@odbc_fetch_into($result,0,$rArr))
        $sChtCont = "<font color=gray>【系統消息】$targetName目前在房間".$rArr[0]."。</font>";
      else
        $sChtCont = "<font color=gray>【系統消息】系統混亂,$targetName房間不可識別!</font>";
    } else
      $sChtCont = "<font color=gray>【系統消息】$targetName目前沒有上線。</font>";
  } else
    $sChtCont = "<font color=gray>【系統消息】沒有$targetName這個人。</font>";
  $sSecret = "True"; $bCht = "False"; $bToMe = "True";
} elseif (substr($sSend,0,3)=="// ") {
  $act = trim(substr($sSend,3));
  $result = @odbc_exec($ConnID, "SELECT ActCont FROM Action WHERE ActID='$act'");
  if (@odbc_fetch_into($result,0,$rArr)) {
    $sChtCont = "<font color=red>".$rArr[0]."</font>";
    $sSecret = "False"; $bCht = "False";
  } else $bCht = "True";
}

安全與架構隱患深度剖析

  1. SQL 注入遍地:$id、$txtSend、$targetName、$act 直接拼接,DelQuot 僅替換三個字符,無法防禦注入。
  2. XSS 風險:htmlspecialchars 僅用於管理員廣播與基礎消息,指令返回的系統消息直接拼接用戶名 $targetName 未轉義;表情模板 %m %g 後期替換時若未轉義同樣危險。
  3. 標籤過濾失效:DelTag 黑名單機制極易繞過(大小寫、編碼、嵌套、事件屬性);AddLost 補全邏輯錯誤(StrOccurs 歸零)且無法處理任意嵌套。現代應採用 HTMLPurifier 或 只允許純文本+標記語言。
  4. 隨機數質量低:suiji() 依賴 rand()(非加密安全),表情隨機選取可預測。
  5. 狀態更新競態:讀取用戶屬性 → 計算 → 更新,中間無鎖,高併發下 RefRate、Secret 等字段易覆蓋丟失。
  6. 編碼混亂:頁面 gb2312,數據庫存儲可能混雜,htmlspecialchars 默認不處理多字節,需指定 ENT_QUOTES, 'GB2312'。

現代化重構設計

領域模型

// 值對象:消息內容
class MessageContent {
    public string $raw;           // 用戶原始輸入
    public string $sanitized;     // 純文本(已轉義)
    public array $mentions = [];  // @用戶
    public ?string $command = null; // /t /: /w //
    public ?string $commandArg = null;
    
    public static function fromRaw(string $raw): self {
        $obj = new self();
        $obj->raw = $raw;
        $obj->sanitized = htmlspecialchars($raw, ENT_QUOTES, 'UTF-8');
        // 指令解析
        if (preg_match('/^\/(t|:|w|\/)\s*(.+)$/', $raw, $m)) {
            $obj->command = $m[1];
            $obj->commandArg = trim($m[2]);
        }
        return $obj;
    }
}

// 實體:聊天消息
class ChatMessage {
    public int $fromId;
    public int $toId;        // 0=公開
    public int $roomId;
    public MessageContent $content;
    public bool $isSecret;
    public string $color;    // #RRGGBB
    public ?int $emotionId = null;
    public bool $isSystem = false;
    public string $renderedHtml; // 最终渲染 HTML(服務端生成,前端直接 innerHTML)
}

服務層:消息處理管道

class MessageProcessor {
    public function process(User $sender, MessageContent $content, array $options): ChatMessage {
        $msg = new ChatMessage();
        $msg->fromId = $sender->id;
        $msg->roomId = $sender->room_id;
        $msg->toId = $options['to_id'] ?? 0;
        $msg->isSecret = $options['secret'] ?? false;
        $msg->color = $options['color'] ?? '#000000';
        $msg->emotionId = $options['emotion_id'] ?? null;
        
        // 1. 指令處理器鏈
        $handler = new CommandHandlerChain([
            new ThoughtCommandHandler(),      // /t
            new ActionCommandHandler(),       // /:
            new WhereCommandHandler(),        // /w
            new SystemActionCommandHandler(), // //
        ]);
        $cmdResult = $handler->handle($content, $sender);
        if ($cmdResult) {
            $msg->isSystem = true;
            $msg->renderedHtml = $cmdResult->html;
            $msg->toId = $cmdResult->targetId ?? 0;
            $msg->isSecret = $cmdResult->isSecret ?? false;
            return $msg;
        }
        
        // 2. 普通消息:表情模板
        $template = $this->getEmotionTemplate($msg->emotionId) 
            ?? "%m對%g{$msg->isSecret ? '悄悄說' : '說'}:%c";
        $msg->renderedHtml = $this->renderTemplate($template, $sender, $msg);
        
        return $msg;
    }
    
    private function renderTemplate(string $tpl, User $sender, ChatMessage $msg): string {
        $color = $msg->isSecret ? '#008000' : $msg->color;
        $fromName = htmlspecialchars($sender->username, ENT_QUOTES, 'UTF-8');
        $toName = $msg->toId ? htmlspecialchars(User::find($msg->toId)?->username ?? '未知', ENT_QUOTES, 'UTF-8') : '大家';
        $content = htmlspecialchars($msg->content->sanitized, ENT_QUOTES, 'UTF-8');
        
        return str_replace(
            ['%m', '%g', '%c'],
            ["{$fromName}", "{$toName}", "{$content}"],
            $tpl
        );
    }
}

指令處理器示例

interface CommandHandler {
    public function handle(MessageContent $content, User $sender): ?CommandResult;
}

class WhereCommandHandler implements CommandHandler {
    public function handle(MessageContent $content, User $sender): ?CommandResult {
        if ($content->command !== 'w') return null;
        $target = User::where('username', $content->commandArg)->first();
        if (!$target) {
            return new CommandResult(
                html: "【系統消息】找不到用戶 {$content->commandArg}",
                targetId: $sender->id, // 僅發送者可見
                isSecret: true
            );
        }
        $roomName = Room::find($target->room_id)?->name ?? '未知房間';
        return new CommandResult(
            html: "【系統消息】{$target->username} 目前在房間 {$roomName}",
            targetId: $sender->id,
            isSecret: true
        );
    }
}

前端交互(Vue 3 組件)

<script setup>
import { ref, computed } from 'vue'
import { useChatStore } from '@/stores/chat'
import { useUserStore } from '@/stores/user'

const chatStore = useChatStore()
const userStore = useUserStore()
const message = ref('')
const secret = ref(false)
const emotionId = ref(null)
const color = ref('#000000')
const refreshRate = ref(5)

const send = () => {
    if (!message.value.trim()) return
    chatStore.sendMessage({
        content: message.value,
        secret: secret.value,
        emotion_id: emotionId.value,
        color: color.value,
        to_id: userStore.user.to_id,
    })
    message.value = ''
}
</script>

<template>
  <div class="input-panel">
    <label><input type="checkbox" v-model="secret"> 悄悄話</label>
    <input v-model="message" maxlength="120" placeholder="輸入消息..." @keyup.enter="send">
    <select v-model="emotionId"><option value="">無表情</option><option v-for="e in chatStore.emotions" :key="e.id" :value="e.id">{{e.name}}</option></select>
    <input type="color" v-model="color">
    <input type="number" v-model.number="refreshRate" min="2" max="60" style="width:60px"> 秒刷新
    <button @click="send">送出</button>
    <button @click="chatStore.leave" class="danger">退出</button>
  </div>
</template>

總結

cinput.php3 在 300 行內實現了極其豐富的聊天室輸入功能:多級權限、指令系統、表情模板、悄悄話、顏色、私聊、刷新控制。其業務模型(消息類型、指令、模板、權限)設計合理,可直接映射到現代領域模型。重構關鍵在於:引入領域驅動設計分離關注點、用管道模式處理指令、模板引擎替換字符串拼接、預處理語句消除 SQL 注入、HTMLPurifier/純文本+標記語言消除 XSS、WebSocket 替換輪詢。本文提供的重構藍圖可指導完整遷移。

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