diff --git a/app/controller/exam.js b/app/controller/exam.js index aadc974..7b695a5 100644 --- a/app/controller/exam.js +++ b/app/controller/exam.js @@ -216,6 +216,20 @@ class ExamController extends Controller { return; } + // 🔴 已正式交卷禁止再次进入(考试组一次性答题,测试反馈 #3)。 + // 这里是「进入考试」的真实门控:题目经此下发,拒绝即等于无法进入答题界面, + // 前端按钮置灰只是 UX 层,绕过前端直调本接口同样被拦。 + // hasSubmitted 仅在 exam_submissions.submit_time 落库后为 true ⇒ + // 未交卷的意外退出(断网/断电)不受影响,仍可重进恢复作答。 + // 仅约束考试组;本科/教师走作品提交,不在此限制。 + if (group === 'zhongzhi' || group === 'gaozhi') { + const alreadySubmitted = await service.exam.hasSubmitted(aggregation.aggregation_uid, group); + if (alreadySubmitted) { + ctx.body = ctx.helper.error(4006, '您已提交试卷,无法再次进入考试'); + return; + } + } + // 团队锁:同一小组只能一人进入正式考试(原子检查+占位,G1 修复) const teamGate = await this._enterExamWithTeamLock(aggregation); if (!teamGate.allowed) { @@ -852,6 +866,19 @@ class ExamController extends Controller { } } + // 🔴 窗口内重复交卷拦截(考试组,测试反馈 #3)。 + // 背景:下方窗口内分支会重新校验请求体答案并覆盖写库 ⇒ 已交卷者若可重进, + // 即可「改答案 → 再交卷」无损刷分。入口 questions() 已拦重进,此处再堵住 + // 绕过前端直接重放 submit-all 的路径。 + // ⚠️ 只作用于窗口打开时;窗口关闭后走上方结算通道(那是补写标记,不是重新作答)。 + if (windowOpen && (group === 'zhongzhi' || group === 'gaozhi')) { + const alreadyDone = await service.exam.hasSubmitted(aggregation.aggregation_uid, group); + if (alreadyDone) { + ctx.body = ctx.helper.error(4006, '您已提交试卷,不能重复提交'); + return; + } + } + // 团队锁:同一小组只能一人进入正式考试(G1 修复,防绕过 questions 直接提交) // ⚠️ 结算通道跳过:一是窗口已关闭不应再产生 setTeamLock 写副作用, // 二是结算绝不能被队友残留锁拦下(否则又回到「答案在库却交不上」)。