accept 时按库里的 adminType 复核身份,房间以学生为键。 服务端只按房间转发二进制帧,不解析内容。 老师掉线请求退回排队,学生掉线请求随人清除。 顺带处理三项 Task 2 评审遗留: - TEACHER_ROLES 去重,handler.ts 改为从 routes/helpers.ts 导入,不再自留一份 - 补全学生排队中断线(尚未进房间)的清理分支,避免陈旧请求卡死在列表里 - 修正 websocket.ts 里一处过期注释:username/adminType 是三种 kind 都会填, 不是只有 collab 才填 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K1d8B3f4SXJwDvUY625eQd
114 lines
3.1 KiB
TypeScript
114 lines
3.1 KiB
TypeScript
/**
|
||
* 课堂求助的内存状态。
|
||
*
|
||
* 不落库是有意的:求助是课堂上的即时行为,学生关掉页面这条请求就该消失。
|
||
* 服务端只有一个 serve 进程(main.ts 单二进制 + 子命令,compose 里 oj-api 一个容器),
|
||
* 所以内存态够用,不需要 Redis 同步。进程重启丢掉全部状态,两端重连后回到干净状态。
|
||
*/
|
||
|
||
export type CollabSocket = Bun.ServerWebSocket<import("../websocket").SubmissionSocketData>
|
||
|
||
export interface HelpRequest {
|
||
studentId: number
|
||
studentName: string
|
||
className: string | null
|
||
/** 题目的展示号(problem._id 列,前端一路用的都是它),不是自增主键 */
|
||
problemId: string
|
||
problemTitle: string
|
||
createdAt: number
|
||
status: "pending" | "active"
|
||
teacherId?: number
|
||
teacherName?: string
|
||
socket: CollabSocket
|
||
}
|
||
|
||
/** 求助表,以学生为键 —— 一个学生同时只有一个求助 */
|
||
const requests = new Map<number, HelpRequest>()
|
||
|
||
/** 在线老师的连接。用于推列表,也用于判断 no_teacher */
|
||
const teachers = new Set<CollabSocket>()
|
||
|
||
export function addRequest(request: HelpRequest) {
|
||
requests.set(request.studentId, request)
|
||
}
|
||
|
||
export function getRequest(studentId: number) {
|
||
return requests.get(studentId)
|
||
}
|
||
|
||
export function removeRequest(studentId: number) {
|
||
return requests.delete(studentId)
|
||
}
|
||
|
||
export function hasRequest(studentId: number) {
|
||
return requests.has(studentId)
|
||
}
|
||
|
||
/** 按发起时间正序。老师端按等待时长排序展示,不强制先来先到 */
|
||
export function listRequests() {
|
||
return Array.from(requests.values()).sort((a, b) => a.createdAt - b.createdAt)
|
||
}
|
||
|
||
/** 比自己早创建、且仍在排队的请求数 */
|
||
export function queueAheadOf(studentId: number) {
|
||
const self = requests.get(studentId)
|
||
if (!self) return 0
|
||
let ahead = 0
|
||
for (const request of requests.values()) {
|
||
if (request.status === "pending" && request.createdAt < self.createdAt) ahead += 1
|
||
}
|
||
return ahead
|
||
}
|
||
|
||
export function addTeacher(ws: CollabSocket) {
|
||
teachers.add(ws)
|
||
}
|
||
|
||
export function removeTeacher(ws: CollabSocket) {
|
||
teachers.delete(ws)
|
||
}
|
||
|
||
export function hasTeacherOnline() {
|
||
return teachers.size > 0
|
||
}
|
||
|
||
export function teacherSockets() {
|
||
return teachers
|
||
}
|
||
|
||
export interface Room {
|
||
/** 房主 = 学生。房间以学生为键,因为学生的代码是内容源 */
|
||
studentId: number
|
||
teacherId: number
|
||
studentSocket: CollabSocket
|
||
teacherSocket: CollabSocket
|
||
problemId: string
|
||
}
|
||
|
||
const rooms = new Map<number, Room>()
|
||
|
||
export function openRoom(room: Room) {
|
||
rooms.set(room.studentId, room)
|
||
}
|
||
|
||
export function getRoom(studentId: number) {
|
||
return rooms.get(studentId)
|
||
}
|
||
|
||
export function closeRoom(studentId: number) {
|
||
return rooms.delete(studentId)
|
||
}
|
||
|
||
/** 这条连接当前所在的房间。ws.data.roomOwnerId 是房主(学生)的 id */
|
||
export function roomOf(ws: CollabSocket) {
|
||
const ownerId = ws.data.roomOwnerId
|
||
return ownerId === undefined ? undefined : rooms.get(ownerId)
|
||
}
|
||
|
||
/** 仅供进程退出或测试用,正常路径不该调 */
|
||
export function resetCollabState() {
|
||
requests.clear()
|
||
teachers.clear()
|
||
rooms.clear()
|
||
}
|