答题管理系统 设计文档(升级方案)¶
面向现有"局域网联机答题"项目(FastAPI + SQLite + 原生 JS),将其从昵称制实时抢答工具升级为账号制答题管理系统。 本文档覆盖:管理员端(账号管理 / 题目管理 / 统计查看 / 用户管理)与用户端(登录 / 注册 / 账号认领 / 答题记录)的完整功能规格,先评审、后编码。
v1.1 实现修订(校园落地,以本段为准) 学生通常没有手机号:注册 / 登录 / 认领不再依赖手机与短信验证码,统一使用 用户名 或 邮箱 作为身份凭据; - 注册:姓名 + (用户名 或 邮箱,至少一项)+ 密码;用户名留空时服务端自动生成(只留邮箱也能登录); - 登录:输入框接受 用户名 或 邮箱 任一,密码校验; - 认领:身份凭证下拉项为 用户名 / 邮箱(导入模板中的手机号仅为可选项,填了也可作认领键); - 忘记密码:登录页不再提供自助找回,提示联系老师 → 老师于【账号管理 → 重置密码】一键生成新密码(
POST /api/users/{id}/pwd); - 批量导入模板示例已去手机列;.xlsx解析依赖openpyxl。 本文后续章节提到的"验证码登录 Tab、手机必填、注册字段手机"等均以本修订为准(视为不存在或已弱化)。
1. 总体架构与升级思路¶
1.1 现状与目标¶
| 维度 | 现状 | 升级后 |
|---|---|---|
| 身份 | 考生输入昵称进入(players 表,无密码) |
必须登录(账号体系),成绩自动归属账号 |
| 玩法 | 主持人逐题过题:顺序 / 随机 / 抢答,实时排行榜 | 保留上述"实时场";新增"整卷测验场"(整卷限时作答、交卷判分) |
| 记录 | answers 表只有 (player_id, qid, sel),无时间/场次,跨场累计无法统计 |
引入"答题场次 session",每次答题(每场)独立归档,可统计 |
| 题目 | questions 表(A/B/C/D 单选,无解析)+ qb_questions 题库(多题型、富文本、含解析 analysis 字段) |
题目统一收敛到 qb_questions,解析必填,按场次策略展示 |
| 管理端 | 4 个面板(模式/控制/题目/排行) | 页签化:现场控制 / 题库组卷 / 账号管理 / 用户管理 / 统计报告 |
1.2 答题"场次"概念(统计与记录的基础)¶
每一次答题 = 一个场次(session),两种产生方式:
- 实时场(live):沿用主持人逐题控制。主持人在现场控制区点新增按钮【结束本场并结算】时,把本轮所有作答与得分归档为一个 session(
players表计分清零,可开启下一场)。清空数据= 放弃本场、不归档(保留现状语义)。 - 整卷测验场(exam):主持人在题库页"组卷"后或从题库抽题发起一场测验(设置标题、时长、每题分值沿用组卷/默认),考生从大厅进入作答,可逐题保存、提前交卷或到时自动交卷,交卷即时结算归档。
两种场次落库结构完全一致,统计、用户记录、报表共用同一套查询。
1.3 页面与路由规划(★=新增,▲=改造)¶
用户端
| 页面 | 路由 | 说明 |
|---|---|---|
| 答题大厅 | / ▲ |
未登录显示登录卡(内嵌),已登录显示等待/答题界面与"我的记录"入口 |
| 登录 | /login ★(或首页内嵌卡片,二选一,推荐内嵌卡片 + 独立页双入口) |
密码登录 / 手机号验证码登录 两个 Tab |
| 注册 | /register ★ |
自助注册(管理端可关闭) |
| 账号认领 | /claim ★ |
认领管理员导入的账号 |
| 个人中心 | /me ★ |
答题历史列表 |
| 答题明细 | /me/sessions/{id} ★ |
逐题作答情况 + 解析回顾 |
| 整卷测验作答 | /exam/{sid} ★ |
考场页(计时、答题卡、交卷) |
管理端(/admin 页签化)
| 页签 | 说明 |
|---|---|
| 现场控制 | 现有实时控制 + 抢答 + 排行榜(玩家名=账号名)▲ |
| 题库与组卷 | 现有 /qbank(迁移入口保留,去掉右上角外链改为页签内嵌或新窗口)▲ |
| 账号管理 | 手工新增 / CSV·Excel 批量导入 / 模板下载 ★ |
| 用户管理 | 用户列表、认领状态、答题记录下钻 ★ |
| 统计报告 | 场次统计:整体指标、各题正确率、得分分布、导出 ★ |
API 前缀约定:认证 /api/auth/*、用户管理 /api/users/*、导入 /api/import/*、场次与记录 /api/sessions/*、统计 /api/stats/*。
1.4 技术决策(离线优先)¶
- 局域网环境可能无外网,页面图表不使用 CDN(ECharts 等),改用纯 CSS/SVG 自绘柱状图、分布图;
- 密码哈希用 Python 内置
hashlib.scrypt(或 PBKDF2-HMAC-SHA256),不新增依赖;会话用users.session_token+ HttpOnly Cookie; - Excel
.xlsx解析需新增依赖openpyxl(同步更新requirements.txt);.csv用内置csv(自动识别 UTF-8 / GBK,读 BOM); - 手机短信验证码在纯局域网内没有短信网关,落地为三级方案(见 5.1.2):邮件验证码(可选 SMTP 配置)→ 调试码通道 → 短信网关接口预留。
2. 数据模型设计(DDL 草案)¶
全部写入现有 quiz.db(qbank.py 的建表沿用)。
-- ============ 账号体系 ============
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL UNIQUE, -- 登录名:3-20位,任意字符开头,字母/数字/下划线/中文
display_name TEXT NOT NULL, -- 姓名(导入列"姓名",注册字段"姓名")
phone TEXT UNIQUE, -- 手机号,可空,唯一
email TEXT UNIQUE, -- 邮箱,可空,唯一
password_hash TEXT NOT NULL, -- scrypt(随机盐 + 密码)
role TEXT NOT NULL DEFAULT 'student', -- student / admin(预留)
status TEXT NOT NULL DEFAULT 'unclaimed', -- unclaimed待认领|normal正常|disabled停用|locked锁定
source TEXT NOT NULL DEFAULT 'import', -- import批量导入|self自助注册
batch_id INTEGER, -- 导入批次,见 import_batches
remark TEXT DEFAULT '', -- 备注/班级(导入可选列)
fail_count INTEGER DEFAULT 0, -- 连续认证失败次数
lock_until REAL, -- 锁定截止时间戳(0/空=未锁定)
claimed_at REAL, -- 认领时间(待认领→正常 的时刻)
last_login_at REAL,
created_at REAL NOT NULL,
updated_at REAL
);
CREATE INDEX IF NOT EXISTS idx_users_status ON users(status);
CREATE INDEX IF NOT EXISTS idx_users_batch ON users(batch_id);
CREATE TABLE IF NOT EXISTS import_batches (
id INTEGER PRIMARY KEY AUTOINCREMENT,
filename TEXT NOT NULL,
total_rows INTEGER NOT NULL, -- 文件总行数(不含表头)
ok_rows INTEGER NOT NULL, -- 成功导入
fail_rows INTEGER NOT NULL, -- 失败行数(明细可下载)
created_at REAL NOT NULL
);
-- ============ 答题场次与作答明细(统计/记录的数据源) ============
CREATE TABLE IF NOT EXISTS sessions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL, -- 场次名(live: "实时场 第N场 20xx-xx-xx 14:00")
mode TEXT NOT NULL, -- live_sequence|live_random|live_buzz|exam
paper_id INTEGER, -- exam 场关联组卷;live 场为空
quiz_plan TEXT, -- 题单快照 JSON: [{qn_id, qn_type, points, ord}]
duration INTEGER, -- exam:限时(秒);live:0
reveal_policy TEXT NOT NULL DEFAULT 'after_submit', -- 解析展示策略,见 4.2.2
pass_line REAL DEFAULT 60, -- 及格线(百分制),统计用,可配置
started_at REAL, -- 开考时间
ended_at REAL, -- 结束/结算时间
status TEXT NOT NULL DEFAULT 'open' -- open答题中|ended已结束|aborted作废
);
CREATE INDEX IF NOT EXISTS idx_sessions_status ON sessions(status);
CREATE TABLE IF NOT EXISTS session_users ( -- 场次-用户 汇总(一人一场一条)
id INTEGER PRIMARY KEY AUTOINCREMENT,
session_id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
score REAL DEFAULT 0, -- 本场得分(live=实时计分规则得分;exam=卷面得分)
max_score REAL DEFAULT 0, -- 满分(统计百分化用)
correct_cnt INTEGER DEFAULT 0, -- 答对题数
answered_cnt INTEGER DEFAULT 0, -- 已作答题数
used_secs INTEGER, -- 用时(exam=交卷-开考;live=入场到结算)
submit_at REAL, -- 交卷/结算时间
UNIQUE(session_id, user_id)
);
CREATE TABLE IF NOT EXISTS attempt_items ( -- 逐题作答明细(一题一条,两种场次统一)
id INTEGER PRIMARY KEY AUTOINCREMENT,
session_id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
qn_type TEXT NOT NULL DEFAULT 'qb', -- qb=新题库题 | legacy=旧表题(迁移过渡期)
qn_id INTEGER NOT NULL, -- 对应 qb_questions.id 或 questions.id
sel TEXT, -- 作答原文:single "A" / multi "A,C" / judge "T"/"F" / code 文本截断
is_correct INTEGER, -- 1对 0错 NULL未答(exam 到点未答的题写一行 is_correct=NULL)
points_got REAL DEFAULT 0, -- 本题得分
answered_at REAL, -- 作答时间戳
latency INTEGER -- 本题用时(毫秒;实时场=发题到作答)
);
CREATE INDEX IF NOT EXISTS idx_att_session ON attempt_items(session_id);
CREATE INDEX IF NOT EXISTS idx_att_user ON attempt_items(user_id);
CREATE INDEX IF NOT EXISTS idx_att_qn ON attempt_items(qn_type, qn_id);
升级改造(对现有表,幂等 ALTER)
ALTER TABLE players ADD COLUMN user_id INTEGER; -- 实时玩法玩家绑定账号;ALTER 需判列存在再执行
ALTER TABLE players ADD COLUMN display TEXT DEFAULT '';
-- players.name 保留为唯一标识的"参赛名",登录后自动以 user 维度 upsert(一个账号对应一条 player)
旧 questions 表处置(迁移方案,见确认清单 Q7):提供迁移脚本把旧 A/B/C/D 单选题导入 qb_questions(type='single',选项转 JSON,analysis='' 标记"缺解析"),此后实时场题目源也读 qb_questions;迁移完成前 attempt_items.qn_type 字段保证双源兼容。players.score 结算后清零,answers 表数据在首次结算历史场次时归入 attempt_items 后弃用。
3. 管理员端——账号管理(导入)¶
3.1 功能入口与界面¶
管理端【账号管理】页签,包含:手工新增单条表单、批量导入区(上传 + 下载模板)、导入历史列表(批次、时间、成功/失败数、下载失败明细)。
3.2 导入文件格式¶
支持格式:.csv(UTF-8 带/不带 BOM、GBK 自动识别,逗号/制表符分隔均可)、.xlsx(openpyxl 解析,取第一个工作表)。
表头:中文列名与英文列名均接受(列名不区分顺序,多余列忽略)。
| 列名(中文 / 英文) | 必填 | 规则 |
|---|---|---|
| 姓名 / name | ✅ | 2–30 字符 |
| 用户名 / username | 视规则 | 唯一;不填时自动生成:拼音_姓名不可靠 → 默认规则:s + 4 位随机,导入后可在用户列表改名;建议校内统一填学号/工号 |
| 手机号 / phone | 见下 | 11 位数字(^1\d{10}$),全库唯一;可空 |
| 邮箱 / email | 见下 | 标准邮箱格式,全库唯一;可空 |
| 初始密码 / password | 条件 | 6–20 位;留空则系统生成随机 8 位(含大小写字母+数字),导入完成后一次性展示,并提供"导出初始密码.csv"(含用户名、姓名、明文密码,仅管理员可下载一次) |
| 账号状态 / status | 否 | 可填:待认领(默认)/正常/停用;不填默认待认领 |
| 备注 / remark | 否 | 班级/部门等,任意文本 |
身份键规则:用户名、手机号、邮箱三者必须至少填一个且全局唯一;批量内不允许出现重复的(用户名 / 手机号 / 邮箱)。填了手机号或邮箱的账号将作为认领的匹配键。
3.3 示例文件(模板下载内容即此,含两行数据)¶
姓名,用户名,手机号,邮箱,初始密码,账号状态,备注
张三,20260001,13800138001,zhangsan@example.com,Init@2026,待认领,高一(3)班
李四,20260002,13800138002,,,正常,高一(3)班
王五,,,wangwu@example.com,,待认领,高一(4)班
- 张三:待认领,可凭 手机/邮箱+初始密码
Init@2026认领; - 李四:用户名+手机,状态正常且留空密码 → 系统生成随机密码,管理员导出后线下发给本人,可直接登录、无需认领;
- 王五:未填用户名 → 自动生成;只有邮箱 → 仅邮箱通道认领。
3.4 导入流程(两步确认式)¶
- 上传解析:选择文件 → 服务端逐行校验(格式、唯一性、与库内已有数据冲突)→ 返回预览:表格展示前 20 行 + 顶部统计(总行数 / 可导入 / 冲突行数)与错误清单(行号、列、原因,逐条列出);
- 确认导入:点击"确认导入 N 行"→ 落库(同一批次内重复用户名自动加后缀并列入提示)→ 完成页:成功 N / 失败 M,提供【下载失败明细.csv】(含行号与原因,修好后可直接再传)。
冲突判定:与库内已存在用户的冲突(用户名/手机/邮箱撞库)默认跳过该行(不覆盖),并提供选项"覆盖更新已存在账号的资料与状态"(密码字段留空则保持原密码不变),防止误导入造成账号覆盖。
4. 管理员端——题目管理(增删改查 + 解析)¶
4.1 现有基础与升级点¶
题库 CRUD、分类/标签、富文本题干(插图/表格/代码)、多题型(单选/多选/判断/编程/材料大题及小题)、组卷与打印已实现于 /qbank(数据在 qb_questions,其中 analysis 即解析字段)。升级点:
- 解析必填:新建/编辑保存时若解析为空 → 拒绝保存并提示(前端 + 后端双重校验)。历史缺解析题目列表标"缺解析"徽标,支持按"缺解析"筛选,批量补录完成后才可在正式场次中被抽中;
- 列表/搜索新增筛选条件:有无解析;
- 删改规则维持现有(删大题时小题转独立题、试卷引用位自动移除,二次确认)。
4.2 解析的展示方式(考生侧三时机 + 管理侧)¶
每道题解析由"场次级开关 reveal_policy"统管(创建场次时选择),实现层:/api/current、交卷响应、记录页接口按策略决定是否下发 analysis。四种策略:
| 策略 | 名称 | 适用 | 规则 |
|---|---|---|---|
instant |
答后立现 | 随堂练习/实时场 | 每题提交判分后,立即在本题卡片下方展开"答案解析"(对错 + 解析同屏) |
after_submit(默认) |
交卷后逐题回顾 | 整卷测验/考试 | 答题中不显示;交卷后跳"答题明细"页,逐题列表每行对错标记 + 可展开解析;个人记录页此后永久可见 |
after_end |
主持人统一公布 | 实时场/课堂讲解 | 答题与结算阶段仅显示对错;主持人在现场控制/统计页点【公布解析】后,全体可见(含个人记录),未公布前接口不下发 |
report_only |
仅报告可见 | 存档测试 | 考生全程看不到解析;解析只出现在管理端统计报告与教师卷打印中 |
补充规则: - 管理端不受限制:题库编辑、教师卷打印、统计报告始终展示解析; - 材料大题:解析展示按小题逐条展示(题干材料本身无解析); - 编程题:解析内容约定为"参考思路 + 关键代码"两段富文本,展示时与判分结果(通过/未通过)并列。
5. 管理员端——统计查看¶
5.1 入口与范围¶
【统计报告】页签。顶部场次选择器(下拉:全部 / 最近场次列表,含模式徽标与时间);选定后展示:整体指标卡片区、每题表现表、得分分布图、场次列表(多场对比)。所有图表 CSS/SVG 自绘,无外部依赖。
5.2 指标定义与计算方式(口径先行)¶
有效作答者 = 该场 attempt_items 中至少作答 1 题的去重 user_id 数(实时场即参加人数;整卷场含未交卷但已作答者)。
| 指标 | 计算公式(口径) | 展示形式 |
|---|---|---|
| 答题人数 | 有效作答者总数 | 顶部卡片(与"交卷人数"并列) |
| 交卷人数 | session_users.submit_at 非空 的行数(exam 场) |
顶部卡片 |
| 平均分 | Σscore ÷ 有效作答者数,保留 1 位小数 | 卡片 |
| 最高分 / 最低分 | 有效作答者中 score 的 max / min | 卡片(并列) |
| 得分分布 | 按百分化 score ÷ max_score × 100,分 10 档 [0-9,…,90-100],统计每档人数与占比;max_score=0 或非百分制场次则按实际分数组距(如满分 20 分按 1 分一档) |
SVG 柱状图 + 悬停显示人数/占比;下方附人数表 |
| 标准差 | √(Σ(x−x̄)² ÷ n) | 卡片(反映两极分化) |
| 及格率 | ≥ pass_line(默认 60 分,场次创建可改)人数 ÷ 有效作答者数 | 卡片 |
| 平均用时 | 交卷者 Σused_secs ÷ 交卷人数(exam);实时场不展示 | 卡片 |
| 总分/满分 | 卷面 Σpoints(exam);实时场展示 Σ每题 10 分制的总分 | 卡片副文本 |
各题表现表(核心行表):行=题目(序号、题干摘要、点击展开全文),列如下——
| 列 | 计算方式 | 展示形式 |
|---|---|---|
| 题型 | — | 徽标 |
| 作答率 | 本题作答人次 ÷ 有效作答者数 ×100%(未答不计入正确率分母) | 百分比 + 细条 |
| 正确率 | 本题答对人次 ÷ 本题作答人次 ×100% | 百分比 + 细条,≤60% 红、60–85% 黄、>85% 绿 |
| 选项分布 | 各选项被选人次及占比(判断:对/错;多选:按组合统计前 6;编程:通过/未通过/未答) | 堆叠细条(A/B/C/D 分色)+ 悬停明细 |
| 人均得分 | Σpoints_got ÷ 作答人次 | 数值 |
汇总行:全卷平均正确率、平均作答率。
5.3 核心 SQL(实现口径示例)¶
-- 单场指标(live 与 exam 统一,得分以 session_users 计)
SELECT COUNT(*) AS n, AVG(score) AS avg, MAX(score) AS hi, MIN(score) AS lo,
ROUND(AVG((score*100.0/NULLIF(max_score,0))),1) AS pct_avg
FROM session_users WHERE session_id = ?;
-- 各题表现(题目信息 join 题单快照 quiz_plan 或 qb_questions)
SELECT a.qn_id, COUNT(*) AS answered, SUM(a.is_correct) AS correct
FROM attempt_items a WHERE a.session_id = ?
GROUP BY a.qn_id;
5.4 展示补充¶
- 场次趋势:多场次平均分/及格率折线(场次选择器切"对比模式"),最多 30 场;
- 导出:本场报告导出 CSV(整体指标一行 + 每题一行);明细导出(每用户每题的答案与判分,管理端用户管理页也有入口);
- 仅管理端可见解析(无论策略),教师卷导出复用 qbank 现有打印。
6. 管理员端——用户管理¶
6.1 列表字段¶
| 列 | 说明 |
|---|---|
| 用户名 | 主键展示,可点击编辑 |
| 姓名 | 显示名 |
| 手机号 / 邮箱 | 脱敏展示(138****8001),认领前为空显示"—" |
| 账号状态 | 徽标:待认领(黄)/ 正常(绿)/ 停用(灰)/ 锁定(红,附剩余时间) |
| 来源 | 导入(批次号) / 自助注册 |
| 创建时间 / 最近登录 | 时间戳 |
| 累计答题次数 | session_users 按 user 计数(仅已结算场次) |
| 最近答题 | 最近一场的标题 + 得分,可点击下钻 |
| 操作 | 编辑 / 重置密码 / 停用·启用 / 导出该用户记录 |
6.2 筛选与搜索¶
- 筛选:状态(待认领/正常/停用/锁定)、来源、导入批次、是否答过题(是/否)、有无手机/邮箱(判断认领通道是否完备);
- 搜索:用户名 / 姓名 / 手机号 / 邮箱 任意片段模糊匹配,支持组合;
- 分页 20 条/页,可切换 50;当前筛选结果可一键导出 CSV。
6.3 账号认领状态与答题记录下钻¶
- "待认领"行显示醒目提示条与【催认领】操作(无短信能力,仅界面提示);
- 点击某用户行 → 右侧抽屉:
- 基本信息 + 认领时间/渠道(若已认领)+ 停用/锁定操作日志(简);
- 答题记录:按场次列出——场次标题/模式、时间、得分/满分、答对数/总题、用时、交卷时间;
- 点击任一场次 → 该用户逐题明细:题号、题干、他的答案 vs 正确答案、判分、得分、用时(解析可见)。管理端不依赖解析策略,始终可见。
7. 用户端——登录¶
7.1 登录方式(两个 Tab)¶
| Tab | 验证方式 | 说明 |
|---|---|---|
| 密码登录 | 用户名 + 密码 | 主通道;用户名支持大小写不敏感 |
| 验证码登录 | 手机号 + 短信验证码 | 点【发送验证码】→ 校验码 6 位、5 分钟有效、60 秒倒计时重发;手机号必须已绑定账号(未绑定提示:可去注册或认领) |
7.2 验证码通道的现实说明与实现(局域网约束)¶
纯局域网无短信网关,因此实现三级通道(服务端接口统一,便于日后替换):
1. 调试通道(默认启用):验证码写入服务端日志并在管理端"现场控制 → 验证码代收"小面板实时展示(局域网演示、教学场景直接用);
2. 邮件通道(可选配置):在 config 中填 SMTP 后自动启用,发到账号绑定邮箱;
3. 短信通道(预留):定义 sms_gateway.send(phone, code) 接口与配置占位,接网关即启用,无需改业务代码。
7.3 错误处理逻辑(含防爆破)¶
| 场景 | 行为 | 提示文案 |
|---|---|---|
| 用户名不存在 / 密码错误 | 统一文案(不泄露账号是否存在),fail_count+1 |
"用户名或密码错误" |
| 连续错误 5 次 | 账号锁定 5 分钟(lock_until),锁定期间即使密码正确也拒绝 |
"尝试次数过多,账号已锁定,请 X 分钟后再试"(前端倒计时) |
| 锁定中 | 拒绝登录 | 同上(显示剩余秒数) |
| 验证码错误 | fail_count+1,同一规则锁定;验证码 5 次错误作废需重发 |
"验证码错误,请重试" |
| 验证码过期 | 需重新发送 | "验证码已过期,请重新获取" |
账号停用 disabled |
直接拒绝 | "该账号已停用,请联系管理员" |
账号待认领 unclaimed |
拒绝直接进入,响应携带提示,前端自动弹出认领引导(跳转 /claim) |
"该账号为管理员导入,请先完成账号认领" |
| 会话过期 | 任何 API 返回 401,前端弹登录卡,完成后回跳原操作 | "登录已过期,请重新登录" |
成功:签发 session_token(写入 users.session_token,HttpOnly Cookie 30 天),跳转答题大厅;/api/me 返回用户名、姓名、状态。
8. 用户端——注册¶
8.1 开关与前提¶
自助注册由管理端【账号管理】开关控制(默认关闭,校内建议以批量导入+认领为主;开启后新注册账号直接可用,或可选"注册后待审核"模式)。
8.2 字段、校验规则与重复性检查¶
| 字段 | 必填 | 校验规则 | 重复性检查 |
|---|---|---|---|
| 用户名 | ✅ | 3–20 位;任意字符开头,仅字母/数字/下划线/中文;前端即时校验(输入变化即提示格式错误) | 失焦即 AJAX 查询 /api/auth/check?username=,被占用红字"该用户名已被注册",实时刷新;提交时服务端唯一索引兜底 |
| 姓名 | ✅ | 2–30 字符 | 不查重(可与他人同名) |
| 手机号 | ✅ | ^1\d{10}$ |
同用户名:失焦 AJAX 查重 + 服务端兜底,占用提示"该手机号已注册(可尝试直接登录或找回)" |
| 邮箱 | 选填 | 标准邮箱格式(正则 + 基本域名校验) | 同上 |
| 密码 | ✅ | 8–20 位,必须同时含字母与数字;不得与用户名相同;输入时强度条(弱/中/强) | — |
| 确认密码 | ✅ | 与密码一致(不一致即红字提示,不提交) | — |
注册页注明:注册后即登录;若你是老师下发的账号,请走"账号认领"而非注册。
提交语义:前端全部通过才可点提交;服务端逐项复验,失败返回字段级错误({field, msg}),不回滚已占用检查提示。成功 → 自动登录 → 大厅。
9. 用户端——账号认领¶
9.1 概念与触发入口¶
认领 = 管理员通过 CSV/Excel 导入的账号(初始状态 unclaimed)由"本人"确权激活。触发入口:① 登录页底部常驻"认领账号"链接(独立页 /claim);② 待认领账号尝试登录时自动引导。
9.2 认领流程(步骤明细)¶
| 步骤 | 表单内容 | 说明 |
|---|---|---|
| ① 填写身份 | 姓名(必填)+ 下面三种任选其一作为匹配键:用户名 / 手机号 / 邮箱 | 表单说明:"与老师导入信息一致" |
| ② 输入初始密码 | 初始密码(导入时下发或管理员导出) | 若选择"用户名"为匹配键,密码校验照常 |
| ③ 系统匹配 | 服务端按唯一键查账号 | 匹配规则:键命中 且 display_name 与"姓名"一致;命中多条(理论上不可能,唯一键约束)则拒绝并要求联系管理员 |
| ④ 设置新密码 | 新密码 + 确认 | 规则同注册密码(8–20 位含字母数字);成功后自动登录并跳大厅 |
| ⑤ 完成 | 状态 → normal,写入 claimed_at、last_login_at,session_token 轮换 |
页面提示"认领成功,欢迎 XXX" |
9.3 身份校验所需字段与安全设计¶
- 匹配键(三选一):
username/phone/email——来自管理员导入数据; - 双因子校验:唯一键命中 + 姓名一致(双重确认防同名误领)+ 初始密码正确(密钥);
- 安全:初始密码只能用于"登录→强制改密"或"认领改密",认领成功后作废;认领接口与登录共用
fail_count锁定策略;已登录用户不可认领(先退出)。
9.4 校验失败处理¶
| 场景 | 提示文案 | 附加行为 |
|---|---|---|
| 键未命中任何账号 | "未找到匹配的账号,请核对姓名与手机/邮箱/用户名,或联系管理员" | 无 |
| 键命中但姓名不一致 | "姓名与账号信息不匹配,请核对" | 无 |
| 初始密码错误 | "初始密码错误" | fail_count+1,5 次锁定 10 分钟 |
| 账号已被认领 | "该账号已被认领,请直接登录" | 提供登录跳转 |
| 账号停用 | "该账号已停用,请联系管理员" | 无 |
| 账号为自助注册(非导入) | "该账号非管理员导入账号,无需认领,直接登录即可" | 无 |
| 锁定中 | "尝试次数过多,请 X 分钟后再试" | 剩余时间展示 |
10. 用户端——答题记录(个人中心 /me)¶
10.1 记录列表字段¶
| 列 | 说明 |
|---|---|
| 场次标题 + 模式徽标 | 实时·顺序 / 实时·随机 / 实时·抢答 / 测验 |
| 答题时间 | 场次开始时间(实时场=入场时间附近;测验场=交卷时间) |
| 得分 / 满分 | 如 85 / 100(含各题分汇总);实时场换算为其规则总分 |
| 正确 / 总题数 | 12/20 |
| 用时 | 秒 → 自动格式 mm:ss |
| 状态 | 已交卷 / 自动交卷 / 已结算 / 进行中(占位,结算后完整) |
| 场次排名(实时场) | 结算时名次(同分按答对时长并列规则:先完成者靠前,或并列) |
| 操作 | 【查看明细】→ /me/sessions/{id} |
顶部汇总条:累计场次数、累计答题数、累计正确率、平均分。筛选:时间范围、模式、标题关键词。分页 10 条/页。
10.2 答题明细页字段(逐题回顾)¶
每题卡片:序号 + 题型徽标;题干全文(富文本渲染);我的答案(未答灰显"未作答")与正确答案对照,对错彩色标记;本题得分与满分;本题用时;解析(可见性规则:场次策略为 after_submit/已公布 after_end/instant → 显示;report_only 或未公布 → 隐藏并提示"解析暂未开放");多选显示组合答案、判断显示对/错、编程题显示提交片段(截断)+ 通过状态。
11. 升级对现有功能的改造点清单¶
index.html:昵称登录框移除 → 登录卡(含注册/认领入口);考生标识显示账号姓名;答题逻辑不变;main.py认证中间件:新增require_user依赖,/api/join、/api/submit、/api/buzz、/api/current(name)等从 Cookie 取账号,服务端按账号自动 upsertplayers(一账号一条),所有管理 API 维持现状(见确认清单 Q1);- 实时排行榜显示账号名 + 本场得分;
/api/admin/clear语义改为"放弃本场"(不清 users); /admin页签化整合:现有面板并入"现场控制",新增账号/用户/统计页签,/qbank作为题库子入口保留独立页;- 结算动作:现场控制新增【结束本场并结算】→ 生成
sessions+session_users+ 迁移answers明细到attempt_items→players.score清零; - 打印:教师卷/考生卷随解析策略(教师卷始终带解析)。
12. 分阶段实施计划(评审通过后按此排期)¶
| 阶段 | 内容 | 验证点 |
|---|---|---|
| M1 用户体系 | users/import_batches 表、密码哈希、会话 Cookie、注册/登录/退出 API + 页面、字段查重、锁定策略 |
冒烟脚本:注册→登录→错 5 次锁定→解锁 |
| M2 账号认领 | 认领页 + 后端匹配/改密/失败处理、待认领拦截登录 | 导入样例数据 → 认领成功/各失败分支 |
| M3 导入 | CSV/xlsx 解析、模板下载、两步确认、批次、失败明细、初始密码导出 | 三种表头样例 + 冲突/非法行用例 |
| M4 场次与记录 | sessions 系建表、结算按钮、exam 整卷作答页、/me 记录与明细、解析策略下发 |
实时场结算 + 测验场全流程 → 记录页核对 |
| M5 统计与用户管理 | 指标 SQL、报告页与 SVG 图、各题表现表、用户列表/筛选/下钻、CSV 导出 | 数据核对:手算 vs 页面指标 |
| M6 整合与回归 | admin 页签化、导航、旧数据迁移脚本、readme 更新 | 全链路回归 + 双模式并存验证 |
13. 待确认清单(请逐条勾选或修改)¶
- Q1 主持人后台
/admin是否需要登录/口令?(现状:局域网内任何人可开,添加"主持人口令"为可选加固) - Q2 注册开关默认值?(建议:默认关闭,导入为主)
- Q3 待认领账号初始密码若留空 → 自动生成并一次性导出,是否接受?(或要求管理员必填密码列)
- Q4 登录账号与实时玩法的"参赛名"关系:直接用账号姓名显示,还是允许每场另取昵称?(建议:直接用账号姓名,排行榜简洁)
- Q5 整卷测验场默认题目来源:从"组卷"发起 还是 从题库按分类/标签筛选抽题?满分=100 按组卷分自动归一?
- Q6 及格线默认 60 分(可场次级修改)?
- Q7 历史 A/B/C/D 单选(
questions表)是否同意迁移脚本并入qb_questions(迁移后解析为空需补录或批量标注"缺解析")? - Q8 验证码通道:默认调试通道(管理端代收面板)是否可接受?邮件通道是否需要一并做 SMTP 配置?
- Q9 实时场结算时机:以主持人点【结束本场并结算】为准(建议),还是每题公布后自动按整场归档?
- Q10 导入时发现与库内用户手机/邮箱冲突的行:默认跳过还是可选手动合并?
以上各条均有默认建议值,全部按默认执行也可;请在回复中标注例外项(如"Q2 改为开启")。