Sfoglia il codice sorgente

✨ feat(classroom): 实现聊天消息持久化功能

- 导入chatMessageClient用于消息持久化
- 添加saveMessageToDatabase方法保存消息到数据库
- 添加saveImMessageToDatabase方法直接从IM消息创建记录
- 添加saveMessagesToDatabase方法批量保存消息
- 在发送文本、图片和系统消息时添加数据库保存逻辑
- 实现课堂开始/结束、静音切换、举手、提问等系统消息的持久化
- 加载历史聊天消息并显示在消息列表中
- 修复stopAudioCapture调用缺少分号的语法问题
yourname 6 mesi fa
parent
commit
77f370b071
1 ha cambiato i file con 123 aggiunte e 5 eliminazioni
  1. 123 5
      src/client/mobile/components/Classroom/useClassroom.ts

+ 123 - 5
src/client/mobile/components/Classroom/useClassroom.ts

@@ -6,7 +6,7 @@ import { useParams, useSearchParams } from 'react-router';
 import AliRtcEngine, { AliRtcSubscribeState, AliRtcVideoTrack } from 'aliyun-rtc-sdk';
 import AliRtcEngine, { AliRtcSubscribeState, AliRtcVideoTrack } from 'aliyun-rtc-sdk';
 import { toast } from 'sonner';
 import { toast } from 'sonner';
 import { User } from '../../hooks/AuthProvider';
 import { User } from '../../hooks/AuthProvider';
-import { aliyunClient, fileClient } from '@/client/api';
+import { aliyunClient, fileClient, chatMessageClient } from '@/client/api';
 import { UserType } from '@/server/modules/users/user.enum';
 import { UserType } from '@/server/modules/users/user.enum';
 export enum Role {
 export enum Role {
   Teacher = UserType.TEACHER,
   Teacher = UserType.TEACHER,
@@ -127,7 +127,11 @@ export const useClassroom = ({ user }:{ user : User }) => {
       senderId,
       senderId,
       timestamp: Date.now()
       timestamp: Date.now()
     };
     };
-    setMessageList((prevMessageList) => [...prevMessageList, message])
+    setMessageList((prevMessageList) => [...prevMessageList, message]);
+    // 异步保存消息到数据库
+    saveMessageToDatabase(message).catch(() => {
+      // 静默失败,已经在saveMessageToDatabase中处理了错误
+    });
   };
   };
 
 
   const showImageMessage = (imageUrl: string, senderName: string, senderId?: string): void => {
   const showImageMessage = (imageUrl: string, senderName: string, senderId?: string): void => {
@@ -138,7 +142,11 @@ export const useClassroom = ({ user }:{ user : User }) => {
       senderId,
       senderId,
       timestamp: Date.now()
       timestamp: Date.now()
     };
     };
-    setMessageList((prevMessageList) => [...prevMessageList, message])
+    setMessageList((prevMessageList) => [...prevMessageList, message]);
+    // 异步保存消息到数据库
+    saveMessageToDatabase(message).catch(() => {
+      // 静默失败,已经在saveMessageToDatabase中处理了错误
+    });
   };
   };
 
 
   const showSystemMessage = (text: string): void => {
   const showSystemMessage = (text: string): void => {
@@ -147,7 +155,72 @@ export const useClassroom = ({ user }:{ user : User }) => {
       content: text,
       content: text,
       timestamp: Date.now()
       timestamp: Date.now()
     };
     };
-    setMessageList((prevMessageList) => [...prevMessageList, message])
+    setMessageList((prevMessageList) => [...prevMessageList, message]);
+    // 异步保存系统消息到数据库
+    saveMessageToDatabase(message).catch(() => {
+      // 静默失败,已经在saveMessageToDatabase中处理了错误
+    });
+  };
+
+  // 保存消息到数据库
+  const saveMessageToDatabase = async (message: Message): Promise<void> => {
+    if (!classId) return;
+    
+    try {
+      await chatMessageClient.$post({
+        json: {
+          classId,
+          type: message.type,
+          content: message.content,
+          senderId: message.senderId || null,
+          senderName: message.sender || null,
+          timestamp: message.timestamp
+        }
+      });
+    } catch (error) {
+      console.error('保存消息到数据库失败:', error);
+      // 静默失败,不影响用户体验
+    }
+  };
+
+  // 保存IM消息到数据库(直接从IM消息对象创建)
+  const saveImMessageToDatabase = async (
+    msg: AliVCInteraction.ImMessage, 
+    type: 'text' | 'image' | 'system',
+    content: string,
+    senderName?: string
+  ): Promise<void> => {
+    if (!classId) return;
+    
+    try {
+      await chatMessageClient.$post({
+        json: {
+          classId,
+          type,
+          content,
+          senderId: msg.sender?.userId || null,
+          senderName: senderName || msg.sender?.userId || '系统',
+          timestamp: msg.timestamp || Date.now()
+        }
+      });
+    } catch (error) {
+      console.error('保存IM消息到数据库失败:', error);
+      // 静默失败,不影响用户体验
+    }
+  };
+
+  // 批量保存消息到数据库
+  const saveMessagesToDatabase = async (messages: Message[]): Promise<void> => {
+    if (!classId || messages.length === 0) return;
+    
+    try {
+      // 由于API只支持单条创建,这里逐条保存
+      for (const message of messages) {
+        await saveMessageToDatabase(message);
+      }
+    } catch (error) {
+      console.error('批量保存消息到数据库失败:', error);
+    }
   };
   };
 
 
   const showToast = (type: 'info' | 'success' | 'error', message: string): void => {
   const showToast = (type: 'info' | 'success' | 'error', message: string): void => {
@@ -266,9 +339,13 @@ export const useClassroom = ({ user }:{ user : User }) => {
           if (data.action === 'start_class') {
           if (data.action === 'start_class') {
             setClassStatus(ClassStatus.IN_PROGRESS);
             setClassStatus(ClassStatus.IN_PROGRESS);
             showSystemMessage('老师已开始上课');
             showSystemMessage('老师已开始上课');
+            // 保存课堂开始消息到数据库
+            saveImMessageToDatabase(msg, 'system', '老师已开始上课', '系统').catch(() => {});
           } else if (data.action === 'end_class') {
           } else if (data.action === 'end_class') {
             setClassStatus(ClassStatus.ENDED);
             setClassStatus(ClassStatus.ENDED);
             showSystemMessage('老师已结束上课');
             showSystemMessage('老师已结束上课');
+            // 保存课堂结束消息到数据库
+            saveImMessageToDatabase(msg, 'system', '老师已结束上课', '系统').catch(() => {});
             
             
             // 学生端收到结束课堂消息后,立即自动离开群组
             // 学生端收到结束课堂消息后,立即自动离开群组
             if (role === Role.Student) {
             if (role === Role.Student) {
@@ -300,6 +377,8 @@ export const useClassroom = ({ user }:{ user : User }) => {
           const data = JSON.parse(msg.data);
           const data = JSON.parse(msg.data);
           if (data.action === 'toggle_mute' && data.userId === userId) {
           if (data.action === 'toggle_mute' && data.userId === userId) {
             showSystemMessage(data.mute ? '你已被老师静音' : '老师已取消你的静音');
             showSystemMessage(data.mute ? '你已被老师静音' : '老师已取消你的静音');
+            // 保存静音消息到数据库
+            saveImMessageToDatabase(msg, 'system', data.mute ? '你已被老师静音' : '老师已取消你的静音', '系统').catch(() => {});
           }
           }
         } catch (err) {
         } catch (err) {
           console.error('解析静音指令失败', err);
           console.error('解析静音指令失败', err);
@@ -314,6 +393,8 @@ export const useClassroom = ({ user }:{ user : User }) => {
             };
             };
             setHandUpList([...handUpList, handUpData]);
             setHandUpList([...handUpList, handUpData]);
             showSystemMessage(`${data.studentName || data.studentId} 举手了`);
             showSystemMessage(`${data.studentName || data.studentId} 举手了`);
+            // 保存举手消息到数据库
+            saveImMessageToDatabase(msg, 'system', `${data.studentName || data.studentId} 举手了`, '系统').catch(() => {});
           } else if (data.action === InteractionAction.CancelHandUp) {
           } else if (data.action === InteractionAction.CancelHandUp) {
             setHandUpList(handUpList.filter(h => h.studentId !== data.studentId));
             setHandUpList(handUpList.filter(h => h.studentId !== data.studentId));
           }
           }
@@ -339,6 +420,8 @@ export const useClassroom = ({ user }:{ user : User }) => {
             setQuestions([...questions, question]);
             setQuestions([...questions, question]);
           }
           }
           showSystemMessage(`收到问题: ${data.question}`);
           showSystemMessage(`收到问题: ${data.question}`);
+          // 保存问题消息到数据库
+          saveImMessageToDatabase(msg, 'system', `收到问题: ${data.question}`, '系统').catch(() => {});
         } catch (err) {
         } catch (err) {
           console.error('解析问题消息失败', err);
           console.error('解析问题消息失败', err);
         }
         }
@@ -348,6 +431,8 @@ export const useClassroom = ({ user }:{ user : User }) => {
           if (data.action === InteractionAction.AnswerHandUp && data.studentId === userId) {
           if (data.action === InteractionAction.AnswerHandUp && data.studentId === userId) {
             showSystemMessage('老师已应答你的举手');
             showSystemMessage('老师已应答你的举手');
             setHandUpList(handUpList.filter(h => h.studentId !== data.studentId));
             setHandUpList(handUpList.filter(h => h.studentId !== data.studentId));
+            // 保存应答消息到数据库
+            saveImMessageToDatabase(msg, 'system', '老师已应答你的举手', '系统').catch(() => {});
           }
           }
         } catch (err) {
         } catch (err) {
           console.error('解析应答消息失败', err);
           console.error('解析应答消息失败', err);
@@ -357,6 +442,8 @@ export const useClassroom = ({ user }:{ user : User }) => {
           const data = JSON.parse(msg.data) as InteractionMessage;
           const data = JSON.parse(msg.data) as InteractionMessage;
           if (data.action === InteractionAction.KickStudent && data.studentId === userId) {
           if (data.action === InteractionAction.KickStudent && data.studentId === userId) {
             showSystemMessage('您已被老师移出课堂');
             showSystemMessage('您已被老师移出课堂');
+            // 保存踢人消息到数据库
+            saveImMessageToDatabase(msg, 'system', '您已被老师移出课堂', '系统').catch(() => {});
             // 学生被踢出后自动离开课堂
             // 学生被踢出后自动离开课堂
             setTimeout(() => {
             setTimeout(() => {
               leaveClass();
               leaveClass();
@@ -664,6 +751,37 @@ export const useClassroom = ({ user }:{ user : User }) => {
       listenGroupEvents();
       listenGroupEvents();
       listenMessageEvents();
       listenMessageEvents();
 
 
+      // 加载历史聊天消息
+      try {
+        const response = await chatMessageClient.history.$get({
+          query: {
+            classId,
+            page: 1,
+            pageSize: 50
+          }
+        });
+        
+        if (response.status === 200) {
+          const historyData = await response.json();
+          if (historyData.data && historyData.data.length > 0) {
+            // 将历史消息添加到消息列表
+            const historyMessages: Message[] = historyData.data.map((msg: any) => ({
+              type: msg.type,
+              content: msg.content,
+              sender: msg.senderName || msg.senderId,
+              senderId: msg.senderId,
+              timestamp: msg.timestamp
+            }));
+            
+            setMessageList(prev => [...historyMessages, ...prev]);
+            showSystemMessage(`已加载 ${historyData.data.length} 条历史消息`);
+          }
+        }
+      } catch (error) {
+        console.error('加载历史消息失败:', error);
+        // 静默失败,不影响主要功能
+      }
+
       // 只有老师角色需要获取现有的群组成员列表
       // 只有老师角色需要获取现有的群组成员列表
       if (role === Role.Teacher) {
       if (role === Role.Teacher) {
         try {
         try {
@@ -1037,7 +1155,7 @@ export const useClassroom = ({ user }:{ user : User }) => {
 
 
     try {
     try {
       if (isAudioOn) {
       if (isAudioOn) {
-        await aliRtcEngine.current?.stopAudioCapture()
+        await aliRtcEngine.current?.stopAudioCapture();
         await aliRtcEngine.current?.publishLocalAudioStream(false);
         await aliRtcEngine.current?.publishLocalAudioStream(false);
       } else {
       } else {
         await aliRtcEngine.current?.publishLocalAudioStream(true);
         await aliRtcEngine.current?.publishLocalAudioStream(true);