本文為系列第四部分,對應 room.php3 —— 聊天室左上角「房間面板」的完整邏輯與界面。功能覆蓋:房間列表展示、加入房間、創建房間入口、刪除房間(拆房)、自動刷新機制、權限控制。代碼延續「PHP 混編 HTML、Meta Refresh 輪詢、JavaScript 輔助表單提交」的經典模式。
$bPost=="1"):
MasterID);RoomID=1。selected 高亮。<meta http-equiv="refresh" content="RefRate*8;url=room.php3?...">。@odbc_exec($ConnID, "DELETE * FROM Room WHERE (RoomID Not IN (SELECT DISTINCT RoomID FROM User)) AND RoomID<>1");
子查詢找出所有被佔用的 RoomID,反選刪除其餘(保留 RoomID=1 大廳)。Access SQL 支持 DELETE * FROM 語法。
if ($bPost=="1") {
$sDelRoom = substr($sDelRoom, 2); // 去掉前綴 "rm"
if ($sDelRoom=="1") $bCanDel=false; // 大廳不可刪
else $bCanDel=true;
if ($id!="1" && $ps=="superldz") { // 非管理員
$result = @odbc_exec($ConnID, "SELECT MasterID FROM Room WHERE RoomID=$sDelRoom");
if (@odbc_fetch_into($result,0,$rArr)) {
if ($rArr[0] != $id) $bCanDel = false; // 非房主不可刪
} else $bCanDel = false;
}
if ($bCanDel) {
@odbc_exec($ConnID, "UPDATE User SET LstTime=".time()." WHERE RoomID=$sDelRoom AND UserID=$id");
@odbc_exec($ConnID, "UPDATE User SET RoomID=1 WHERE RoomID=$sDelRoom");
@odbc_exec($ConnID, "DELETE * FROM Room WHERE RoomID=$sDelRoom");
$sRoomID = "1";
}
}
注意:$ps=="superldz" 此處疑為筆誤,應為 !=;原代碼邏輯可能導致非管理員也被視為管理員。
echo "<select name='selRoom' size='7' style='WIDTH: 160px'>\n";
$result = @odbc_exec($ConnID, "SELECT RoomID, RoomName FROM Room ORDER BY RoomID");
while (@odbc_fetch_into($result,0,$rArr)) {
if ((int)$sRoomID == $rArr[0])
echo "\t\t<option value='rm{$rArr[0]}' selected>{$rArr[1]}</option>\n";
else
echo "\t\t<option value='rm{$rArr[0]}'>{$rArr[1]}</option>\n";
}
echo "\t\t</select>\n";
值格式 rm{RoomID},前端 JS CheckValid() / AlertIt() 解析提交。
<script language="JavaScript">
function CheckValid() {
document.frmInRoom.selRoom.value = document.frmRoom.selRoom.value;
return true;
}
function AlertIt() {
document.frmDelRoom.sDelRoom.value = document.frmRoom.selRoom.value;
return confirm("你真的要拆了這個房子啊!");
}
</script>
三個表單分別對應:frmRoom(列表選擇)、frmInRoom(加入)、frmDelRoom(拆房),通過隱藏字段傳遞動作類型與參數。
| 問題 | 代碼位置 | 風險等級 |
|---|---|---|
| SQL 注入 | 所有 $id、$sDelRoom 直接拼接 | 🔴 嚴重 |
| 權限判斷邏輯錯誤 | if ($id!="1" && $ps=="superldz") 應為 != | 🟠 高 |
| CSRF 缺失 | 所有狀態改變操作無 Token 驗證 | 🟠 高 |
| XSS 風險 | RoomName 直接輸出到 option,未轉義 | 🟡 中 |
| Meta Refresh 體驗差 | 整頁刷新導致閃爍、滾動位置丟失 | 🟡 中 |
| Access 並發瓶頸 | 文件鎖、無行級鎖、易損壞 | 🔴 嚴重 |
| 端點 | 方法 | 功能 | 權限 |
|---|---|---|---|
| /api/rooms | GET | 獲取房間列表(含在線人數) | 登錄用戶 |
| /api/rooms | POST | 創建房間 | 普通用戶 |
| /api/rooms/{id} | DELETE | 刪除房間(遷移成員至大廳) | 房主/管理員 |
| /api/rooms/{id}/join | POST | 加入房間(驗證密碼) | 登錄用戶 |
<script setup>
import { ref, onMounted, watch } from 'vue'
import { useRoomStore } from '@/stores/room'
import { useUserStore } from '@/stores/user'
const roomStore = useRoomStore()
const userStore = useUserStore()
const rooms = computed(() => roomStore.list)
const currentRoomId = computed(() => userStore.user.room_id)
const selectedRoom = ref(null)
const showDeleteConfirm = ref(false)
const deletingRoomId = ref(null)
onMounted(() => {
roomStore.fetchList()
// WebSocket 訂閱房間變更
ws.on('room:update', roomStore.fetchList)
ws.on('room:delete', id => roomStore.remove(id))
})
function joinRoom(room) {
if (room.id === currentRoomId.value) return
if (room.pass_hash && !await promptPassword()) return
api.post(`/rooms/${room.id}/join`).then(() => {
userStore.setRoom(room.id)
router.push('/chat')
})
}
function confirmDelete(room) {
deletingRoomId.value = room.id
showDeleteConfirm.value = true
}
async function executeDelete() {
await api.delete(`/rooms/${deletingRoomId.value}`)
showDeleteConfirm.value = false
if (currentRoomId.value === deletingRoomId.value) {
userStore.setRoom(1) // 大廳
}
}
</script>
<template>
<div class="room-panel">
<h3>房間列表</h3>
<select v-model="selectedRoom" size="10" class="room-list">
<option v-for="r in rooms" :key="r.id" :value="r" :selected="r.id===currentRoomId">
{{ r.name }} ({{ r.online_count }})
</option>
</select>
<div class="actions">
<button @click="joinRoom(selectedRoom)" :disabled="!selectedRoom || selectedRoom.id===currentRoomId">加入</button>
<button @click="router.push('/rooms/create')">新建</button>
<button @click="confirmDelete(selectedRoom)" :disabled="!selectedRoom || selectedRoom.id===1" class="danger">拆房</button>
</div>
<ConfirmDialog v-model:show="showDeleteConfirm" title="確認拆房" message="確定要刪除該房間嗎?成員將被移至大廳。" @confirm="executeDelete" />
</div>
</template>
public function destroy(Room $room) {
// 權限:房主或管理員
$this->authorize('delete', $room);
return DB::transaction(function() use ($room) {
// 1. 將房間內用戶遷移至大廳 (id=1)
User::where('room_id', $room->id)->update([
'room_id' => 1,
'to_id' => 0,
'is_secret' => false,
]);
// 2. 記錄系統消息
Message::createSystem(1, "房間「{$room->name}」已被拆除,成員遷移至大廳");
// 3. 刪除房間
$room->delete();
// 4. 廣播事件
event(new RoomDeleted($room->id));
});
}
room.php3 以 200 行代碼實現了房間全生命周期管理,展示了早期 PHP 開發「單文件包辦數據邏輯、模板渲染、前端交互」的高密度特點。其核心業務模型(房間、成員、權限、狀態遷移)清晰可復用。現代化重構的關鍵在於:拆分關注點(API/Service/Frontend)、引入事務與行級鎖保證一致性、用 WebSocket 替換 Meta Refresh 實現實時同步、統一權限策略。本文提供的重構藍圖可直接指導遷移項目落地。