JavaWeb课设实战:Filter+Session权限控制与文件上传避坑指南
简介这是一套基于JavaWeb技术栈开发的作业管理网站完整源码面向计算机、通信、人工智能等专业的本科生及课程设计指导教师用于支撑期末大作业、课程设计或毕业设计实践。系统采用JSPServletDruid连接池MySQL架构涵盖用户登录、作业发布、提交、批阅、成绩统计等核心教学管理功能代码经调试验证可直接运行。压缩包共54个文件含9个Java业务类、9个JSP页面如login.jsp、work.jsp、submit.jsp、7个CSS样式文件、5个JS脚本含jQuery 3.2.1、9个JAR依赖库及web.xml、druid.properties等关键配置整体7.73MB结构清晰、模块职责分明。已有237人学习下载资源附带README说明文档适合作为JavaWeb入门到进阶的典型项目范例帮助学习者理解MVC分层实现、前后端交互逻辑与数据库操作全流程。1. 为什么一个“只管交作业”的网站反而要花三周重写登录态和权限校验这不是一个炫技的后台管理系统而是一个真实压在学生肩上的课设交付压力某高校《Java Web程序设计》课程要求——两周内完成可运行、可演示、能答辩的作业管理网站。但现实是90%的学生卡在第三天登录成功后跳转到 work.jsp刷新页面就变回未登录老师账号能删学生作业学生账号却能点开 submit.jsp 提交后直接看到所有人的提交记录更玄学的是用 Chrome 能正常上传文件换 Firefox 就报400 Bad Request。这套源码之所以答辩拿了95分不是因为用了多少高大上技术而是把「学生真正在意的三个痛点」全打穿了登录不掉线、角色不越权、提交不丢数据。它适合两类人一类是刚学完 Servlet/JSP、连 Filter 都没写过几行的初学者需要一份能直接跑起来、改两行就能交差的参考另一类是已经搭过 Spring Boot 的进阶者想反向拆解传统 JavaWeb 的权限链路怎么靠纯 Filter Session JSTL 拉通。别被“课程设计”四个字骗了——它本质是一份带完整上下文的 JavaWeb 实战切片。2. 总体架构设计三层结构与四个核心模块的职责划分这套系统没有用 Spring MVC 或 Struts而是回归 JavaWeb 原生三层表现层JSP、控制层Servlet、数据访问层Druid JDBC。这种选择不是为了怀旧而是课程设计场景下的理性妥协Tomcat 8.5 自带 JSP 容器无需额外配置依赖Druid 连接池比 C3P0 更稳定且druid.properties里明文写的数据库参数学生调试时一眼就能改所有业务逻辑都收在com.gdes.web包下没有跨包调用避免初学者陷入类路径地狱。整个系统围绕「作业生命周期」拆出四个模块身份认证模块负责登录验证、Session 绑定、登出清理作业发布模块教师端创建/修改/删除作业含截止时间校验作业提交模块学生端上传文件、填写说明、查看历史提交作业归档模块按班级/课程/日期聚合数据供教师导出统计。提示所有模块交互不走 AJAX全部用同步表单提交。这不是技术倒退而是降低调试复杂度——当submit.jsp提交失败时你能在浏览器地址栏直接看到?errorupload_failed而不是在 F12 的 Network 里翻 17 个 XHR 请求。2.1 身份认证模块Filter 拦截 Session 标记 JSTL 控制视图登录态管理是整套系统的地基。它不用 Cookie 加密也不做 Token 解析而是用最朴素的方式用户登录成功后将User对象存入HttpSession并在header.jsp中用 JSTL 判断% taglib prefixc urihttp://java.sun.com/jsp/jstl/core % c:choose c:when test${empty sessionScope.user} a hreflogin.jsp登录/a /c:when c:otherwise span欢迎${sessionScope.user.name}/span a hreflogout.do退出/a /c:otherwise /c:choose关键在于LoginServlet的实现逻辑// LoginServlet.java protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String username request.getParameter(username); String password request.getParameter(password); // 1. 简单密码校验实际课设中应替换为 MD5 加盐 User user userDao.findByUsername(username); if (user ! null user.getPassword().equals(password)) { // 2. 强制清除旧 Session防止会话固定攻击 HttpSession oldSession request.getSession(false); if (oldSession ! null) oldSession.invalidate(); // 3. 创建新 Session 并绑定用户对象 HttpSession session request.getSession(true); session.setAttribute(user, user); session.setMaxInactiveInterval(1800); // 30 分钟无操作失效 // 4. 重定向到首页避免重复提交 response.sendRedirect(index.jsp); return; } request.setAttribute(error, 用户名或密码错误); request.getRequestDispatcher(login.jsp).forward(request, response); }这段代码背后有三层设计意图第一session.setMaxInactiveInterval(1800)不是随便写的数字而是匹配教务系统常见会话策略第二oldSession.invalidate()是血泪经验——某次测试发现学生用同一账号在两个浏览器登录第二个登录会踢掉第一个但第一个页面刷新后仍显示“已登录”直到手动清缓存第三重定向而非转发彻底切断表单重复提交链路。2.2 权限控制模块基于角色的 URL 拦截与页面级隐藏权限不是靠注解或 XML 配置而是用AuthFilter实现硬拦截。它不校验 RBAC 模型只做两件事检查 Session 是否存在、检查当前用户角色是否匹配请求路径。// AuthFilter.java public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; String path request.getServletPath(); // 白名单登录页、静态资源、登出接口 if (path.startsWith(/login.jsp) || path.startsWith(/css/) || path.startsWith(/js/) || path.startsWith(/images/) || path.equals(/logout.do)) { chain.doFilter(req, resp); return; } // 检查 Session HttpSession session request.getSession(false); if (session null || session.getAttribute(user) null) { response.sendRedirect(login.jsp?redirect URLEncoder.encode(path, UTF-8)); return; } // 角色路由/teacher/ 开头的路径只允许 teacher 访问 User user (User) session.getAttribute(user); if (path.startsWith(/teacher/) !teacher.equals(user.getRole())) { response.sendError(HttpServletResponse.SC_FORBIDDEN, 无权访问教师功能); return; } // 学生不能直接访问 issue.jsp发布作业页 if (path.equals(/issue.jsp) !teacher.equals(user.getRole())) { response.sendError(HttpServletResponse.SC_FORBIDDEN, 请使用教师账号登录); return; } chain.doFilter(req, resp); }这个 Filter 在web.xml中全局注册filter filter-nameAuthFilter/filter-name filter-classcom.gdes.web.filter.AuthFilter/filter-class /filter filter-mapping filter-nameAuthFilter/filter-name url-pattern/*/url-pattern /filter-mapping注意AuthFilter不处理/work.jsp这类通用页面的按钮级权限。按钮显隐交给 JSTL 在页面层控制比如在work.jsp中c:if test${sessionScope.user.role teacher} button onclicklocation.hrefissue.jsp发布新作业/button /c:if c:if test${sessionScope.user.role student} button onclicklocation.hrefsubmit.jsp?workId${work.id}提交作业/button /c:if这种「Filter 拦截 URL JSTL 控制 UI」的双保险比单靠前端隐藏按钮更可靠——毕竟学生自己改 HTML 也能绕过按钮隐藏但改不了 Filter 的 403 错误。2.3 数据访问层Druid 连接池与手写 DAO 的取舍逻辑druid.properties文件内容极简但每行都有讲究# druid.properties driverClassNamecom.mysql.cj.jdbc.Driver urljdbc:mysql://localhost:3306/homework_db?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue usernameroot password123456 initialSize5 maxActive20 minIdle5 maxWait60000 timeBetweenEvictionRunsMillis60000 minEvictableIdleTimeMillis300000 validationQuerySELECT 1 testWhileIdletrue testOnBorrowfalse testOnReturnfalse poolPreparedStatementstrue maxPoolPreparedStatementPerConnectionSize20 filtersstat,wall重点看三处serverTimezoneAsia/ShanghaiMySQL 8.0 必加否则java.sql.SQLException: The server time zone value XXX is unrecognizedallowPublicKeyRetrievaltrue解决 MySQL 8.0 默认禁用公钥检索导致的连接失败filtersstat,wall开启 Druid 内置监控/druid/index.html和防火墙防 SQL 注入课设答辩时打开监控页能直观展示连接池健康状态。DAO 层完全手写不引入 MyBatis。以WorkDao为例// WorkDao.java public class WorkDao { private static final String INSERT_SQL INSERT INTO t_work (title, description, deadline, teacher_id, create_time) VALUES (?, ?, ?, ?, ?); public int insert(Work work) throws SQLException { QueryRunner qr new QueryRunner(JdbcUtils.getDataSource()); Object[] params { work.getTitle(), work.getDescription(), work.getDeadline(), work.getTeacherId(), new Timestamp(System.currentTimeMillis()) }; return qr.update(INSERT_SQL, params); } public ListWork findAllByTeacher(int teacherId) throws SQLException { QueryRunner qr new QueryRunner(JdbcUtils.getDataSource()); String sql SELECT * FROM t_work WHERE teacher_id ? ORDER BY create_time DESC; return qr.query(sql, new BeanListHandler(Work.class), teacherId); } }这里用QueryRunner而非原始PreparedStatement是因为它自动处理资源释放避免学生忘记close()导致连接泄漏。JdbcUtils.getDataSource()返回的就是 Druid 的DataSource实例封装在工具类里// JdbcUtils.java public class JdbcUtils { private static DataSource dataSource; static { try { Properties props new Properties(); props.load(JdbcUtils.class.getClassLoader().getResourceAsStream(druid.properties)); dataSource DruidDataSourceFactory.createDataSource(props); } catch (Exception e) { throw new RuntimeException(初始化 Druid 连接池失败, e); } } public static DataSource getDataSource() { return dataSource; } }这种写法牺牲了 ORM 的便捷性但换来的是——学生能看清每一行 SQL 怎么拼、参数怎么绑、异常怎么抛。答辩时被问「如果数据库挂了你的 DAO 怎么体现」答「QueryRunner.update()抛SQLException上层 Servlet 捕获后跳转 error.jsp」比说「MyBatis 会自动重试」更有说服力。3. 核心功能实现从作业发布到文件提交的完整链路作业管理网站的核心价值不在界面美观而在「教师发得准、学生交得稳、数据看得清」。本节聚焦三个高频操作的真实实现细节教师发布作业时如何校验截止时间、学生提交作业时如何处理文件上传、归档页面如何避免 N1 查询。3.1 作业发布模块表单校验 服务端时间校验 数据库约束issue.jsp是教师发布作业的入口表单字段包括标题、描述、截止日期、附件可选。前端用 HTML5 原生校验form actionIssueWorkServlet methodpost input typetext nametitle required placeholder作业标题 maxlength100 textarea namedescription required placeholder作业要求/textarea input typedatetime-local namedeadline required input typefile nameattachment button typesubmit发布作业/button /form但仅靠前端校验远远不够。IssueWorkServlet做三重防护// IssueWorkServlet.java protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); String title request.getParameter(title); String description request.getParameter(description); String deadlineStr request.getParameter(deadline); // 1. 空值校验防御空字符串 if (StringUtils.isBlank(title) || StringUtils.isBlank(description) || StringUtils.isBlank(deadlineStr)) { request.setAttribute(error, 标题、描述、截止时间不能为空); request.getRequestDispatcher(issue.jsp).forward(request, response); return; } // 2. 时间格式校验ISO 8601 格式yyyy-MM-ddTHH:mm LocalDateTime deadline; try { deadline LocalDateTime.parse(deadlineStr, DateTimeFormatter.ISO_LOCAL_DATE_TIME); } catch (DateTimeParseException e) { request.setAttribute(error, 截止时间格式错误请选择有效日期时间); request.getRequestDispatcher(issue.jsp).forward(request, response); return; } // 3. 业务规则校验截止时间不能早于当前时间 LocalDateTime now LocalDateTime.now(); if (deadline.isBefore(now)) { request.setAttribute(error, 截止时间不能早于当前时间); request.getRequestDispatcher(issue.jsp).forward(request, response); return; } // 4. 构建 Work 对象并保存 Work work new Work(); work.setTitle(title); work.setDescription(description); work.setDeadline(deadline); work.setTeacherId(((User) request.getSession().getAttribute(user)).getId()); try { int result workDao.insert(work); if (result 0) { // 5. 附件处理见 3.2 节 handleAttachment(request, work.getId()); response.sendRedirect(index.jsp?msg作业发布成功); } else { request.setAttribute(error, 作业发布失败请重试); request.getRequestDispatcher(issue.jsp).forward(request, response); } } catch (SQLException e) { request.setAttribute(error, 数据库操作异常 e.getMessage()); request.getRequestDispatcher(issue.jsp).forward(request, response); } }这里的关键设计点是时间校验必须在服务端做且用LocalDateTime而非Date。Date类在时区处理上容易翻车而LocalDateTime.parse()明确指定格式配合DateTimeFormatter.ISO_LOCAL_DATE_TIME能精准捕获2024-05-20T14:30这类标准输入。更重要的是isBefore(now)校验堵死了「教师手输 2020-01-01」这类低级错误。3.2 作业提交模块Apache Commons FileUpload 的安全配置学生提交作业的核心是文件上传。本项目用commons-fileupload-1.5.jarlib目录下而非 Tomcat 自带的PartAPI原因有二一是Part在 Tomcat 8.5 下需额外配置MultipartConfig学生容易漏二是FileUpload的流式处理更可控能实时校验文件大小和类型。SubmitServlet的上传逻辑如下// SubmitServlet.java protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 1. 创建 DiskFileItemFactory设置临时目录和内存阈值 DiskFileItemFactory factory new DiskFileItemFactory(); factory.setSizeThreshold(1024 * 1024); // 1MB 内存缓冲 String tempPath request.getServletContext().getRealPath(/WEB-INF/temp); File tempDir new File(tempPath); if (!tempDir.exists()) tempDir.mkdirs(); factory.setRepository(tempDir); // 2. 创建 ServletFileUpload 实例设置总大小限制20MB ServletFileUpload upload new ServletFileUpload(factory); upload.setFileSizeMax(10 * 1024 * 1024); // 单文件 ≤10MB upload.setSizeMax(20 * 1024 * 1024); // 总请求 ≤20MB try { ListFileItem items upload.parseRequest(request); MapString, String params new HashMap(); FileItem attachmentItem null; // 3. 解析表单字段和文件项 for (FileItem item : items) { if (item.isFormField()) { params.put(item.getFieldName(), item.getString(UTF-8)); } else if (item.getFieldName().equals(attachment)) { attachmentItem item; } } // 4. 校验必填字段 String workIdStr params.get(workId); String content params.get(content); if (StringUtils.isBlank(workIdStr) || StringUtils.isBlank(content)) { throw new IllegalArgumentException(作业ID和提交内容不能为空); } // 5. 校验文件类型只允许 .pdf .doc .docx .txt if (attachmentItem ! null !attachmentItem.getName().isEmpty()) { String fileName attachmentItem.getName(); String ext FilenameUtils.getExtension(fileName).toLowerCase(); if (!Arrays.asList(pdf, doc, docx, txt).contains(ext)) { throw new IllegalArgumentException(不支持的文件类型 ext); } // 6. 生成唯一文件名防止覆盖 String uniqueName UUID.randomUUID().toString() . ext; String savePath request.getServletContext().getRealPath(/uploads/ uniqueName); File saveFile new File(savePath); File parentDir saveFile.getParentFile(); if (!parentDir.exists()) parentDir.mkdirs(); attachmentItem.write(saveFile); // 7. 保存到数据库含文件路径 Submission submission new Submission(); submission.setWorkId(Integer.parseInt(workIdStr)); submission.setStudentId(((User) request.getSession().getAttribute(user)).getId()); submission.setContent(content); submission.setFilePath(/uploads/ uniqueName); submission.setSubmitTime(new Timestamp(System.currentTimeMillis())); submissionDao.insert(submission); } response.sendRedirect(submit.jsp?workId workIdStr success1); } catch (FileSizeLimitExceededException e) { request.setAttribute(error, 文件大小超过限制单文件≤10MB); request.getRequestDispatcher(submit.jsp).forward(request, response); } catch (SizeLimitExceededException e) { request.setAttribute(error, 总上传大小超过限制≤20MB); request.getRequestDispatcher(submit.jsp).forward(request, response); } catch (Exception e) { request.setAttribute(error, 提交失败 e.getMessage()); request.getRequestDispatcher(submit.jsp).forward(request, response); } }这段代码的避坑点藏在注释里factory.setRepository(tempDir)必须显式指定临时目录否则默认用系统临时目录不同操作系统路径不一致FilenameUtils.getExtension()来自commons-io它比String.substring()更安全能正确处理archive.tar.gz这类多点扩展名UUID.randomUUID()生成唯一文件名彻底杜绝同名覆盖风险——某次测试发现两个学生同时交report.docx后交的直接覆盖前交的导致教师下载错文件。3.3 作业归档模块分页查询 手动关联 缓存优化gather.jsp是教师查看所有作业提交情况的汇总页。它不依赖任何分页插件而是用原生 SQLLIMIT和OFFSET实现-- 获取总记录数用于计算页数 SELECT COUNT(*) FROM t_submission s JOIN t_work w ON s.work_id w.id WHERE w.teacher_id ? AND s.submit_time ? -- 获取当前页数据 SELECT s.*, w.title as work_title, u.name as student_name FROM t_submission s JOIN t_work w ON s.work_id w.id JOIN t_user u ON s.student_id u.id WHERE w.teacher_id ? AND s.submit_time ? ORDER BY s.submit_time DESC LIMIT ? OFFSET ?对应的GatherServlet// GatherServlet.java protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { int currentPage Integer.parseInt(request.getParameter(page) ! null ? request.getParameter(page) : 1); int pageSize 10; int offset (currentPage - 1) * pageSize; User user (User) request.getSession().getAttribute(user); long startTime System.currentTimeMillis() - 30 * 24 * 60 * 60 * 1000L; // 近30天 try { // 1. 查询总条数 long totalCount gatherDao.countByTeacherAndTime(user.getId(), startTime); int totalPage (int) Math.ceil((double) totalCount / pageSize); // 2. 查询当前页数据 ListGatherItem items gatherDao.findByTeacherAndTime( user.getId(), startTime, pageSize, offset); request.setAttribute(items, items); request.setAttribute(currentPage, currentPage); request.setAttribute(totalPage, totalPage); request.setAttribute(totalCount, totalCount); request.getRequestDispatcher(gather.jsp).forward(request, response); } catch (SQLException e) { request.setAttribute(error, 数据加载失败 e.getMessage()); request.getRequestDispatcher(gather.jsp).forward(request, response); } }GatherItem是一个手动组装的 VO 类包含work_title、student_name等字段避免在 JSP 中写 N1 查询// GatherItem.java public class GatherItem { private int id; private String workTitle; // 作业标题 private String studentName; // 学生姓名 private String content; // 提交内容摘要截取前50字 private String filePath; // 文件路径 private Timestamp submitTime; // 提交时间 // getter/setter 省略 }在gather.jsp中用 JSTL 循环渲染c:forEach items${items} varitem tr td${item.workTitle}/td td${item.studentName}/td td${fn:substring(item.content, 0, 50)}${fn:length(item.content) 50 ? ... : }/td td c:if test${not empty item.filePath} a href${item.filePath} target_blank下载附件/a /c:if /td td${item.submitTime}/td /tr /c:forEach分页导航栏用纯 HTML 实现不依赖 JSdiv classpagination c:if test${currentPage 1} a href?page${currentPage - 1}laquo; 上一页/a /c:if span第 ${currentPage} 页共 ${totalPage} 页/span c:if test${currentPage totalPage} a href?page${currentPage 1}下一页 raquo;/a /c:if /div这种写法看似笨重但胜在稳定——学生改 CSS 样式不影响分页逻辑换浏览器也不用担心 JS 兼容性。答辩时被问「为什么不用 PageHelper」答「PageHelper 依赖 MyBatis而本项目用原生 JDBC保持技术栈纯粹」比强行套用更显思考深度。4. 常见问题排查五个让课设答辩翻车的高频陷阱这套源码虽经调试但学生在本地部署时仍极易踩坑。以下是我在指导某高校 12 个小组课设时统计出的最高频、最致命的五个问题。每个问题都按「现象 → 原因 → 解决」给出可立即执行的方案不讲原理只给解法。4.1 现象登录成功后跳转到index.jsp但页面右上角仍显示「登录」F12 查看 Network 发现index.jsp返回 200 但 Session 为空原因web.xml中session-config缺失或cookie被浏览器拦截。Tomcat 默认启用 Session Cookie但若浏览器隐私模式或禁用 CookieSession ID 无法传递。解决在web.xml中强制启用 Cookie 并设置路径session-config cookie-config http-onlytrue/http-only securefalse/secure !-- 本地开发用 HTTP -- path//path /cookie-config /session-config浏览器访问http://localhost:8080/yourapp/login.jsp不要用127.0.0.1确保域名一致清除浏览器所有localhost相关 Cookie 后重试。4.2 现象submit.jsp提交文件时request.getParameter(content)返回null但文件能正常上传原因FileItem解析顺序错误。当表单同时含普通字段和文件字段时upload.parseRequest(request)返回的ListFileItem中普通字段和文件字段混排若遍历顺序不对可能先处理了文件项再处理文本项导致paramsMap 未填充。解决严格按「先收集所有普通字段再单独处理文件项」的顺序写代码for (FileItem item : items) { if (item.isFormField()) { // ✅ 必须先处理所有 isFormField() 项 params.put(item.getFieldName(), item.getString(UTF-8)); } } // ❌ 禁止在此处 break 或 return for (FileItem item : items) { if (!item.isFormField() item.getFieldName().equals(attachment)) { // ✅ 再处理文件项 attachmentItem item; } }4.3 现象MySQL 连接报错The server time zone value XXX is unrecognized或插入时间字段全是0000-00-00 00:00:00原因MySQL 服务器时区与 Java 应用时区不一致且 JDBC URL 未显式声明serverTimezone。解决登录 MySQL 执行SELECT global.time_zone, session.time_zone;确认服务器时区如SYSTEM或08:00修改druid.properties中的url强制指定时区urljdbc:mysql://localhost:3306/homework_db?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue若服务器时区是SYSTEM在 MySQL 配置文件my.cnf中添加[mysqld] default-time-zone 08:004.4 现象gather.jsp页面空白查看 Tomcat 日志出现java.lang.ClassNotFoundException: org.apache.commons.fileupload.FileItem原因commons-fileupload-1.5.jar和commons-io-2.8.0.jar未放入WEB-INF/lib或版本不匹配fileupload 1.5必须配io 2.8.0低版本会报NoClassDefFoundError。解决确认lib目录下存在以下两个 JARcommons-fileupload-1.5.jar1.4MBcommons-io-2.8.0.jar320KB删除lib中所有commons-*的旧版本 JAR如commons-fileupload-1.3.jar重启 Tomcat观察日志是否还有ClassNotFoundException。4.5 现象work.jsp列表显示「暂无作业」但数据库t_work表中有数据且teacher_id字段值正确原因WorkDao.findAllByTeacher()方法中 SQL 的WHERE条件字段名写错比如写成teacherid少下划线或teacher_idd多字母导致查询返回空集合。解决打开WorkDao.java定位findAllByTeacher方法复制 SQL 语句到 MySQL 客户端执行替换?为真实teacher_id值如1确认是否返回数据若客户端能查到说明 Java 代码中QueryRunner.query()的参数顺序错了——检查new BeanListHandler(Work.class)后的参数是否与 SQL 中?位置一一对应最保险做法在findAllByTeacher方法开头加日志System.out.println(查询教师ID teacherId); // 确认传入值5. 答辩验收指南从环境准备到演示话术的全流程 checklist答辩不是代码秀而是需求闭环验证。评审老师最关心三件事能不能跑、有没有用、靠不靠谱。我带过的 23 个课设小组中95 分以上的共同点不是功能多炫而是验收环节零卡顿、问题回答有依据、演示过程有节奏。以下是我给学生定制的答辩前 48 小时 checklist按时间倒序排列每一步都对应一个答辩得分点。5.1 答辩前 2 小时环境快照与故障预案此时不该再改代码而是固化环境、预演故障。打开命令行依次执行# 1. 检查端口占用避免 Tomcat 启动失败 netstat -ano | findstr :8080 # 2. 清理 Tomcat 工作目录防止旧编译类干扰 del /q C:\apache-tomcat-8.5.94\work\Catalina\localhost\* # 3. 导出当前数据库生成 backup.sql答辩时若被问「数据丢了怎么办」可立刻展示 mysqldump -u root -p homework_db backup.sql # 4. 截图三张关键页面存桌面备用 # - http://localhost:8080/yourapp/druid/index.html Druid 监控页证明连接池健康 # - http://localhost:8080/yourapp/index.jsp 首页显示登录态 # - http://localhost:8080/yourapp/gather.jsp?page1 归档页证明数据可查注意druid/index.html默认账号密码是admin/admin答辩时打开此页指着「ActiveCount」「PoolingCount」指标说「连接池当前活跃连接 3 个空闲 2 个说明并发处理正常」比空讲「我用了 Druid」有力十倍。5.2 答辩前 30 分钟演示脚本与话术设计把 10 分钟演示拆成 4 个 2 分钟模块每个模块配一句「价值话术」避免陷入技术细节模块操作步骤价值话术身份可信用 teacher/123456 登录 → 点击「发布作业」→ 填写标题「JavaWeb 课设」、截止时间选明天 → 点击发布「系统强制校验截止时间不能早于当前从源头杜绝教师误操作」提交可靠切换 student/123456 登录 → 在「我的作业」列表找到刚发布的作业 → 点击「提交」→ 上传report.pdf→ 填写说明「已完成」→ 提交「文件上传限制 10MB 且只允许 PDF/DOC保障服务器安全与教学规范」数据可视教师账号登录 → 点击「作业归档」→ 查看近 30 天提交列表 → 点击「下载附件」→ 打开report.pdf「归档页按教师维度聚合数据支持一键下载满足教学管理刚需」权限可控学生账号尝试访问http://localhost:8080/yourapp/issue.jsp→ 触发 403 Forbidden「URL 级权限由 Filter 硬拦截非前端隐藏真正保障数据隔离」演示时禁止说「这个功能还没做完」「这部分是同学写的」。如果某个按钮没实现提前在header.jsp中注释掉对应c:if块让界面看起来就是「本就不该有」。5.3 答辩中高频问题应答模板评审老师的问题往往围绕「为什么」和「怎么改」。以下是 5 个必问问题的标准答案全部基于本项目真实设计Q1为什么不用 Spring BootA课程设计要求体现 JavaWeb 核心技术栈Spring Boot 的自动配置会掩盖 Servlet 生命周期、Filter 链、JSP 编译等底层机制。本项目用原生 Filter 实现权限控制用 JSTL 控制视图能让评审老师清晰看到每个技术点的落地。Q2文件上传怎么保证不被恶意文件攻击A三重防护第一FileUpload设置setFileSizeMax(10*1024*1024)限制单文件大小第二解析文件名后用FilenameUtils.getExtension()提取扩展名只允许.pdf/.doc/.docx/.txt第三本文还有配套的精品资源点击获取