
关于毕业设计很多同学一上来就问选什么题目但真正的问题其实是选一个什么技术栈的题目。JSP学生疫情信息管理系统这算是我见过最典型的毕设题目之一了。它有明确的管理系统特征有用户角色划分有数据流转能展示增删改查基本功又能挂上统计图表这类加分项。更重要的是它完全可以用JSPServletMySQL这套老三样实现不需要你临时去啃Spring Boot和Vue门槛适中工作量可控非常适合用来完成本科毕设。这篇文章不打算给你推倒重来的代码也不会列一长串第一步干嘛、第二步干嘛那种假大空的清单。我想以一个带过多年毕设、也亲自手写过这类系统的视角把这个题目从选题逻辑、数据库设计、代码分层到答辩准备的全过程拆开讲一遍。你会知道哪些地方值得认真做哪些地方是大多数人的通病以及那些只有在实际动手时才会踩到的坑。1. 为什么JSP学生疫情信息管理这个题目经久不衰选题逻辑与技术栈分析1.1 核心需求拆解这个系统到底要管什么先别急着想我要不要用别的框架。毕业设计的第一件事是把题目的业务需求拆清楚。学生疫情信息管理系统看起来是个管理系统但它的数据模型和普通的学生管理系统、图书管理系统有明显区别。普通的CRUD系统重点在增删改查而疫情信息管理系统的核心其实是两个词收集和核查。学生的健康信息是每天或每周产生的它不是一次性的静态数据而是一条持续更新的时间序列。比如体温记录、所在地、行程轨迹、健康码状态这些信息在不同时间点会产生不同值。所以设计数据库时如果沿用用户表健康表这种一锤子模式后面做统计和按日期查询时会极其痛苦。我一般建议把该系统拆成四个业务域基础信息域学生档案、院系班级、辅导员信息。这是管理系统的地基。健康上报域学生的健康打卡、体温记录、位置信息、健康状态。这对应一张高频写入的表必须以日期和学号做联合约束。行程与外出登记域离校申请、返校申请、行程轨迹登记对应审批流程。统计与预警域按班级、院系维度的未上报统计、异常体温筛选、全校上报趋势图。很多同学做毕设时容易把能登录、能增删改查当成全部结果系统做出来功能点是不少但数据之间是孤立的没有一条主线把信息串起来。如果你在开题阶段就把上面这四个域理清楚后面无论是写代码还是画ER图思路都会清晰很多。1.2 JSP技术栈的过时与不过时每次带毕设我都会被问同一个问题老师JSP是不是太老了我是不是该用Spring Boot我的回答通常分两方面。如果你有半年以上的Java Web基础用Spring Boot做毕设当然更时髦简历上也更好看。但问题是很多同学的实际情况是Java基础刚过考试线Maven都还没玩熟Spring Boot的自动配置和依赖管理反而成为拦路虎。JSPServletJavaBean这套老组合虽然不够现代但它把Web开发的底层逻辑暴露得清清楚楚——HTTP请求怎么进来、Servlet如何匹配、请求如何转发到JSP页面、JDBC如何操作数据库。把这一套走通你对Java Web的理解反而是扎实的。再从毕设验收的角度说学生疫情信息管理系统的核心价值在业务逻辑和数据组织不在技术架构。JSP页面可以直接内嵌Java代码虽然不推荐写起来直观改起来也快特别适合需要快速出活的毕业设计节奏。而且JSP天然支持JavaBean用EL表达式和JSTL标签就能把页面展示做得干净整洁和Servlet配合非常顺手。所以我的结论很直接如果你的目标是顺利完成毕设并真正理解一个Web系统的运转过程JSPServletMySQL完全够用而且是一条稳妥的路线。2. 从数据表设计到用户权限先把系统骨架搭扎实2.1 分表逻辑推演哪些表必须拆哪些字段必须冗余很多同学建表时的习惯是想到哪儿建到哪儿这是大忌。对于学生疫情信息管理系统我建议一开始就规划好6张核心表sys_user用户表存放用户ID、账号、密码MD5加密存储、姓名、角色类型学生/辅导员/管理员、关联学号或工号。t_student学生信息表学号、姓名、性别、院系ID、班级ID、联系电话、家庭所在地。用户表和学生表分开是为了以后扩展其他角色时不必改动学生结构。t_department、t_class院系表和班级表保持两级组织结构方便做按院系和班级的下钻统计。t_health_report健康上报表主键ID、学号、上报日期、体温、当前所在地、健康码状态、行程码截图路径、上报时间。这里一定要加一个联合唯一索引(student_id, report_date)保证一个学生一天只能上报一条。t_travel_record行程登记表主键、学号、出发地、目的地、出发日期、返回日期、交通方式、审批状态、审批意见。有一个容易忽略的点学生姓名和班级名称要不要冗余到健康上报表里我推荐冗余。虽然有学号可以关联查询但在高频查询比如按班级筛选未上报名单时多表JOIN的代价远大于冗余字段占用的空间。毕设系统数据量不大性能问题不明显但冗余字段能极大简化JSP页面的查询逻辑属于以空间换时间的典型做法。另外一张重要的表是t_health_report里的健康码状态字段。这项数据在实际业务里可能有红、黄、绿三种状态设计时不要用VARCHAR随便存字符串可以用TINYINT存0/1/2然后在Java代码里做枚举映射。这样不仅存储更紧凑也方便后续写统计SQL。2.2 三种角色三条权限线登录后各看各的学生疫情信息管理系统最怕做成所有登录用户看到同一个菜单。如果辅导员登录后也能看到管理员的系统设置页面那这个系统在答辩时基本是送人头。所以权限控制要在一开始就规划好。建议用最经典的拦截器思路自定义一个LoginFilter在doFilter里检查session中是否存在登录用户再根据roleType判断是否放行。比如/student/*路径只允许角色为2学生的账号访问/teacher/*路径只允许角色为1辅导员的账号访问/admin/*路径只允许角色为0管理员的账号访问。三种角色的核心操作分别是学生端每日健康上报、查看历史上报记录、提交行程登记申请、查看审批结果。这个界面要尽量简洁一个表单搞定不要堆砌无关信息。辅导员端查看本班学生上报情况、查看未上报名单、审批学生的行程申请、按日期导出本班健康信息Excel。这里最核心的需求是查漏所以要重点设计按日期筛选未上报列表。管理员端维护院系班级信息、管理用户账号、查看全校上报统计图表、管理所有学生的上报记录。统计图表是整个系统的亮点建议用ECharts展示趋势折线和柱状图。2.3 健康上报的状态流转这条链子怎么串起来健康上报本身是简单的提交动作但如果加上辅导员审核环节就需要一套流转逻辑。我建议在t_health_report表中加一个status字段0表示待审核、1表示正常通过、2表示异常待复核。学生提交后默认状态为0辅导员在列表中看到待审核记录判断体温是否正常、健康码状态如何然后选择通过或标记异常。这个状态流转看起来简单但有个小细节很重要学生端修改当天上报记录的权限应当被限制。比如学生提交后如果状态已经是正常通过那就不能再修改当天记录只能通过补充说明功能留言。如果状态还是待审核或异常待复核则允许在规定时间内编辑一次。这个逻辑用代码实现也就是一两个if判断但体现了业务流程的完整性答辩时是可以拿出来讲的产品细节。3. Servlet / DAO / JSP三层实战核心功能与踩坑记录3.1 从JDBC到DBUtil不要再在Servlet里写SQL了代码结构问题我见过太多反面教材了。有的同学把数据库连接、SQL查询、前端跳转全部写在Servlet的doPost里一个方法几百行看着很唬人但一旦需求改动改一个地方崩三处。JSP项目的标准做法是三层架构Servlet控制层→ Service业务逻辑层→ DAO数据访问层。JSP只负责渲染数据。先写一个标准的数据库连接工具类。这里强烈推荐用c3p0连接池而不是传统的DriverManager每次新建连接。连接池的好处是复用数据库连接资源在高频提交健康上报时不会因为频繁创建连接导致Tomcat卡顿。配置起来也不复杂import com.mchange.v2.c3p0.ComboPooledDataSource; public class DBUtil { private static ComboPooledDataSource dataSource new ComboPooledDataSource(); static { try { dataSource.setDriverClass(com.mysql.jdbc.Driver); dataSource.setJdbcUrl(jdbc:mysql://localhost:3306/epidemic_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai); dataSource.setUser(root); dataSource.setPassword(your_password); dataSource.setInitialPoolSize(5); dataSource.setMaxPoolSize(20); } catch (Exception e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } public static void close(Connection conn, PreparedStatement ps, ResultSet rs) { try { if (rs ! null) rs.close(); if (ps ! null) ps.close(); if (conn ! null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }这里有个非常常见的坑MySQL 8.0之后的驱动类是com.mysql.cj.jdbc.Driver不是com.mysql.jdbc.Driver。如果你用的是旧驱动类启动时不会立刻报错但第一次查询数据库时会抛出ClassNotFoundException。另外连接URL一定要配置serverTimezoneAsia/Shanghai否则MySQL 8.0的时间类型处理会报错。这两个问题我在指导毕设时几乎每年都会遇到。3.2 登录模块与密码加密如何优雅地处理Session登录模块是每个管理系统的门面也是最容易被问你这里安全吗的地方。明文存储密码在毕设里很常见但实在不该出现在一个2020年代的系统里。建议使用MD5加盐的方式处理。public static String md5Encrypt(String input, String salt) { String value input salt; try { MessageDigest md MessageDigest.getInstance(MD5); byte[] bytes md.digest(value.getBytes(StandardCharsets.UTF_8)); StringBuilder sb new StringBuilder(); for (byte b : bytes) { String hex Integer.toHexString(b 0xff); if (hex.length() 1) sb.append(0); sb.append(hex); } return sb.toString(); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } }登录成功后把用户对象放进session然后重定向到对应角色的首页。这里有一个用户体验的细节登录页面用验证码。虽然增加了一点实现成本但能体现完整性也是答辩时的一个加分项。验证码可以用Java的BufferedImage动态生成一个包含4位数字的图片把验证码字符串存到session中登录时比对即可。Session的管理也有讲究。很多同学在退出登录时只写一句session.removeAttribute(user)这是不够的。正确的做法是session.invalidate()让整个会话失效同时FrontController里处理Session超时的跳转逻辑避免用户会话过期后点击提交报出空指针异常。3.3 健康上报流程实现事务与并发约束是核心考验健康上报是整个系统里最重要的功能也是容易出问题的地方。在t_health_report表中我们设置了(student_id, report_date)的联合唯一索引。如果学生疯狂点击提交按钮第二个请求会被数据库拒绝并抛出DuplicateKeyException。代码层面要在Service层做拦截public int submitReport(HealthReport report) throws Exception { Connection conn null; PreparedStatement ps null; ResultSet rs null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); String checkSql SELECT COUNT(*) FROM t_health_report WHERE student_id? AND report_date?; ps conn.prepareStatement(checkSql); ps.setInt(1, report.getStudentId()); ps.setDate(2, report.getReportDate()); rs ps.executeQuery(); if (rs.next() rs.getInt(1) 0) { throw new BusinessException(今日已上报无需重复提交); } rs.close(); ps.close(); String insertSql INSERT INTO t_health_report(student_id, report_date, temperature, location, health_code, travel_code_img, status, create_time) VALUES(?,?,?,?,?,?,?,NOW()); ps conn.prepareStatement(insertSql); ps.setInt(1, report.getStudentId()); ps.setDate(2, report.getReportDate()); ps.setBigDecimal(3, report.getTemperature()); ps.setString(4, report.getLocation()); ps.setInt(5, report.getHealthCode()); ps.setString(6, report.getTravelCodeImg()); ps.setInt(7, 0); ps.executeUpdate(); conn.commit(); return 1; } catch (Exception e) { if (conn ! null) conn.rollback(); throw e; } finally { DBUtil.close(conn, ps, rs); } }注意这个事务控制。上报操作涉及先查后插两步之间如果不在同一事务里就可能出现并发下的重复插入。虽然联合唯一索引兜底了但加上事务后流程才严谨这也是答辩时导师会关注的点。3.4 文件上传与ECharts图表让页面丰富起来行程码截图上传是很多同学到后期才补的功能做得比较随意。JSP里实现文件上传最稳妥的方式是用Apache的Commons FileUpload组件。需要引入commons-fileupload-1.4.jar和commons-io-2.11.0.jar两个依赖然后在Servlet中解析MultipartRequest。文件存储路径有个细节不要把文件存在Web应用根目录下的一个固定文件夹里否则项目重新部署时文件会丢。建议存放在Tomcat的外部路径比如D:/upload/数据库里保存的是相对路径或者文件名页面展示时通过一个专门的Servlet流式读取返回。这样Demo期间即使清了Tomcat缓存图片也还在。统计图表的实现强烈推荐用ECharts。它不用你写任何后端图表代码只要在JSP页面里引入ECharts的JS库然后通过Ajax请求后端接口返回JSON数据再渲染到div容器中。后端返回JSON可以用Gson库非常方便。script srchttps://cdn.jsdelivr.net/npm/echarts5.4.3/dist/echarts.min.js/script div idtrendChart stylewidth: 100%;height:400px;/div script var chart echarts.init(document.getElementById(trendChart)); $.getJSON(admin/reportTrend.do, function(data) { chart.setOption({ title: { text: 近14天全校上报人数趋势 }, xAxis: { type: category, data: data.dates }, yAxis: { type: value }, series: [{ type: line, data: data.counts, smooth: true }] }); }); /script这块的难度在于写统计SQL。比如近14天每天上报人数对应的SQL是SELECT report_date, COUNT(*) FROM t_health_report WHERE report_date DATE_SUB(CURDATE(), INTERVAL 13 DAY) GROUP BY report_date;这里要注意没有上报的那几天这条SQL查出来是没有结果行的需要后端的Java代码补0否则ECharts的坐标轴会缺数据。这个补零的细节很典型平时不写统计功能的人第一次处理时一定会漏。4. 组装JSP页面从前端布局到后台路径管理4.1 用include把页面骨架统一起来很多同学的JSP页面是CtrlC、CtrlV出来的一个项目里几十个页面每个页面都有重复的导航栏和侧边栏。不仅代码冗余后期想改菜单标题得改几十个文件。这里推荐用jsp:include或者JSTL的c:import做公共布局。以辅导员端为例可以把页面拆成三个部分header.jsp顶部导航、menu.jsp侧边栏菜单、footer.jsp页脚每个业务页面只需要写主体内容。% page contentTypetext/html;charsetUTF-8 languagejava % html head title班级上报情况/title jsp:include pagecommon/header.jsp/ /head body jsp:include pagecommon/menu.jsp/ div classmain-content h2本班今日上报情况/h2 table classtable table-bordered thead trth学号/thth姓名/thth体温/thth所在地/thth健康状态/thth上报时间/th/tr /thead tbody c:forEach items${reportList} varitem tr td${item.studentNo}/td td${item.studentName}/td td${item.temperature}/td td${item.location}/td td${item.healthCodeName}/td td${item.createTime}/td /tr /c:forEach /tbody /table /div jsp:include pagecommon/footer.jsp/ /body /html这里要多说一句JSP页面本身的编码问题。项目里所有JSP文件都要在头部声明pageEncodingUTF-8同时Tomcat的server.xml中Connector需要设置URIEncodingUTF-8。否则你会遇到中文乱码的问题而且是最难排查的那种——数据库存的是正常中文页面上显示却是问号。排查过程通常要花掉半天时间其实源头就是POST请求的编码没设置。4.2 Servlet路径设计的取舍约定优于配置Servlet的路径映射看起来只是个细节但做好了能省很多事。建议采用REST风格比如LoginServlet映射到/login.doLogoutServlet映射到/logout.doStudentReportServlet映射到/student/report.doTeacherAuditServlet映射到/teacher/audit.doAdminStatisticServlet映射到/admin/reportTrend.do这么做的好处是路径本身就是功能模块的语义。前端JSP里写表单的action时一眼就能看出这个请求属于哪个角色、干什么用的。用了REST风格后还可以给每个角色一个独立的前缀目录再和权限拦截器的路径判断配合起来非常丝滑。我还建议在项目中使用一个BaseServlet作为基类。它继承HttpServlet重写doGet和doPost统一解决请求编码、登录状态校验和异常处理三个问题。业务Servlet再继承这个BaseServlet每个业务方法只需要关注自己的逻辑。这是非常典型的经验模式也是答辩时展示工程能力的加分项。4.3 前端控件的细节优化让Demo显得更完整疫情影响系统的前端不需要炫技但要有意识地做几件小事第一个是日期控件默认值。健康上报表单里的日期默认应该是今天不需要用户手动选择。用input typedate value${today}即可后端在Model中传入今天的日期字符串。第二个是表格的分页。虽然毕设系统数据量小但上报历史记录这种表会随着时间累积变长不做分页就显得不专业。可以用PageBean类封装页码、总记录数、每页大小和数据列表DAO层用LIMIT ?, ?配合预编译实现JSP页面用c:forEach渲染页码按钮。第三个是表单校验。不要只依赖JavaScript弹窗提示后端Servlet里也要做非空和格式校验。体温如果超过42度就拒绝写入这也是一个简单的业务规则几个if就能搞定。5. 答辩前一晚功能自查清单与演示脚本5.1 完整走一遍核心流程别在台上翻车一个常见的答辩翻车场景是学生现场演示上报流程结果发现报了十几条数据后辅导员端列表里一条都没显示。原因多半是学号关联查询写错了或者新增的学生没有被分配班级。所以我一直强调答辩前至少完整走三遍核心流程。建议你的演示脚本按下面这个顺序走用一个学生账号登录提交一条健康上报体温36.5所在地填一个正常城市健康码状态选绿码。切到辅导员账号登录查看今日上报列表确认刚才那条记录出现。辅导员对这条记录执行审核通过。切回学生账号查看历史记录确认状态变为正常。在辅导员端点按班级统计确认上报率图表更新。切成管理员账号查看全校近14天趋势图切到院系列表。这套脚本覆盖了所有角色和核心数据流。只要这六个环节都能走通你的系统在功能层面已经能打过大部分毕业设计了。5.2 准备几个主动回答的技术亮点答辩时不是等老师问你要主动把亮点讲出来。我建议你在最后准备好三个可以主动展示的点亮点一事务控制与防重复提交。说明你在上报模块用到了事务和联合唯一索引的双重保护在处理重复点击时不会产生脏数据。亮点二分层设计与可维护性。直接展示你的项目目录结构说明基于三层架构业务代码和页面展示分离后期扩展角色和功能不需要大规模重构。亮点三密码加密与权限控制。告诉评委登录密码使用了加盐MD5并且通过Filter实现了基于角色的URL访问控制未授权用户直接绕过路径会被拦截。5.3 老师们爱问的问题提前想好答案我在答辩现场见过的老师问题高度集中在下面几个方向。建议每道题都准备一口头答案你的系统如何保证一个学生不会重复上报答联合唯一索引加业务判断在事务里先查询当日记录再插入体温异常的数据你是怎么处理的答体温超过阈值时状态自动标为异常待复核并推送给辅导员审核如果你变成千级并发用户JSP这套方案会遇到什么瓶颈答数据库连接池限制、Session在分布式下不共享、Servlet容器单机吞吐量有限可以横向扩展或改造SSM/Spring Boot你数据库里用什么字段存日期读取时候时区问题怎么处理答用DATETIMEURL配置serverTimezone统一使用Asia/Shanghai时区最后这个问题很容易被问到因为很多同学在这个细节上栽过跟头。能主动说出时区配置的处理方式比你背十个名词解释都管用。收尾一些副产物资料每次做完这类项目我都会顺手把下面这几样东西整理成独立文档答辩后直接装进毕设归档文件夹里SQL脚本.sql包含建库、建表、插入初始账号的完整SQL语句。方便评委机器上直接建环境。README.md写清楚环境要求JDK版本、Tomcat版本、MySQL版本、部署步骤、初始账号密码。这个文档的价值在答辩当天体现得淋漓尽致——评委换成自己的电脑时一份好的README能省大量解释时间。操作说明.doc配好安装截图的用户手册给不使用软件系统的人看足以证明工程能力有条理。这些副产物不需要花太多时间但它们能让你的毕设从能跑的程序变成完整的项目交付物。评委看的不只是功能还有一位毕业生是否具备了基本的软件工程交付意识——说实在话很多职场新人连这点都做不好。如果时间允许把这三样东西准备好你的JSP学生疫情信息管理系统就不仅仅是一份合格毕业设计还是一件能拿出去给面试官看的真实作品了。