feat(api): 新增 /ws/collab 通道与课堂求助控制面
学生发起/撤销求助,在线教师收到全量列表。求助只在内存,不落库。 二进制帧的限流单独一档,避免协作输入把连接踢掉。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K1d8B3f4SXJwDvUY625eQd
This commit is contained in:
83
apps/api/src/collab/state.ts
Normal file
83
apps/api/src/collab/state.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* 课堂求助的内存状态。
|
||||
*
|
||||
* 不落库是有意的:求助是课堂上的即时行为,学生关掉页面这条请求就该消失。
|
||||
* 服务端只有一个 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 function resetCollabState() {
|
||||
requests.clear()
|
||||
teachers.clear()
|
||||
}
|
||||
Reference in New Issue
Block a user