feat(meeting): 实现学生排序功能并优化Redis存储

- 将Redis中的学生排序存储从长ID改为短ID
- 在监控端按studentListForSort顺序重新排列学生列表
- 添加新加入学生的处理逻辑
- 实现WebSocket消息处理学生排序变更事件
- 更新类型定义支持学生排序变更数据结构
- 优化Redis服务注释说明短ID存储格式
This commit is contained in:
2026-03-14 22:35:30 +08:00
parent 5873cda8f5
commit 525281e3f8
5 changed files with 67 additions and 20 deletions

View File

@ -97,21 +97,21 @@ export class MeetingService {
// 如果提供了 roomId从 Redis 获取排序并应用
if (roomId) {
const orderedIds = await this.redis.getStudentOrder(roomId);
if (orderedIds.length > 0) {
// 创建映射
const orderedShortIds = await this.redis.getStudentOrder(roomId);
if (orderedShortIds.length > 0) {
// 创建短 ID 到学生的映射
const studentMap = new Map<number, StudentDetailInfo>();
finalResults.forEach((student) => {
studentMap.set(student.longId, student);
studentMap.set(student.shortId, student);
});
// 按 Redis 中的顺序重新排列
// 按 Redis 中的短 ID 顺序重新排列
const orderedResults: StudentDetailInfo[] = [];
for (const longId of orderedIds) {
const student = studentMap.get(longId);
for (const shortId of orderedShortIds) {
const student = studentMap.get(shortId);
if (student) {
orderedResults.push(student);
studentMap.delete(longId); // 移除已添加的学生
studentMap.delete(shortId); // 移除已添加的学生
}
}

View File

@ -74,8 +74,8 @@ export class MeetingRedisService {
* 用途:存储学生列表的排序顺序
* 数据结构List
* Key 格式meeting:student-order:{roomId}
* List 内容:按顺序存储学生的 ID (字符串)
* 适用场景:教师端拖动排序后,监控端按相同顺序显示
* List 内容:按顺序存储学生的 ID (数字字符串)
* 适用场景:教师端拖动排序后,监控端和学生端按相同顺序显示
* 过期时间24 小时
*/
STUDENT_ORDER: 'meeting:student-order:',
@ -518,7 +518,7 @@ export class MeetingRedisService {
/**
* 获取学生排序顺序
* @param roomId - 房间 ID
* @returns 学生 ID 数组 (按排序顺序),未设置返回空数组
* @returns 学生 ID 数组 (按排序顺序),未设置返回空数组
*/
async getStudentOrder(roomId: string): Promise<number[]> {
const key = `${this.KEY_PREFIX.STUDENT_ORDER}${roomId}`;

View File

@ -173,7 +173,31 @@
*/
const studentUsers = computed(() => {
const users = meeting.remoteUsers.value as (IAgoraRTCRemoteUser & { userName?: string })[];
return users.filter((u) => (teacherUid.value ? u.uid !== teacherUid.value : true));
const filteredUsers = users.filter((u) => (teacherUid.value ? u.uid !== teacherUid.value : true));
// 按照 studentListForSort 中的顺序重新排列
if (studentListForSort.value.length > 0) {
// 创建 shortId -> user 的映射
const userMap = new Map<number, typeof filteredUsers[0]>();
filteredUsers.forEach((user) => {
userMap.set(Number(user.uid), user);
});
// 按照 studentListForSort 的顺序重新排列
const sortedUsers: typeof filteredUsers = [];
studentListForSort.value.forEach((student) => {
const user = userMap.get(student.shortId);
if (user) {
sortedUsers.push(user);
userMap.delete(student.shortId);
}
});
// 添加 studentListForSort 中没有的用户 (新加入的学生)
return [...sortedUsers, ...Array.from(userMap.values())];
}
return filteredUsers;
});
/**
@ -285,14 +309,15 @@
selectedUserUid.value = packet.data.targetUid;
break;
}
// 学生排序变更事件 (类型定义待添加)
// case 'sev_student_order_changed': {
// const newOrder = packet.data.studentShortIds;
// if (Array.isArray(newOrder) && newOrder.length > 0) {
// console.log('[监控端] 学生排序已更新:', newOrder);
// }
// break;
// }
// 学生排序变更事件
case 'sev_student_order_changed': {
const newOrder = packet.data.studentShortIds;
if (Array.isArray(newOrder) && newOrder.length > 0) {
console.log('[监控端] 学生排序已更新,重新获取学生列表');
fetchStudentList();
}
break;
}
case 'sev_class_started': {
classStatus.value = 'in_class';
console.log('[监控端] 收到上课消息');

View File

@ -518,6 +518,14 @@
}
break;
}
case 'sev_student_order_changed': {
const newOrder = packet.data.studentShortIds;
if (Array.isArray(newOrder) && newOrder.length > 0) {
console.log('[教师端] 学生排序已更新,重新获取学生列表');
fetchStudentList();
}
break;
}
case 'sev_class_started': {
classStatus.value = 'in_class';

View File

@ -323,6 +323,19 @@ export interface OnSetMainVideoData extends OnControlMessageData {
targetUid: number;
}
/**
* 控制消息数据 - 学生排序变更
*/
export interface OnStudentOrderChangedData extends OnControlMessageData {
/** 学生短 ID 数组 (按排序顺序) */
studentShortIds: number[];
}
/**
* WebSocket 消息包 - 服务端下发:学生排序变更
*/
export type OnStudentOrderChangedPacket = WebSocketPacket<'sev_student_order_changed', OnStudentOrderChangedData>;
/**
* WebSocket 消息包 - 客户端接收:加入房间成功响应
*/
@ -402,6 +415,7 @@ export type DownlinkControlMessagePacket =
| OnScreenShareStartedPacket
| OnScreenShareStoppedPacket
| OnSetMainVideoPacket
| OnStudentOrderChangedPacket
| OnUnmuteAudioPacket
| OnUnmuteVideoPacket;