فهرست منبع

添加课堂创建功能和IM自动重连机制

在mobile_app.tsx中添加ClassroomPage路由
在pages_classroom.tsx中实现课堂创建功能(createClass)
添加IM自动重连机制处理断线情况
新增IM Token生成函数(generateImToken)
完善课堂创建UI和错误处理
yourname 7 ماه پیش
والد
کامیت
2157dce1e4
2فایلهای تغییر یافته به همراه149 افزوده شده و 1 حذف شده
  1. 6 0
      client/mobile/mobile_app.tsx
  2. 143 1
      client/mobile/pages_classroom.tsx

+ 6 - 0
client/mobile/mobile_app.tsx

@@ -21,6 +21,7 @@ import { ExclamationTriangleIcon, HomeIcon, BellIcon, UserIcon } from '@heroicon
 import { NotificationsPage } from './pages_messages.tsx';
 import { LivePage } from './pages_live.tsx';
 import { RTCPage } from './pages_rtc.tsx';
+import { ClassroomPage } from './pages_classroom.tsx'
 
 // 设置中文语言
 dayjs.locale('zh-cn');
@@ -252,6 +253,11 @@ const App = () => {
       element: <Navigate to="/mobile" replace />,
       errorElement: <ErrorPage />
     },
+    {
+      path: '/mobile/classroom',
+      element: <ClassroomPage />,
+      errorElement: <ErrorPage />
+    },
     {
       path: '/mobile/live',
       element: <LivePage />,

+ 143 - 1
client/mobile/pages_classroom.tsx

@@ -58,6 +58,7 @@ type ClassroomContextType = {
   handUpList: HandUpRequest[]; // 举手列表
   questions: string[]; // 问题列表
   setRole: (role: 'teacher' | 'student') => void;
+  createClass: (className: string, maxMembers?: number) => Promise<string | null>; // 创建课堂
   startClass: () => Promise<void>;
   endClass: () => Promise<void>;
   toggleMuteMember: (userId: string, mute: boolean) => Promise<void>;
@@ -119,9 +120,21 @@ const IM_APP_SIGN = 'H4sIAAAAAAAE/wCQAG//zguHB+lYCilkv7diSkk4GmcvLuds+InRu9vFOFe
 const RTC_APP_ID = 'a5842c2a-d94a-43be-81de-1fdb712476e1';
 const RTC_APP_KEY = 'b71d65f4f84c450f6f058f4ad507bd42';
 
+// IM Token生成
+async function generateImToken(userId: string, role: string): Promise<string> {
+    const nonce = 'AK_4';
+    const timestamp = Math.floor(Date.now() / 1000) + 3600 * 3;
+    const pendingShaStr = `${IM_APP_ID}${IM_APP_KEY}${userId}${nonce}${timestamp}${role}`;
+    const encoder = new TextEncoder();
+    const data = encoder.encode(pendingShaStr);
+    const hash = await crypto.subtle.digest('SHA-256', data);
+    return hex(hash);
+}
+
 export const ClassroomPage = () => {
   // 状态管理
   const [userId, setUserId] = useState<string>('');
+  const [className, setClassName] = useState<string>('');
   const [role, setRole] = useState<'teacher' | 'student'>('student');
   const [classId, setClassId] = useState<string>('');
   const [isLoggedIn, setIsLoggedIn] = useState<boolean>(false);
@@ -153,8 +166,28 @@ export const ClassroomPage = () => {
       showMessage('IM连接成功');
     });
 
-    imEngine.current.on('disconnect', (code: number) => {
+    imEngine.current.on('disconnect', async (code: number) => {
       showMessage(`IM断开连接: ${code}`);
+      // 自动重连
+      try {
+        const imToken = await generateImToken(userId, role);
+        await imEngine.current!.login({
+          user: {
+            userId,
+            userExtension: '{}'
+          },
+          userAuth: {
+            nonce: 'AK_4',
+            timestamp: Math.floor(Date.now() / 1000) + 3600 * 3,
+            token: imToken,
+            role
+          }
+        });
+        showMessage('IM自动重连成功');
+      } catch (err: unknown) {
+        const error = err as Error;
+        showMessage(`IM自动重连失败: ${error.message}`);
+      }
     });
   };
 
@@ -268,6 +301,21 @@ export const ClassroomPage = () => {
         appSign: IM_APP_SIGN,
         logLevel: ImLogLevel.ERROR,
       });
+
+      // 登录IM
+      const imToken = await generateImToken(userId, role);
+      await imEngine.current.login({
+        user: {
+          userId,
+          userExtension: '{}'
+        },
+        userAuth: {
+          nonce: 'AK_4',
+          timestamp: Math.floor(Date.now() / 1000) + 3600 * 3,
+          token: imToken,
+          role
+        }
+      });
       
       // 初始化RTC
       aliRtcEngine.current = AliRtcEngine.getInstance();
@@ -415,6 +463,73 @@ export const ClassroomPage = () => {
     }
   };
 
+  // 创建课堂
+  const createClass = async (className: string, maxMembers = 200): Promise<string | null> => {
+    if (!imEngine.current || !isLoggedIn || role !== 'teacher') {
+      showToast('error', '只有老师可以创建课堂');
+      return null;
+    }
+    
+    try {
+      const groupManager = imEngine.current.getGroupManager();
+      if (!groupManager) {
+        throw new Error('群组管理器未初始化');
+      }
+
+      // 显示创建中状态
+      showToast('info', '正在创建课堂...');
+      
+      // 调用IM SDK创建群组
+      const response = await groupManager.createGroup({
+        groupName: className,
+        groupMeta: JSON.stringify({
+          classType: 'interactive',
+          creator: userId,
+          createdAt: Date.now(),
+          maxMembers
+        })
+      });
+
+      if (!response?.groupId) {
+        throw new Error('创建群组失败: 未返回群组ID');
+      }
+
+      // 创建成功后自动加入群组
+      try {
+        await groupManager.joinGroup(response.groupId);
+        showToast('success', '课堂创建并加入成功');
+        showMessage(`课堂 ${className} 创建成功,ID: ${response.groupId}`);
+        
+        // 更新状态
+        setClassId(response.groupId);
+        setIsJoinedClass(true);
+        
+        // 初始化群组消息管理器
+        const messageManager = imEngine.current.getMessageManager();
+        if (messageManager) {
+          imMessageManager.current = messageManager;
+          listenMessageEvents();
+        }
+        
+        // 记录创建时间
+        const createTime = new Date();
+        showMessage(`创建时间: ${createTime.toLocaleString()}`);
+        
+        return response.groupId;
+      } catch (joinErr: any) {
+        throw new Error(`创建成功但加入失败: ${joinErr.message}`);
+      }
+    } catch (err: any) {
+      const errorMsg = err.message.includes('alreadyExist')
+        ? '课堂已存在'
+        : `课堂创建失败: ${err.message}`;
+      
+      setErrorMessage(errorMsg);
+      showToast('error', errorMsg);
+      return null;
+    }
+  };
+
   // 清理资源
   useEffect(() => {
     return () => {
@@ -501,6 +616,7 @@ export const ClassroomPage = () => {
       handUpList,
       questions,
       setRole,
+      createClass,
       startClass,
       endClass,
       toggleMuteMember,
@@ -526,6 +642,17 @@ export const ClassroomPage = () => {
         <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
           <div className="md:col-span-1">
             <form>
+              {!isLoggedIn && (
+                <div className="mb-2">
+                  <label className="block text-sm font-medium text-gray-700">课堂名称</label>
+                  <input
+                    className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm"
+                    value={className}
+                    onChange={(e) => setClassName(e.target.value)}
+                    placeholder="输入课堂名称"
+                  />
+                </div>
+              )}
               <div className="mb-2">
                 <label className="block text-sm font-medium text-gray-700">用户ID</label>
                 <input
@@ -557,6 +684,21 @@ export const ClassroomPage = () => {
               </div>
               
               <div className="flex space-x-2 mb-2">
+                {!isLoggedIn && (
+                  <button
+                    type="button"
+                    className="px-3 py-2 bg-green-600 text-white rounded-md"
+                    disabled={!className}
+                    onClick={async () => {
+                      const classId = await createClass(className);
+                      if (classId) {
+                        setClassId(classId);
+                      }
+                    }}
+                  >
+                    创建课堂
+                  </button>
+                )}
                 <button
                   type="button"
                   className="px-3 py-2 bg-blue-600 text-white rounded-md"