Commit 4eab09ff authored by 越 贾's avatar 越 贾
Browse files

ver2: 新增 room/message/getUpdate 接口,将消息轮询优化为只轮询最新消息;登录用户每次进入聊天室默认打开最近访问的房间

parent bac5d6b8
No preview for this file type
// src/app/api/room/message/getUpdate/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export async function GET(req: NextRequest) {
try {
const { searchParams } = new URL(req.url);
const roomId = searchParams.get('roomId');
const sinceMessageId = searchParams.get('sinceMessageId');
if (!roomId) {
return NextResponse.json({
code: 1,
message: '缺少 roomId 参数'
}, { status: 400 });
}
if (!sinceMessageId) {
return NextResponse.json({
code: 1,
message: '缺少 sinceMessageId 参数'
}, { status: 400 });
}
// 获取指定消息ID之后的所有新消息
const messages = await prisma.message.findMany({
where: {
roomId: Number(roomId),
id: {
gt: Number(sinceMessageId) // greater than - 大于指定的消息ID
}
},
orderBy: { createdAt: 'asc' },
});
const formattedMessages = messages.map(msg => ({
messageId: msg.id,
roomId: msg.roomId,
sender: msg.sender,
content: msg.content,
time: msg.createdAt.getTime(),
}));
return NextResponse.json({
code: 0,
data: { messages: formattedMessages }
});
} catch (error) {
console.error('获取增量消息失败:', error);
return NextResponse.json({
code: 1,
message: '获取消息失败'
}, { status: 500 });
}
}
\ No newline at end of file
...@@ -73,7 +73,6 @@ function MessageItem({ message, currentUser }: MessageItemProps) { ...@@ -73,7 +73,6 @@ function MessageItem({ message, currentUser }: MessageItemProps) {
); );
} }
// 单个房间组件
// 登录状态组件 // 登录状态组件
function AuthStatus({ isLoggedIn, username, onLogout }: { function AuthStatus({ isLoggedIn, username, onLogout }: {
isLoggedIn: boolean; isLoggedIn: boolean;
...@@ -124,13 +123,13 @@ function PermissionAlert({ message, onClose }: { message: string; onClose: () => ...@@ -124,13 +123,13 @@ function PermissionAlert({ message, onClose }: { message: string; onClose: () =>
); );
} }
// 单个房间组件 - 添加权限检查 // 单个房间组件
interface RoomEntryProps { interface RoomEntryProps {
room: RoomPreviewInfo; room: RoomPreviewInfo;
onClick: () => void; onClick: () => void;
isActive: boolean; isActive: boolean;
onDelete: () => void; onDelete: () => void;
canDelete: boolean; // 新增:是否可以删除 canDelete: boolean;
} }
function RoomEntry({ room, onClick, isActive, onDelete, canDelete }: RoomEntryProps) { function RoomEntry({ room, onClick, isActive, onDelete, canDelete }: RoomEntryProps) {
...@@ -178,9 +177,35 @@ export default function ChatPage() { ...@@ -178,9 +177,35 @@ export default function ChatPage() {
const [permissionAlertMessage, setPermissionAlertMessage] = useState(''); const [permissionAlertMessage, setPermissionAlertMessage] = useState('');
const router = useRouter(); const router = useRouter();
// 新增:消息相关状态
const [messages, setMessages] = useState<Message[]>([]);
const [lastMessageId, setLastMessageId] = useState<number>(0);
const [isFirstLoad, setIsFirstLoad] = useState(true);
// 使用身份验证 hook // 使用身份验证 hook
const { isLoggedIn, username: authUsername, isLoading: authLoading, logout } = useAuth(); const { isLoggedIn, username: authUsername, isLoading: authLoading, logout } = useAuth();
// v2新增:保存最后访问的房间ID到 localStorage
const saveLastRoom = (roomId: number | null) => {
if (roomId) {
localStorage.setItem('lastRoomId', roomId.toString());
} else {
localStorage.removeItem('lastRoomId');
}
};
// v2新增读取最后访问的房间ID
const getLastRoom = (): number | null => {
const lastRoom = localStorage.getItem('lastRoomId');
return lastRoom ? parseInt(lastRoom, 10) : null;
};
// v2新增更新 selectedRoomId 的设置函数,同时保存到 localStorage
const handleSelectRoom = (roomId: number | null) => {
setSelectedRoomId(roomId);
saveLastRoom(roomId);
};
useEffect(() => { useEffect(() => {
const saved = localStorage.getItem('nickname'); const saved = localStorage.getItem('nickname');
if (!saved) { if (!saved) {
...@@ -213,18 +238,80 @@ export default function ChatPage() { ...@@ -213,18 +238,80 @@ export default function ChatPage() {
{ refreshInterval: 1000 } { refreshInterval: 1000 }
); );
// 获取消息列表 // v2新增:房间列表加载完成后,自动选择上次访问的房间
useEffect(() => {
// 只在房间列表首次加载完成且没有选中房间时执行
if (roomData?.rooms && !selectedRoomId) {
const lastRoomId = getLastRoom();
if (lastRoomId) {
// 检查上次的房间是否仍然存在
const roomExists = roomData.rooms.some(r => r.roomId === lastRoomId);
if (roomExists) {
console.log(`自动进入上次访问的房间: ${lastRoomId}`);
setSelectedRoomId(lastRoomId);
} else {
// 如果上次的房间不存在了,清除记录
console.log('上次访问的房间已不存在');
localStorage.removeItem('lastRoomId');
}
}
}
}, [roomData, selectedRoomId]);
// v2改进:首次加载获取全部消息
const { const {
data: messageData, data: initialMessages,
error: messageError, error: initialError,
isLoading: messageIsLoading, isLoading: initialLoading
mutate: refreshMessages,
} = useSWR<RoomMessageListRes>( } = useSWR<RoomMessageListRes>(
selectedRoomId ? `/api/room/message/list?roomId=${selectedRoomId}` : null, selectedRoomId && isFirstLoad ? `/api/room/message/list?roomId=${selectedRoomId}` : null,
(key) => authFetch(key).then((r) => r.json().then((d: any) => d.data)),
{
revalidateOnFocus: false,
revalidateOnReconnect: false
}
);
// v2改进:增量获取新消息
const { data: newMessages } = useSWR<RoomMessageListRes>(
selectedRoomId && lastMessageId > 0
? `/api/room/message/getUpdate?roomId=${selectedRoomId}&sinceMessageId=${lastMessageId}`
: null,
(key) => authFetch(key).then((r) => r.json().then((d: any) => d.data)), (key) => authFetch(key).then((r) => r.json().then((d: any) => d.data)),
{ refreshInterval: 1000 } { refreshInterval: 1000 }
); );
// v2处理首次加载的消息
useEffect(() => {
if (initialMessages?.messages && isFirstLoad) {
setMessages(initialMessages.messages);
if (initialMessages.messages.length > 0) {
const lastMsg = initialMessages.messages[initialMessages.messages.length - 1];
setLastMessageId(lastMsg.messageId);
}
setIsFirstLoad(false);
}
}, [initialMessages, isFirstLoad]);
// v2处理增量更新的消息
useEffect(() => {
if (newMessages?.messages && newMessages.messages.length > 0) {
setMessages(prev => [...prev, ...newMessages.messages]);
const lastMsg = newMessages.messages[newMessages.messages.length - 1];
setLastMessageId(lastMsg.messageId);
}
}, [newMessages]);
// v2切换房间时重置状态
useEffect(() => {
setMessages([]);
setLastMessageId(0);
setIsFirstLoad(true);
}, [selectedRoomId]);
// 创建房间 // 创建房间
const { trigger: addRoomTrigger, isMutating: isAddingRoom } = useSWRMutation< const { trigger: addRoomTrigger, isMutating: isAddingRoom } = useSWRMutation<
RoomAddRes, RoomAddRes,
...@@ -240,15 +327,15 @@ export default function ChatPage() { ...@@ -240,15 +327,15 @@ export default function ChatPage() {
// 删除房间 // 删除房间
const { trigger: deleteRoomTrigger } = useSWRMutation( const { trigger: deleteRoomTrigger } = useSWRMutation(
'/api/room/delete', // 确保路径正确 '/api/room/delete',
async (key, { arg }: { arg: { roomId: number } }) => { async (key, { arg }: { arg: { roomId: number } }) => {
const response = await authFetch(key, { const response = await authFetch(key, {
method: 'POST', method: 'POST',
body: JSON.stringify({ roomId: arg.roomId }), // 参数格式修正 body: JSON.stringify({ roomId: arg.roomId }),
}); });
return response; return response;
} }
); );
// 发送消息 // 发送消息
const { trigger: sendMessageTrigger, isMutating: isSendingMessage } = const { trigger: sendMessageTrigger, isMutating: isSendingMessage } =
...@@ -279,7 +366,9 @@ export default function ChatPage() { ...@@ -279,7 +366,9 @@ export default function ChatPage() {
try { try {
const res = await addRoomTrigger({ user: nickname, roomName: newRoomName.trim() }); const res = await addRoomTrigger({ user: nickname, roomName: newRoomName.trim() });
refreshRooms(); refreshRooms();
if (res?.roomId) setSelectedRoomId(res.roomId); if (res?.roomId) {
handleSelectRoom(res.roomId); // 🆕 使用新的设置函数
}
setShowAddModal(false); setShowAddModal(false);
setNewRoomName(''); setNewRoomName('');
} catch (error) { } catch (error) {
...@@ -290,50 +379,46 @@ export default function ChatPage() { ...@@ -290,50 +379,46 @@ export default function ChatPage() {
// 处理删除房间 // 处理删除房间
const handleDeleteRoom = async (roomId: number) => { const handleDeleteRoom = async (roomId: number) => {
if (!isLoggedIn) { if (!isLoggedIn) {
alert('请先登录后再删除房间'); alert('请先登录后再删除房间');
return; return;
} }
const token = localStorage.getItem('token');
if (!token) {
alert('登录状态已过期,请重新登录');
logout();
return;
}
if (!confirm('确定删除该房间?此操作不可撤销!')) return; const token = localStorage.getItem('token');
if (!token) {
try { alert('登录状态已过期,请重新登录');
await deleteRoomTrigger({ roomId }); logout();
return;
// 删除成功后的处理
if (selectedRoomId === roomId) {
setSelectedRoomId(null);
} }
if (!confirm('确定删除该房间?此操作不可撤销!')) return;
// 立即刷新房间列表 try {
refreshRooms(); await deleteRoomTrigger({ roomId });
alert('房间删除成功'); if (selectedRoomId === roomId) {
} catch (error) { handleSelectRoom(null); // 🆕 使用新的设置函数
console.error('删除房间失败:', error); }
if (error instanceof Error) { refreshRooms();
if (error.message.includes('认证') || error.message.includes('token')) { alert('房间删除成功');
alert('认证失败,请重新登录'); } catch (error) {
logout(); console.error('删除房间失败:', error);
if (error instanceof Error) {
if (error.message.includes('认证') || error.message.includes('token')) {
alert('认证失败,请重新登录');
logout();
} else {
alert(`删除房间失败: ${error.message}`);
}
} else { } else {
alert(`删除房间失败: ${error.message}`); alert('删除房间失败,请稍后重试');
} }
} else {
alert('删除房间失败,请稍后重试');
} }
} };
};
// 处理发送消息 //处理发送消息
const handleSendMessage = async () => { const handleSendMessage = async () => {
if (!messageInput.trim() || !selectedRoomId) return; if (!messageInput.trim() || !selectedRoomId) return;
try { try {
...@@ -343,7 +428,8 @@ export default function ChatPage() { ...@@ -343,7 +428,8 @@ export default function ChatPage() {
sender: nickname, sender: nickname,
}); });
setMessageInput(''); setMessageInput('');
refreshMessages(); // 发送成功后,增量更新会自动获取新消息
// 不需要手动刷新
} catch { } catch {
alert('发送消息失败'); alert('发送消息失败');
} }
...@@ -360,6 +446,10 @@ export default function ChatPage() { ...@@ -360,6 +446,10 @@ export default function ChatPage() {
return <div className={styles.loading}>正在检查登录状态...</div>; return <div className={styles.loading}>正在检查登录状态...</div>;
} }
//计算加载和错误状态
const messageIsLoading = initialLoading && isFirstLoad;
const messageError = initialError;
return ( return (
<main className={styles.chatContainer}> <main className={styles.chatContainer}>
{/* 身份验证状态显示 */} {/* 身份验证状态显示 */}
...@@ -369,7 +459,7 @@ export default function ChatPage() { ...@@ -369,7 +459,7 @@ export default function ChatPage() {
onLogout={logout} onLogout={logout}
/> />
{/* 左侧房间列表 */} {/* 左侧房间列表 - 修改点击事件 */}
<div className={styles.sidebar}> <div className={styles.sidebar}>
<div className={styles.sidebarHeader}> <div className={styles.sidebarHeader}>
<h2>聊天室</h2> <h2>聊天室</h2>
...@@ -389,10 +479,10 @@ export default function ChatPage() { ...@@ -389,10 +479,10 @@ export default function ChatPage() {
<RoomEntry <RoomEntry
key={r.roomId} key={r.roomId}
room={r} room={r}
onClick={() => setSelectedRoomId(r.roomId)} onClick={() => handleSelectRoom(r.roomId)} // 🆕 使用新的设置函数
isActive={selectedRoomId === r.roomId} isActive={selectedRoomId === r.roomId}
onDelete={() => handleDeleteRoom(r.roomId)} onDelete={() => handleDeleteRoom(r.roomId)}
canDelete={isLoggedIn} // 只有登录用户可以删除 canDelete={isLoggedIn}
/> />
))} ))}
</div> </div>
...@@ -411,7 +501,7 @@ export default function ChatPage() { ...@@ -411,7 +501,7 @@ export default function ChatPage() {
<div className={styles.messagesContainer}> <div className={styles.messagesContainer}>
{messageIsLoading && <div className={styles.loading}>加载消息中...</div>} {messageIsLoading && <div className={styles.loading}>加载消息中...</div>}
{messageError && <div className={styles.error}>加载消息失败</div>} {messageError && <div className={styles.error}>加载消息失败</div>}
{messageData?.messages?.map((m) => ( {messages.map((m) => (
<MessageItem key={m.messageId} message={m} currentUser={nickname} /> <MessageItem key={m.messageId} message={m} currentUser={nickname} />
))} ))}
</div> </div>
...@@ -476,4 +566,4 @@ export default function ChatPage() { ...@@ -476,4 +566,4 @@ export default function ChatPage() {
)} )}
</main> </main>
); );
} }
\ No newline at end of file
div{ /* chatroom/src/app/greet/greet.css */
.div{
color: #c3aae2; color: #c3aae2;
text-align: center; text-align: center;
padding: 20px; padding: 20px;
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment