perf(classroom): инкрементальный поллинг доски, картинки в файлы, ретеншн

#1 Студенческий поллинг: вместо полной перезагрузки доски каждые 2с —
   лёгкая сигнатура страницы (?meta=1 → maxSeq+count). Если доска совпадает
   с сервером (обычный случай при живом WS) — ничего не грузим. Полная
   перезагрузка только при расхождении. Счёт подтверждённых штрихов — по
   положительным id (без bookkeeping).
#2 Картинки-штрихи выносятся в файлы /uploads/classroom (вместо base64 в БД):
   меньше БД и payload поллинга. Имя с префиксом sessionId.
#5 Ретеншн: classroom-cleanup удаляет штрихи+файлы завершённых сессий старше
   N дней (app_settings.classroom_retention_days, по умолч. 30; 0 = выкл),
   историю/чат/посещаемость не трогает. Планировщик в server.js.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-06-12 13:02:26 +03:00
parent ddc260e114
commit 5d3db90b5d
4 changed files with 128 additions and 8 deletions
+4
View File
@@ -339,6 +339,7 @@ app.use('/avatars', express.static(path.join(__dirname, '../uploads/avatars'), {
app.use('/uploads/flashcards', express.static(path.join(__dirname, '../uploads/flashcards'), { maxAge: '7d' }));
app.use('/uploads/generated', express.static(path.join(__dirname, '../uploads/generated'), { maxAge: '7d' }));
app.use('/uploads/materials', express.static(path.join(__dirname, '../uploads/materials'), { maxAge: '7d' }));
app.use('/uploads/classroom', express.static(path.join(__dirname, '../uploads/classroom'), { maxAge: '7d' }));
// Redirect legacy .html URLs → clean URLs (301)
app.use((req, res, next) => {
@@ -526,6 +527,9 @@ const server = app.listen(PORT, () => logger.info(`Server running on port ${PORT
/* ── WebSocket server for low-latency classroom events (cursor + preview) ── */
require('./ws-server').attach(server);
/* ── Ретеншн данных доски: чистка штрихов/картинок старых завершённых сессий ── */
try { require('./classroom-cleanup').schedule(); } catch (e) { logger.error('classroom-cleanup schedule error', { err: e.message }); }
/* ── Graceful shutdown ── */
function shutdown(signal) {
logger.info(`${signal} received — shutting down gracefully`);