- 运行时带上当前实验;实验有 database.sql 而学生还没有其中的库时,先建好这些库 (已有的库不动,保留学生的数据);建库失败不影响运行 - 教材项目 8 要"附加"现成的 WisdomCampus 库,学生不用先去数据库页面恢复 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
88 lines
3.1 KiB
TypeScript
88 lines
3.1 KiB
TypeScript
import type { ClientMessage, ServerMessage } from '@iot/shared'
|
|
import { createAiQueue } from './ai-review'
|
|
import { createApp } from './app'
|
|
import { ensureTeacher, tokenUser } from './auth'
|
|
import { compileService } from './compile-service'
|
|
import { languageService } from './language-service'
|
|
import { config } from './config'
|
|
import { openDatabase } from './db'
|
|
import { createGradingQueue } from './grading/queue'
|
|
import { restoreExperimentDatabases, userDatabaseDir } from './databases'
|
|
import { listSamples } from './samples'
|
|
import { cleanupStaleRuns, RunSession } from './run-session'
|
|
import { sqlService } from './sql-service'
|
|
import { checkSandbox, sandboxError } from './sandbox'
|
|
|
|
await cleanupStaleRuns()
|
|
await checkSandbox()
|
|
if (sandboxError) console.error(sandboxError)
|
|
|
|
const db = openDatabase()
|
|
ensureTeacher(db)
|
|
const aiQueue = createAiQueue(db)
|
|
const app = createApp(db, createGradingQueue(db, (id) => aiQueue.enqueue(id)), aiQueue)
|
|
|
|
interface SocketData {
|
|
userId?: number
|
|
session?: RunSession
|
|
}
|
|
|
|
const server = Bun.serve({
|
|
port: config.port,
|
|
async fetch(req, server) {
|
|
const url = new URL(req.url)
|
|
if (url.pathname === '/ws') {
|
|
// 浏览器的 WebSocket 不能带请求头,令牌放在网址参数里
|
|
const user = await tokenUser(db, url.searchParams.get('token'))
|
|
if (!user) return new Response('请先登录', { status: 401 })
|
|
if (server.upgrade(req, { data: { userId: user.id } })) return
|
|
return new Response('WebSocket 升级失败', { status: 400 })
|
|
}
|
|
return app.fetch(req, server)
|
|
},
|
|
websocket: {
|
|
data: {} as SocketData,
|
|
open(ws) {
|
|
const userId = ws.data.userId!
|
|
ws.data.session = new RunSession((message) => ws.send(JSON.stringify(message)), () => userDatabaseDir(userId))
|
|
},
|
|
async message(ws, raw) {
|
|
let message: ClientMessage
|
|
try {
|
|
message = JSON.parse(String(raw))
|
|
} catch {
|
|
return
|
|
}
|
|
const session = ws.data.session
|
|
if (!session) return
|
|
if (message.type === 'run') {
|
|
try {
|
|
const { sampleId } = message
|
|
if (typeof sampleId === 'string' && (await listSamples()).some((s) => s.id === sampleId)) {
|
|
// 建库失败不影响运行(程序连不上库时会报出来)
|
|
await restoreExperimentDatabases(ws.data.userId!, sampleId, true).catch((error) => console.error('自动建实验数据库失败', error))
|
|
}
|
|
await session.start(message.files)
|
|
} catch (error) {
|
|
ws.send(JSON.stringify({ type: 'serverError', message: String(error) } satisfies ServerMessage))
|
|
}
|
|
} else if (message.type === 'stop') {
|
|
await session.stop()
|
|
} else if (message.type === 'boardInput') {
|
|
session.boardInput(message)
|
|
} else if (message.type === 'boardReset') {
|
|
session.boardReset()
|
|
} else {
|
|
session.input(message)
|
|
}
|
|
},
|
|
async close(ws) {
|
|
await ws.data.session?.dispose()
|
|
},
|
|
},
|
|
})
|
|
|
|
console.log(`服务器已启动:http://localhost:${server.port}`)
|
|
await Promise.all([compileService.start(), languageService.start(), sqlService.start()])
|
|
console.log('C# 编译服务已就绪')
|