feat(meeting): 添加通过作业ID查询学生详细信息功能
- 在MeetingController中新增getStudentDetails方法,支持通过作业ID查询学生信息 - 添加StudentDetailResponseDto和StudentListResponseDto数据传输对象 - 实现getStudentDetailsByHomeworkId服务方法,支持长短ID映射和自动分配 - 优化SQL查询逻辑,支持批量插入缺失的短ID - 添加相应的API文档注解和参数验证
This commit is contained in:
@ -5,7 +5,7 @@ import { MeetingService } from './meeting.service';
|
||||
import { NonEmptyStringPipe } from '@/common/pipes/non-empty-string.pipe';
|
||||
import { Uint32Pipe } from '@/common/pipes/uint32.pipe';
|
||||
import { ApiCustomOkResponse } from '@/common/decorators/swagger.decorator';
|
||||
import { TokenResponseDto } from './meeting.dto';
|
||||
import { StudentListResponseDto, TokenResponseDto } from './meeting.dto';
|
||||
import { MeetingRedisService } from '../websocket/meeting-redis.service';
|
||||
|
||||
/**
|
||||
@ -70,7 +70,7 @@ export class MeetingController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 从房间黑名单移除指定用户(短 UID)
|
||||
* 从房间黑名单移除指定用户 (短 UID)
|
||||
*/
|
||||
@Delete('blacklist/:roomId/:shortUid')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@ -89,4 +89,24 @@ export class MeetingController {
|
||||
await this.redis.removeFromBlacklist(roomId, shortUid);
|
||||
return new OkResult(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过作业 ID 查询学生详细信息列表
|
||||
*/
|
||||
@Get('students/:homeworkId')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@ApiParam({ name: 'homeworkId', description: '作业 ID', example: '1234567890' })
|
||||
@ApiCustomOkResponse({
|
||||
summary: '学生详细信息列表',
|
||||
model: StudentListResponseDto,
|
||||
apiDescription: '学生详细信息列表,包含长 ID、短 ID (可能为 null) 和姓名',
|
||||
resDescription: '学生详细信息列表',
|
||||
})
|
||||
public async getStudentDetails(@Param('homeworkId', new Uint32Pipe('homeworkId')) homeworkId: number) {
|
||||
const students = await this.meetingService.getStudentDetailsByHomeworkId(homeworkId);
|
||||
return new OkResult({
|
||||
total: students.length,
|
||||
students,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,9 +7,34 @@ export class TokenResponseDto {
|
||||
@ApiProperty({ description: '应用 ID', example: '1234567890' })
|
||||
public appid!: string;
|
||||
|
||||
@ApiProperty({ description: '声网RTC的Token', example: '0061234567890abcdef...' })
|
||||
@ApiProperty({ description: '声网 RTC 的 Token', example: '0061234567890abcdef...' })
|
||||
public rtcToken!: string;
|
||||
|
||||
@ApiProperty({ description: '过期时间戳', example: 1704067200000 })
|
||||
public expiresAt!: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 学生详细信息响应 DTO
|
||||
*/
|
||||
export class StudentDetailResponseDto {
|
||||
@ApiProperty({ description: '学生长 ID (数据库 ID)', example: 1234567890 })
|
||||
longId!: number;
|
||||
|
||||
@ApiProperty({ description: '学生短 ID (声网短 ID)', example: 1001 })
|
||||
shortId!: number;
|
||||
|
||||
@ApiProperty({ description: '学生姓名', example: '张三' })
|
||||
studentName!: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 学生列表响应 DTO
|
||||
*/
|
||||
export class StudentListResponseDto {
|
||||
@ApiProperty({ description: '学生总数', example: 30 })
|
||||
total!: number;
|
||||
|
||||
@ApiProperty({ description: '学生列表', type: [StudentDetailResponseDto] })
|
||||
students!: StudentDetailResponseDto[];
|
||||
}
|
||||
|
||||
@ -5,6 +5,28 @@ import { EntityManager } from '@mikro-orm/core';
|
||||
import type { TokenResponseDto } from './meeting.dto';
|
||||
import { MeetingUser } from '@/entities/MeetingUser';
|
||||
|
||||
/**
|
||||
* 学生信息接口
|
||||
*/
|
||||
export interface StudentInfo {
|
||||
/** 学生 ID */
|
||||
studentId: number;
|
||||
/** 学生姓名 */
|
||||
studentName: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 学生详细信息接口 (包含长短 ID)
|
||||
*/
|
||||
export interface StudentDetailInfo {
|
||||
/** 学生长 ID (数据库 ID) */
|
||||
longId: number;
|
||||
/** 学生短 ID (声网短 ID) */
|
||||
shortId: number;
|
||||
/** 学生姓名 */
|
||||
studentName: string;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class MeetingService {
|
||||
constructor(
|
||||
@ -12,6 +34,96 @@ export class MeetingService {
|
||||
private readonly nacosConfig: NacosConfigService
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 通过作业 ID 获取学生详细信息列表 (包含长短 ID)
|
||||
* @param homeworkId - 作业 ID
|
||||
* @returns 学生详细信息列表,包含长 ID、短 ID 和姓名
|
||||
*/
|
||||
public async getStudentDetailsByHomeworkId(homeworkId: number): Promise<StudentDetailInfo[]> {
|
||||
const id = Number(homeworkId);
|
||||
if (!Number.isFinite(id) || id <= 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// 查询学生信息并批量分配缺失的短 ID
|
||||
const sql = `
|
||||
SELECT u.Id AS longId, u.Name AS studentName, mu.id AS shortId
|
||||
FROM icr_homework h
|
||||
INNER JOIN scs_studentgroup sg ON h.StudentGroupId = sg.Id
|
||||
INNER JOIN scs_studentgroupdetail sgd ON sg.Id = sgd.StudentGroupId
|
||||
INNER JOIN scs_user u ON sgd.StudentId = u.Id
|
||||
LEFT JOIN meeting_user mu ON u.Id = mu.longUserId
|
||||
WHERE h.Id = ${id}
|
||||
`;
|
||||
|
||||
const rows = await this.em.getConnection().execute(sql);
|
||||
const results = rows as Array<{ longId: number | string; studentName: string; shortId: number | null }>;
|
||||
|
||||
// 批量插入缺失的短 ID
|
||||
const missingIds = results.filter((row) => row.shortId === null).map((row) => Number(row.longId));
|
||||
if (missingIds.length > 0) {
|
||||
// 批量插入缺失的短 ID
|
||||
await this.em.getConnection().execute(`
|
||||
INSERT INTO meeting_user (longUserId, createdAt)
|
||||
VALUES ${missingIds.map((longUserId) => `(${longUserId}, NOW())`).join(',')}
|
||||
ON DUPLICATE KEY UPDATE longUserId = longUserId
|
||||
`);
|
||||
|
||||
// 查询新分配的短 ID
|
||||
const newUsers = await this.em.getConnection().execute(`
|
||||
SELECT longUserId, id FROM meeting_user WHERE longUserId IN (${missingIds.join(',')})
|
||||
`);
|
||||
const shortIdMap = new Map<number, number>();
|
||||
(newUsers as Array<{ longUserId: number; id: number }>).forEach((user) => {
|
||||
shortIdMap.set(user.longUserId, user.id);
|
||||
});
|
||||
|
||||
// 更新缺失的短 ID
|
||||
results.forEach((row) => {
|
||||
if (row.shortId === null) {
|
||||
row.shortId = shortIdMap.get(Number(row.longId)) ?? null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 转换为最终结果
|
||||
return results.map((row) => ({
|
||||
longId: Number(row.longId),
|
||||
studentName: row.studentName || '',
|
||||
shortId: row.shortId ?? 0,
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过作业 ID 获取学生列表
|
||||
* @param homeworkId - 作业 ID
|
||||
* @returns 学生列表,包含学生 ID 和姓名
|
||||
*/
|
||||
public async getStudentsByHomeworkId(homeworkId: number): Promise<StudentInfo[]> {
|
||||
const id = Number(homeworkId);
|
||||
if (!Number.isFinite(id) || id <= 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// 使用单条 SQL JOIN 查询 (性能最优)
|
||||
const sql = `
|
||||
SELECT
|
||||
u.Id AS studentId,
|
||||
u.Name AS studentName
|
||||
FROM icr_homework h
|
||||
INNER JOIN scs_studentgroup sg ON h.StudentGroupId = sg.Id
|
||||
INNER JOIN scs_studentgroupdetail sgd ON sg.Id = sgd.StudentGroupId
|
||||
INNER JOIN scs_user u ON sgd.StudentId = u.Id
|
||||
WHERE h.Id = ${id}
|
||||
`;
|
||||
|
||||
const rows = await this.em.getConnection().execute(sql);
|
||||
return (rows as Array<{ studentId: number | string; studentName: string }>).map((row) => ({
|
||||
studentId: Number(row.studentId),
|
||||
studentName: row.studentName || '',
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过作业 ID 查询老师的长 ID
|
||||
* @param homeworkId - 作业 ID(JoinRoomData.homeworkId)
|
||||
|
||||
Reference in New Issue
Block a user