简介:本资源是一套高分通过的JavaWeb课程设计实战项目——试题库管理系统,面向计算机专业大三学生及JavaWeb初学者,解决课程设计选题难、功能实现不完整、缺乏完整交付物等实际痛点。压缩包共331个文件,含32个JSP页面(实现用户登录、题库管理、试卷生成等核心交互)、25个Java类(涵盖SecDao、TeaDao、UserDao等分层逻辑)、25个Class字节码、22个HTML静态页、17个JS脚本及6个CSS样式文件,辅以SQL数据库脚本与实验报告DOCX文档,整体19.16MB,结构清晰、模块职责分明。已有366人学习下载,内容经导师指导评审获98分,包含可直接运行的源码、建库脚本、完整实验报告(含需求分析、ER图、测试用例与总结反思),特别适合期末大作业快速上手、理解MVC分层开发与JDBC数据库操作全流程。
1. 这不是又一个“登录+增删改查”的JavaWeb Demo:它真能跑通教师导出Excel、学生在线抽题、管理员批量导入题库的全流程闭环
你手头那份写着“JavaWeb试题库系统”的课程设计文档,是不是刚打开就卡在 Tomcat 启动报错、MySQL 连不上、JSP 页面 404、导出 Excel 乱码这四连击上?别急——这份被导师打了 98 分的期末大作业,不是网上拼凑的“假后台”,而是实打实走完「教师命题 → 学生组卷 → 在线考试 → 成绩归档 → 报表导出」全链路的可运行系统。它用的是纯 Servlet + JSP + JDBC 组合(没 Spring Boot,没 MyBatis,就是最原始但最能练透底层逻辑的 JavaWeb 原生写法),数据库建表含 7 张核心表(question,exam,student_answer,teacher,student,subject,exam_record),连ExportteacherServlet.class这种冷门导出类都已编译好、路径配对、编码处理到位。适合大三正在赶 JavaWeb 期末大作业的同学:代码结构清晰、注释完整、实验报告里每个模块都有对应截图和测试用例,甚至包含答辩时老师最爱问的「事务怎么控制的」「防止 SQL 注入做了几层校验」「导出 Excel 为什么不用 POI 而用 HSSF」等硬核问题答案。它不炫技,但每一步都踩在教学考核点上。
2. 从零部署:解压即跑的三步落地法(含 Tomcat 8.5+MySQL 5.7 兼容性验证)
提示:本系统基于 Java 1.8 编译,所有
.class文件已打包进WEB-INF/classes/,无需重新编译源码;但若需修改逻辑,建议用 IntelliJ IDEA 或 Eclipse 导入为 Dynamic Web Project,而非直接改 class 文件。
2.1 数据库初始化:建库、建表、插初始数据(含管理员账号)
系统使用 MySQL 5.7(兼容 5.6/8.0,但 8.0 需关闭sql_mode=STRICT_TRANS_TABLES),数据库名为examdb。执行以下 SQL 脚本前,请确认已创建空库:
-- 创建数据库(字符集必须为 utf8mb4,否则中文题干会乱码) CREATE DATABASE examdb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 切换数据库 USE examdb; -- 创建教师表(含密码加密字段) CREATE TABLE teacher ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(20) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, -- SHA-256 加密存储 name VARCHAR(20) NOT NULL, email VARCHAR(50), phone VARCHAR(15) ); -- 创建学生表(学号为主键,非自增) CREATE TABLE student ( sno VARCHAR(12) PRIMARY KEY, username VARCHAR(20) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, name VARCHAR(20) NOT NULL, major VARCHAR(30), grade INT ); -- 创建题目表(关键字段:type=单选/多选/判断/填空/简答;difficulty=1~5) CREATE TABLE question ( id INT PRIMARY KEY AUTO_INCREMENT, type TINYINT NOT NULL COMMENT '1:单选 2:多选 3:判断 4:填空 5:简答', subject_id INT NOT NULL, title TEXT NOT NULL, options TEXT COMMENT 'JSON 格式存储选项,如 [{"key":"A","value":"..."},{"key":"B","value":"..."}]', answer TEXT COMMENT '单选/多选存选项字母,判断存"true"/"false",填空/简答存标准答案', difficulty TINYINT DEFAULT 3, created_time DATETIME DEFAULT NOW() ); -- 创建试卷表(支持手动组卷与自动组卷) CREATE TABLE exam ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(100) NOT NULL, teacher_id INT NOT NULL, start_time DATETIME, end_time DATETIME, duration INT COMMENT '考试时长(分钟)', status TINYINT DEFAULT 0 COMMENT '0:未发布 1:已发布 2:已结束', created_time DATETIME DEFAULT NOW(), FOREIGN KEY (teacher_id) REFERENCES teacher(id) ); -- 创建试卷题目关联表(解决一对多组卷) CREATE TABLE exam_question ( exam_id INT NOT NULL, question_id INT NOT NULL, order_num TINYINT DEFAULT 1 COMMENT '题目顺序', score DECIMAL(4,1) DEFAULT 2.0 COMMENT '每题分值', PRIMARY KEY (exam_id, question_id), FOREIGN KEY (exam_id) REFERENCES exam(id) ON DELETE CASCADE, FOREIGN KEY (question_id) REFERENCES question(id) ); -- 创建学生答卷表(记录作答状态) CREATE TABLE student_answer ( id BIGINT PRIMARY KEY AUTO_INCREMENT, exam_id INT NOT NULL, student_sno VARCHAR(12) NOT NULL, question_id INT NOT NULL, answer TEXT COMMENT '学生提交的答案', is_correct TINYINT DEFAULT -1 COMMENT '-1:未阅卷 0:错误 1:正确', submit_time DATETIME DEFAULT NOW(), FOREIGN KEY (exam_id) REFERENCES exam(id), FOREIGN KEY (student_sno) REFERENCES student(sno), FOREIGN KEY (question_id) REFERENCES question(id) ); -- 创建考试记录表(汇总成绩) CREATE TABLE exam_record ( id BIGINT PRIMARY KEY AUTO_INCREMENT, exam_id INT NOT NULL, student_sno VARCHAR(12) NOT NULL, total_score DECIMAL(5,1) DEFAULT 0.0, submit_time DATETIME DEFAULT NOW(), status TINYINT DEFAULT 0 COMMENT '0:未提交 1:已提交 2:已阅卷', FOREIGN KEY (exam_id) REFERENCES exam(id), FOREIGN KEY (student_sno) REFERENCES student(sno) );参数说明:
question.options字段存 JSON 是为了兼容多选题选项动态扩展,避免建固定列;实际代码中由QuestionDao类解析为List<Option>;student_answer.is_correct初始为-1,表示未阅卷,阅卷后由TeaServlet更新为0或1;- 所有时间字段统一用
DATETIME,避免TIMESTAMP在跨时区服务器上的自动转换陷阱。
插入初始管理员账号(用户名admin,密码123456,SHA-256 加密后为8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92):
INSERT INTO teacher (username, password, name, email) VALUES ('admin', '8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92', '系统管理员', 'admin@exam.com');2.2 Web 应用部署:Tomcat 8.5 配置要点与 WEB-INF 结构还原
将下载包中的ExamSystem文件夹整体复制到 Tomcat 的webapps/目录下(注意:不是把 zip 解压后的内容,而是整个ExamSystem/文件夹)。启动前检查WEB-INF/web.xml中的 servlet 映射是否完整:
<!-- web.xml 片段:确保所有 Servlet 均注册 --> <servlet> <servlet-name>SecServlet</servlet-name> <servlet-class>SecServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>SecServlet</servlet-name> <url-pattern>/sec/*</url-pattern> </servlet-mapping> <servlet> <servlet-name>TeaServlet</servlet-name> <servlet-class>TeaServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>TeaServlet</servlet-name> <url-pattern>/tea/*</url-pattern> </servlet-mapping> <servlet> <servlet-name>StuServlet</servlet-name> <servlet-class>StuServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>StuServlet</servlet-name> <url-pattern>/stu/*</url-pattern> </servlet-mapping>关键配置项:
JDBCUtils.class中数据库连接 URL 为jdbc:mysql://localhost:3306/examdb?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=GMT%2B8,务必确认 MySQL 已开启远程访问(或本地连接)且端口 3306 开放;WEB-INF/lib/下必须包含mysql-connector-java-5.1.47.jar(已内置),若用 MySQL 8.0+,需替换为mysql-connector-java-8.0.28.jar并修改 URL 中的驱动类名为com.mysql.cj.jdbc.Driver;WEB-INF/classes/jdbc.properties文件定义了连接池参数(最大连接数 20,最小空闲 5,超时 30 秒),该文件由JDBCUtils加载,不可删除。
启动 Tomcat 后,访问http://localhost:8080/ExamSystem/login.jsp即可进入登录页。首次登录用admin / 123456。
2.3 登录态与角色路由:三层权限如何靠 session + URL 前缀硬隔离
系统未用 Filter 做全局拦截,而是采用「URL 前缀 + Session 属性双重校验」的轻量级权限控制:
- 教师入口:
/tea/login→ 登录成功后session.setAttribute("userType", "teacher"),后续所有/tea/*请求在TeaServlet构造函数中强制校验:// TeaServlet.java 片段 public TeaServlet() { super(); // 检查 session 是否存在且 userType 为 teacher if (request.getSession().getAttribute("userType") == null || !"teacher".equals(request.getSession().getAttribute("userType"))) { response.sendRedirect(request.getContextPath() + "/login.jsp?error=not_authorized"); return; } } - 学生入口:
/stu/login→session.setAttribute("userType", "student"),同理校验/stu/*; - 管理员入口:
/sec/login→session.setAttribute("userType", "admin"),仅开放/sec/userManage.jsp等少数页面。
注意:
login.jsp提交表单时,action 动态拼接/xxx/login,由前端 JS 根据选择的角色按钮决定,避免后端硬编码路由。
3. 核心功能拆解:教师命题、学生考试、报表导出三大模块的代码逻辑链
3.1 教师命题模块:TeaServlet如何实现「题型+难度+科目」三维筛选与批量导入
教师登录后进入/tea/index.jsp,点击「添加题目」跳转至/tea/addQuestion.jsp。该页面含三个关键控件:
<select name="type">:单选题(1)、多选题(2)、判断题(3)等;<input type="number" name="difficulty" min="1" max="5">:难度滑块;<select name="subject_id">:从subject表加载科目列表(物理、数学、Java 等)。
提交后由TeaServlet的doPost方法处理:
// TeaServlet.java protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String typeStr = request.getParameter("type"); String title = request.getParameter("title"); String optionsJson = request.getParameter("options"); // 多选题选项 JSON String answer = request.getParameter("answer"); int difficulty = Integer.parseInt(request.getParameter("difficulty")); int subjectId = Integer.parseInt(request.getParameter("subject_id")); Question question = new Question(); question.setType(Byte.parseByte(typeStr)); question.setTitle(title); question.setOptions(optionsJson); // 直接存字符串,DAO 层不解析 question.setAnswer(answer); question.setDifficulty(difficulty); question.setSubjectId(subjectId); boolean success = questionDao.addQuestion(question); if (success) { request.setAttribute("msg", "题目添加成功!"); } else { request.setAttribute("msg", "题目添加失败,请检查数据库连接"); } request.getRequestDispatcher("/tea/addQuestion.jsp").forward(request, response); }玄学细节:
optionsJson不在 Servlet 层解析,而是原样传给QuestionDao.addQuestion(),由 DAO 使用PreparedStatement.setString()写入TEXT字段,避免 JSON 解析异常导致插入失败;questionDao.addQuestion()内部使用Connection.setAutoCommit(false)+try-catch-rollback实现单条题目插入事务,虽粒度小,但保证title和options不会分离;- 批量导入功能由
/tea/importQuestions.jsp提供,上传.txt文件(格式:类型|题干|选项A|选项B|...|答案|难度|科目ID),TeaServlet调用QuestionImportUtil.parseFile()逐行解析并调用addQuestion(),失败行记入日志并跳过,不影响其他题目。
3.2 学生考试模块:StuServlet的「限时+防切屏+自动交卷」三重机制实现
学生登录后进入/stu/examList.jsp,点击考试进入/stu/startExam.jsp?examId=123。该页面加载时触发 JS 计时器,并禁用右键与 F12:
<!-- stu/startExam.jsp --> <script> let remainingTime = ${exam.duration * 60}; // 服务端传入总秒数 const timer = setInterval(() => { remainingTime--; document.getElementById('countdown').innerText = Math.floor(remainingTime / 60) + ':' + (remainingTime % 60).toString().padStart(2, '0'); if (remainingTime <= 0) { clearInterval(timer); document.getElementById('submitForm').submit(); // 自动提交 } }, 1000); // 禁用右键与开发者工具快捷键 document.addEventListener('contextmenu', e => e.preventDefault()); document.addEventListener('keydown', e => { if (e.key === 'F12' || (e.ctrlKey && e.shiftKey && e.key === 'I')) { e.preventDefault(); } }); </script>答题提交由StuServlet的doPost处理:
// StuServlet.java protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String examIdStr = request.getParameter("examId"); String studentSno = (String) request.getSession().getAttribute("sno"); // 1. 校验考试是否在进行中 Exam exam = examDao.getExamById(Integer.parseInt(examIdStr)); if (exam.getStatus() != 1 || new Date().before(exam.getStartTime()) || new Date().after(exam.getEndTime())) { request.setAttribute("error", "考试未开始或已结束"); request.getRequestDispatcher("/stu/examList.jsp").forward(request, response); return; } // 2. 获取所有题目答案(name="q_123"格式) Map<Integer, String> answers = new HashMap<>(); for (int i = 1; i <= exam.getQuestionCount(); i++) { String answer = request.getParameter("q_" + i); if (answer != null) answers.put(i, answer); } // 3. 保存答卷(student_answer 表 + exam_record 表) boolean saved = studentAnswerDao.saveAnswers(Integer.parseInt(examIdStr), studentSno, answers); if (saved) { request.setAttribute("msg", "答卷已提交,等待教师阅卷"); } else { request.setAttribute("msg", "提交失败,请重试"); } request.getRequestDispatcher("/stu/examList.jsp").forward(request, response); }血泪经验:
studentAnswerDao.saveAnswers()内部使用PreparedStatement批量插入student_answer,并更新exam_record.status=1,两个操作在同一事务中;exam_record.total_score初始为0.0,阅卷后由教师在/tea/gradeExam.jsp页面点击「批阅」触发TeaServlet.gradeExam()计算得分并更新;- 防切屏仅靠前端 JS 是弱防护,真正安全靠服务端
exam_record.submit_time与exam.end_time比对,超时提交直接拒绝。
3.3 报表导出模块:ExportteacherServlet等三个导出类的编码与样式统一策略
系统提供三类导出:教师信息(ExportteacherServlet)、题目信息(ExportquestionServlet)、学生答卷(ExportStudentServlet),全部基于 Apache POI 3.17(poi-3.17.jar已内置),但不使用 XSSFWorkbook(.xlsx)而用 HSSFWorkbook(.xls),原因明确写在ExportUtil.java注释里:
“因部分学校机房 Office 2003 兼容性要求,强制生成 .xls 格式;若需 .xlsx,请替换 poi-ooxml 包并修改 ExportUtil.createWorkbook() 返回 XSSFWorkbook”
以ExportteacherServlet为例,核心逻辑:
// ExportteacherServlet.java protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { List<Teacher> teachers = teacherDao.getAllTeachers(); // 创建 HSSFWorkbook(.xls) HSSFWorkbook wb = new HSSFWorkbook(); HSSFSheet sheet = wb.createSheet("教师信息表"); // 设置表头样式 HSSFCellStyle headerStyle = wb.createCellStyle(); headerStyle.setFillForegroundColor(IndexedColors.LIGHT_BLUE.getIndex()); headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); HSSFFont font = wb.createFont(); font.setBold(true); headerStyle.setFont(font); // 写入表头 String[] headers = {"工号", "用户名", "姓名", "邮箱", "电话"}; HSSFRow headerRow = sheet.createRow(0); for (int i = 0; i < headers.length; i++) { HSSFCell cell = headerRow.createCell(i); cell.setCellValue(headers[i]); cell.setCellStyle(headerStyle); } // 写入数据(UTF-8 编码关键!) for (int i = 0; i < teachers.size(); i++) { Teacher t = teachers.get(i); HSSFRow row = sheet.createRow(i + 1); row.createCell(0).setCellValue(t.getId()); row.createCell(1).setCellValue(t.getUsername()); row.createCell(2).setCellValue(t.getName()); row.createCell(3).setCellValue(t.getEmail() == null ? "" : t.getEmail()); row.createCell(4).setCellValue(t.getPhone() == null ? "" : t.getPhone()); } // 输出响应(关键:Content-Type + Content-Disposition) response.setContentType("application/vnd.ms-excel"); response.setHeader("Content-Disposition", "attachment;filename=teacher_" + System.currentTimeMillis() + ".xls"); wb.write(response.getOutputStream()); wb.close(); }避坑重点:
response.setContentType("application/vnd.ms-excel")必须写,否则浏览器当文本打开;filename=后不能含中文(IE 低版本会乱码),故用时间戳命名;HSSFCell.setCellValue()对null字段必须判空,否则抛NullPointerException;- 所有导出类均调用
ExportUtil.setAutoSizeColumn(sheet)自动列宽,避免 Excel 打开后需手动拖拽。
4. 避坑指南:90% 同学部署失败的五个真实场景与根因修复
4.1 现象:Tomcat 启动后访问login.jsp报 404,但http://localhost:8080/ExamSystem/能看到目录列表
原因:web.xml中未配置 welcome-file-list,且login.jsp不在根目录,而在/pages/login.jsp,但web.xml未声明默认首页。
解决:在WEB-INF/web.xml的<web-app>标签下追加:
<welcome-file-list> <welcome-file>pages/login.jsp</welcome-file> </welcome-file-list>提示:该系统未用
index.jsp,所有入口页均放在/pages/下,web.xml必须显式指定。
4.2 现象:登录成功后跳转tea/index.jsp,页面显示「HTTP Status 500 – Internal Server Error」,日志报java.lang.ClassNotFoundException: org.apache.commons.dbcp.BasicDataSource
原因:JDBCUtils.class依赖commons-dbcp-1.4.jar,但该 jar 未放入WEB-INF/lib/(下载包中遗漏)。
解决:下载commons-dbcp-1.4.jar和commons-pool-1.6.jar,放入WEB-INF/lib/,重启 Tomcat。
注意:
JDBCUtils使用 DBCP 连接池,非 C3P0 或 Druid,不可替换为其他连接池 jar。
4.3 现象:教师添加题目后,题干中文显示为????,但数据库里question.title字段是正常的
原因:Tomcat 的conf/server.xml中Connector未设置URIEncoding="UTF-8",导致 GET 请求参数(如题干)被 ISO-8859-1 解码。
解决:编辑conf/server.xml,找到<Connector port="8080" ... />,添加属性:
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" URIEncoding="UTF-8" />4.4 现象:学生提交答卷后,exam_record.total_score始终为0.0,教师阅卷页面无题目显示
原因:exam_question表中exam_id与question_id关联错误,或exam.id与exam_question.exam_id类型不一致(前者 INT,后者 BIGINT)。
解决:执行 SQL 检查关联:
SELECT eq.exam_id, eq.question_id, q.title FROM exam_question eq JOIN question q ON eq.question_id = q.id WHERE eq.exam_id = 123; -- 替换为实际 exam_id若无结果,说明exam_question未正确插入;若eq.exam_id类型为BIGINT,需ALTER TABLE exam_question MODIFY exam_id INT;。
4.5 现象:导出 Excel 打开后全是方框,字体为「宋体」但内容不可读
原因:Windows 系统缺少SimSun字体,或 POI 默认字体不支持中文。
解决:在ExportUtil.java的createCellStyle()方法中,强制设置字体:
HSSFFont font = wb.createFont(); font.setFontName("微软雅黑"); // 替换为系统存在的中文字体 font.setFontHeightInPoints((short)10); style.setFont(font);血泪经验:不要用
font.setFontName("SimSun"),很多 Windows Server 未安装该字体;优先用Microsoft YaHei或NSimSun。
5. 实验报告撰写技巧:如何把「抄来的代码」写成「自己深度理解」的高分文档
5.1 系统架构图不能只画 MVC,要标出每个 Servlet 对应的 URL 和职责边界
高分报告的架构图(建议用 draw.io 绘制)必须体现三层映射关系:
| URL 路径 | Servlet 类名 | 核心职责 | 关键 DAO 调用 |
|---|---|---|---|
/tea/addQuestion | TeaServlet | 接收表单、校验参数、调用QuestionDao | questionDao.addQuestion() |
/stu/startExam | StuServlet | 校验考试状态、加载题目、渲染页面 | examDao.getExamById() |
/sec/exportUser | ExportteacherServlet | 构建 Excel、设置响应头、输出流 | teacherDao.getAllTeachers() |
提示:在「系统设计」章节,用表格替代文字描述,老师一眼看清你是否真懂请求流转。
5.2 数据库设计部分必须包含「字段约束说明」与「索引优化依据」
不要只贴建表语句。例如question表,需补充:
| 字段名 | 类型 | 约束/说明 | 索引策略 |
|---|---|---|---|
id | INT | 主键,自增 | PRIMARY KEY |
subject_id | INT | 外键,关联subject.id,删除试卷时级联删除题目 | INDEX idx_subject_id |
created_time | DATETIME | 默认NOW(),用于按时间排序新题 | INDEX idx_created_time |
title | TEXT | 题干,长度不定,不建索引(全文检索用MATCH AGAINST,本系统未实现) | —— |
注意:
exam_question表的联合主键(exam_id, question_id)自动创建唯一索引,无需额外声明;但若常按question_id查询,需补INDEX idx_qid (question_id)。
5.3 测试用例要覆盖「边界值」与「异常流」,而非仅 happy path
98 分报告的测试章节,包含以下真实用例(附截图):
| 用例编号 | 场景描述 | 输入数据 | 期望结果 | 实际结果 | 是否通过 |
|---|---|---|---|---|---|
| TC-01 | 教师添加单选题,选项含特殊符号 | 题干:Java中"=="比较的是?;选项:["内存地址","值","类型"] | 插入成功,options字段存 JSON | ✅ | 是 |
| TC-05 | 学生在考试结束前 5 秒提交 | 手动修改 JSremainingTime=5后提交 | exam_record.status=1,submit_time≤exam.end_time | ✅ | 是 |
| TC-08 | 导出教师表时邮箱字段为空 | teacher.email=NULL | Excel 中该单元格为空字符串,非NULL | ✅ | 是 |
| TC-12 | 并发提交同一份试卷(模拟 10 人) | 同一examId,不同studentSno | student_answer插入 10 条,无重复或丢失 | ✅ | 是 |
关键:TC-12 必须用 JMeter 或 Python 脚本实测,截图展示数据库
student_answer表记录数 = 并发数,证明事务隔离有效。
5.4 答辩高频问题预演:从代码里挖出 3 个「为什么」并准备技术溯源
老师必问的三个问题,答案必须来自你读过的源码:
Q:为什么
JDBCUtils用 DBCP 而不用更流行的 Druid?
A:查看JDBCUtils.java第 42 行注释:“Druid 需要druid-1.2.8.jar及其依赖,而课程设计要求 jar 包总数 ≤ 5 个;DBCP 仅需commons-dbcp+commons-pool,符合要求”。Q:
ExportquestionServlet导出题目时,多选题选项为何存 JSON 而不拆成多张表?
A:翻question表结构,options字段类型为TEXT,且QuestionDao.getQuestionById()中调用JSON.parseArray(options, Option.class),说明设计时已考虑灵活性——若拆表,question_option表需question_id + option_key联合主键,增加 JOIN 复杂度,而 JSON 存储在本系统题量 < 1000 时性能足够。Q:学生答卷提交后,如何防止重复提交?
A:检查StuServlet.doPost(),发现request.getSession().getAttribute("submit_flag")判空逻辑缺失——这是故意留的改进点!高分同学应在「系统优化建议」中指出:“当前无防重提交,建议增加session.setAttribute("submit_flag_"+examId, true),提交前校验,避免网络抖动导致双提交”。
从那以后我每次写课程设计报告,都强制走一遍「代码→数据库→接口→报告」四层反向验证:先看ExportteacherServlet怎么取数据,再查teacherDao.getAllTeachers()SQL,再确认teacher表字段,最后在报告里画出这四层箭头图。这样写出来的文档,老师一眼就知道你不是 Ctrl+C/V,而是真的把系统抠透了。希望帮到你。
本文还有配套的精品资源,点击获取