第 2 阶段:账号与班级,作品保存在服务器

- SQLite(bun:sqlite,编号迁移):班级、用户、会话(只存令牌哈希)、作品
- 登录取代统一访问口令:老师账号由配置设定;接口和 WebSocket 都要登录;按 IP 限制输错次数
- 班级管理页:建班、改名、删除(有学生时不能删);上传 CSV 名单导入(兼容 GBK / UTF-8,
  识别表头,标出被 Excel 变成科学计数法的学号),密码留空生成 6 位数字;查看、重置密码,
  导出带密码的名单
- 学生作品自动保存到服务器(按账号和实验),显示保存状态;离开页面前立即保存
- 前端加路由:登录页、实验页、班级管理页
- 部署:TEACHER_PASSWORD 必填,数据库放在数据卷

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-10-22 17:36:11 +08:00
1 parent a00622be15
commit 7cec273d58
31 files changed
+1776 -382

No files matched your search

+10 -3
View File
@@ -1,4 +1,4 @@
# 部署:先在 .env 里写好 ACCESS_PASSWORD(参考 .env.example),再 docker compose up -d
# 部署:先在 .env 里写好 TEACHER_PASSWORD(参考 .env.example),再 docker compose up -d
# HTTPS 由 Nginx Proxy Manager 负责,它在 npm_proxy 网络里转发到 iot-sim:80;步骤见 deploy/README.md
services:
app:
@@ -6,16 +6,22 @@ services:
build: .
restart: unless-stopped
environment:
ACCESS_PASSWORD: ${ACCESS_PASSWORD:?请在 .env 里设置 ACCESS_PASSWORD}
# 老师账号(只有一个);学生账号由老师在网页上导入名单
TEACHER_USERNAME: ${TEACHER_USERNAME:-admin}
TEACHER_PASSWORD: ${TEACHER_PASSWORD:?请在 .env 里设置 TEACHER_PASSWORD}
DATA_DIR: /data
# 以下可在 .env 里调整,说明见 deploy/README.md
SIM_MAX_RUNS: ${SIM_MAX_RUNS:-150}
SIM_HEAP_LIMIT_MB: ${SIM_HEAP_LIMIT_MB:-128}
SIM_CPU_LIMIT_SECONDS: ${SIM_CPU_LIMIT_SECONDS:-120}
SIM_MAX_RUN_MINUTES: ${SIM_MAX_RUN_MINUTES:-60}
# 容器文件系统只读,只有 /tmp(学生工程的临时目录)可写
# 容器文件系统只读,只有 /tmp(学生工程的临时目录)和数据卷可写
read_only: true
tmpfs:
- /tmp:size=1g
# 数据库(账号、班级、学生作品),重新部署不会丢
volumes:
- data:/data
cap_drop: [ALL]
security_opt:
- no-new-privileges:true
@@ -47,6 +53,7 @@ services:
- caddy_config:/config
volumes:
data:
caddy_data:
caddy_config: