ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

从源码到部署:JSP+Servlet+MySQL高校疫情管理系统的设计与调试

从源码到部署:JSP+Servlet+MySQL高校疫情管理系统的设计与调试 最近帮一位朋友调一套课设项目拿到手的时候是个压缩包标题写着“JSP某高校新冠疫情查询与管理系统rc52d程序源码数据库调试部署开发环境”解压一看典型的Java Web课程设计——JSP Servlet MySQL数据库脚本、源码、说明文档都在。我花了一晚上把它从404调到能正常跑通又顺手梳理了一遍功能逻辑和表结构。这篇文章就围绕这个项目把源码结构、环境搭建、部署调试、数据库设计这些事写透让想复现它、改造它或者拿它当课设参考的同学少走弯路。这套系统解决的是高校疫情期间每天收集学生健康状况的问题学生通过网页上报体温、健康码状态、行程情况辅导员能查看自己班级的上报进度和异常情况管理员做整体数据统计和用户管理。说白了就是把原来靠Excel表格手工汇总的流程变成了一个带角色权限的Web管理系统。它适合三类人看正在做Java Web课设但不知道怎么下手的同学、拿到类似源码但跑不起来的研究者、以及想从老项目里学习Servlet/JSP底层原理的入门开发者。1. 项目定位与价值在Spring Boot时代JSP疫情管理系统还能教给我们什么1.1 这套系统解决的真实场景先还原业务场景。高校在疫情期间有一个非常刚需的流程每个学生每天要报一次健康信息包括体温是否异常、有没有咳嗽、是否接触过高风险人群、当前在哪个城市偶尔还要上传健康码截图。如果通过微信群接龙或者Excel表收集数据是零散的辅导员要一个个打开消息去对想统计“今天哪些人没报”得有专人花不少时间去核对。这套系统把这个流程“网页化”了。学生打开登录页进入自己的上报界面填完当天信息点提交数据落到MySQL里。辅导员登录后看到一个待办面板直接显示今天缺报的学生名单点开详情能看某个学生过去7天甚至14天的体温曲线。管理员则负责更底层的东西维护院系和班级结构、给用户分配角色、重置密码以及跑各种汇总统计。系统名称里的“查询”和“管理”两个字不是随便起的。查询指的是面向普通用户的数据检索——我自己查历史记录、辅导员查本班上报情况管理指的是后台的增删改查——管理员维护用户系统对异常记录做标记和过滤。理解这个定位很重要因为很多课设项目失败就失败在“只有CRUD没有场景”而这个项目的功能都是能直接对上实际需求的。1.2 JSP项目的底层逻辑Servlet与模板引擎是怎么分工的JSP项目在技术圈里常被吐槽“过时”但它的分工逻辑放在今天依然值得理解。一次完整请求的旅程是这样的浏览器地址栏输入某个链接比如/epidemic-system/login.jspTomcat收到请求后先看JSP页面里有没有Java代码有的话把它编译成一个Servlet类如果请求指向的是/adminServletTomcat则根据web.xml或注解找到对应的Servlet类执行里面的doGet或doPost方法处理完数据后用request.getRequestDispatcher(xxx.jsp).forward()把用户跳到指定页面。页面渲染的“活儿”其实是被拆分的。Servlet负责从数据库拿数据、做判断、把结果塞进request或sessionJSP负责把Java对象里面的值输出成HTML。在这个项目里常用的是% %表达式和JSTL标签比如c:forEach items${list} varitem循环出列表。这种模式的好处是简单直观——逻辑和数据都摆在一个项目里学生很容易跟上。你看项目里的代码就会发现Tomcat的work目录会在第一次访问JSP时生成对应的.java和.class文件这就是为什么有时改了JSP不重新部署也能生效而改了web.xml却必须重启。了解这个机制在排查更新不生效问题时会省很多时间。1.3 rc52d不是版本号课程设计项目命名的那些事很多人拿到压缩包会问“rc52d是什么意思是Release Candidate 52d吗”不是。这类带一串随机字母数字的标题通常是网上下载站或资源共享平台给每个文件生成的一个标识码为了防止同名文件冲突也可能是打包时自动追加的哈希片段。它和软件版本没有任何关系更不是正式项目代号。这一点看似无关紧要实际会影响后续操作。你需要做的第一件事就是把文件夹重命名为自己能看懂的名字比如去掉rc52d改成epidemic-system。我就是这样处理的——项目里到处都是原始名称的残留包括数据库脚本文件名、web.xml里的display-name、页面顶部标题甚至包名。如果不先统一改名后面改上下文路径、导数据库、找资源都会绕弯子。2. 从源码目录到功能地图拿到项目先别急着跑先看懂结构2.1 WebRoot下的JSP页面划分解压后的项目里核心目录是WebRoot有些版本叫webapp或WebContent。这是一个标准的动态Web项目目录WebRoot/ ├── login.jsp ├── register.jsp ├── index.jsp ├── admin/ │ ├── index.jsp │ ├── student_list.jsp │ ├── user_manage.jsp │ ├── report_statistics.jsp │ └── notice_edit.jsp ├── teacher/ │ ├── index.jsp │ ├── class_reports.jsp │ ├── unsubmitted_list.jsp │ └── student_detail.jsp ├── student/ │ ├── index.jsp │ ├── daily_report.jsp │ ├── my_history.jsp │ └── personal_info.jsp ├── static/ │ ├── css/ │ ├── js/ │ └── images/ ├── WEB-INF/ │ ├── web.xml │ ├── lib/ │ └── classes/ └── common/ ├── header.jsp └── footer.jsp这给我们的信息量很大。首先系统按角色拆了三个目录说明权限设计是从页面级别就开始区分的。其次有common目录说明抽过公共头部和尾部而不是每个页面都写一遍完整HTML。第三页面命名基本是“功能名动作”比如daily_report.jsp是学生端每日上报表单unsubmitted_list.jsp是辅导员端未提交名单一眼就能看出这个页面干嘛的。启动后默认跳转的入口是index.jsp一般会在里面判断当前session里有没有登录用户没有就重定向到login.jsp有就根据角色跳到对应目录下的首页。这个逻辑代码通常只有几行但它是一个Web项目最基本的“路由意识”。2.2 src下的Servlet/JavaBean/DAO分层Java源码集中在src目录包结构通常是这样com.xxx.epidemic ├── bean/ # 实体类对应数据库表 │ ├── User.java │ ├── Report.java │ ├── Student.java │ └── Clazz.java ├── dao/ # 数据访问对象封装JDBC操作 │ ├── UserDao.java │ ├── ReportDao.java │ └── BaseDao.java ├── servlet/ # 控制器接收请求、调用DAO、跳转页面 │ ├── LoginServlet.java │ ├── ReportServlet.java │ ├── AdminServlet.java │ └── StatisticServlet.java ├── filter/ # 登录过滤器权限控制 │ └── AuthFilter.java └── util/ └── DBUtil.java这是典型的“实体类 DAO Servlet”三层结构很多课设标准答案都长这样。实体类里的字段基本和数据库表字段一一对应DAO类里主要写select、insert、update、delete这些方法Servlet负责抓住request参数调用DAO然后把结果setAttribute到request里带去JSP。说句实话这层抽象并不完美企业开发中也早已被MyBatis、MyBatis-Plus这类框架替代。但对学Web基础的人来说它把“浏览器请求从哪里来、参数怎么取、数据库结果怎么回去”这条链路展示得明明白白。后面你学Spring Boot时会发现很多思想一脉相承Servlet就是Controller的祖师爷DAO就是Mapper的前身。2.3 三条角色权限线学生、辅导员、管理员分别能做什么系统的业务玩法可以简单归纳成一张表角色核心功能典型操作学生每日上报、查看历史、修改资料填写体温、选择症状状态、提交当天记录辅导员查看本班上报情况、导出统计查看未报名单、点击学生查看14天记录管理员用户管理、院系班级维护、公告管理添加用户、重置密码、发布通知、查看全校统计权限控制实现上一般有两层。第一层是过滤器AuthFilter在web.xml里用url-pattern/*/url-pattern拦所有请求检查session里有没user对象没有就跳回登录页这样没登录的人根本进不去功能页。第二层是页面内判断每个JSP顶部写一段类似下面的代码根据角色显示不同菜单防止学生访问辅导员页面User currentUser (User) session.getAttribute(currentUser); if (currentUser null || currentUser.getRole() ! 1) { response.sendRedirect(login.jsp); return; }这里的role字段通常约定为1代表管理员2代表辅导员3代表学生。有些系统用字符串admin、teacher、student效果一样。理解这条权限线你才能明白为什么有的页面学生能看到、有的页面点了就报错——不是功能藏着而是权限被拦了。3. 环境准备与数据库初始化复现老项目运行环境的关键步骤3.1 JDK/Tomcat/MySQL的版本匹配关系课程设计项目大多基于旧环境开发版本匹配是第一个坑。这类JSP项目一般是在以下组合下调试出来的组件推荐版本说明JDKJDK 1.8兼容性最好所有课设代码基本都能跑TomcatTomcat 8.5支持Servlet 3.1/JSP 2.3对应JSTL 1.2MySQLMySQL 5.7如果是MySQL 8则需要改驱动和URL数据库驱动mysql-connector-java 5.1.x或8.0.x版本必须和MySQL对应IDEIntelliJ IDEA 2020 或 Eclipse两种都能导入为什么推荐JDK 8因为很多老代码是在JDK 8环境下编译的如果你用JDK 17Servlet API、JSP API在有些组合下会出现模块访问限制问题报ClassNotFoundException或者IllegalAccessError。我不是说高版本一定不行而是为了“最快跑通”用和老项目最接近的环境最省事。如果本机没有单独的Tomcat直接用IDEA/Eclipse内置的Tomcat集成也可以。最重要的一点如果本机只有MySQL 8不要急着卸载重装5.7。只要把驱动换成8.x版本并在JDBC URL里加上几个参数MySQL 8也能正常连上。这点我在4.2节详细说。3.2 数据库导入与连接配置数据库脚本通常放在项目根目录或者db文件夹比如db_epidemic.sql。导入步骤很简单用Navicat、MySQL Workbench或命令行创建一个数据库名字要和脚本里的CREATE DATABASE一致很多是db_epidemic然后选择该数据库运行SQL脚本。跑完以后表格应该自动生成。连接配置通常在源文件里。找到DBUtil.java核心代码长这样private static final String DRIVER com.mysql.jdbc.Driver; private static final String URL jdbc:mysql://localhost:3306/db_epidemic?useUnicodetruecharacterEncodingUTF-8; private static final String USERNAME root; private static final String PASSWORD root; static { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USERNAME, PASSWORD); }说白了你需要改的就是PASSWORD一栏改成你自己MySQL的登录密码。如果改了配置文件却还是用旧密码连接Tomcat会因为无法获得连接而报错。还有个小细节改完配置后要重新部署甚至重启Tomcat因为驱动类和连接池只在类加载时初始化一次。3.3 IDEA导入JSP项目的参数设置IDEA导入这类项目有几个关键点顺序错了会卡很久。第一步选择File - Open选中解压后的项目根目录以普通目录打开不要选“Open as Maven/Ant”。第二步进入Project Structure快捷键CtrlAltShiftS在Project设置里把SDK选为JDK 1.8在Libraries或Modules里添加项目WebRoot/WEB-INF/lib下的所有jar包。如果你漏了这一步后面启动时会报各种ClassNotFoundException比如找不到mysql-connector-java或jstl.jar。第三步在Facets里加Web把Web Resource Directory指到WebRootDeployment Descriptor指到WebRoot/WEB-INF/web.xml。第四步在Artifacts里新建一个Web Application Exploded把lib和classes添加进去。这些配置不复杂但很容易漏。我的习惯是一个个确认SDK、Libraries、Facets、Artifacts四步全绿才去创建Tomcat运行配置。漏一步启动前的编译阶段就可能失败或者页面404。4. 部署调试实录从404到跑通首页的完整排查链路4.1 部署方式与常见定位JSP项目的部署方式有两种常见路径。第一种是在IDEA/Eclipse里配置Tomcat Server运行。这是调试阶段的推荐方式好处是可以在控制台直接看日志改代码后可以用“Update resources”快速生效。配置时注意Deployment标签页里Application context的路径。如果填了/epidemic-system那么访问地址就是http://localhost:8080/epidemic-system/login.jsp如果填/访问地址就是http://localhost:8080/login.jsp。第二种是手动打war包扔进Tomcat的webapps目录。在IDEA里通过Build - Build Artifacts打一个war包复制到Tomcat的webapps目录下启动Tomcat后它会自动解压。这种方式适合最后交付或部署到服务器但调试时没有第一种方便。遇到404时90%的情况是路径问题。你要分清是页面不存在还是项目上下文不对直接访问http://localhost:8080/先看Tomcat首页能不能开能开说明Tomcat正常再看http://localhost:8080/项目名/能不能进首页项目名和你的Application context必须对得上。还有一个常见的低级错误忘了在Web模块里把lib目录加入工件页面存在但启动后JSP能渲染一访问Servlet就404。4.2 JDBC连接MySQL8的经典报错与解决这台机器装的是MySQL 8.0按传统写法直接用项目自带的驱动包启动登录时控制台直接刷出几行异常。第一类是驱动两连问Loading class com.mysql.jdbc.Driver. This is deprecated。MySQL 8的驱动包已经把旧类com.mysql.jdbc.Driver保留成过时状态用还是能用但如果你项目里lib目录的驱动版本很老比如5.1.8它显然连不上MySQL 8后面必然跟一句Communications link failure或者Public Key Retrieval is not allowed。solution很简单换驱动包并换驱动类名class启动类改成com.mysql.cj.jdbc.Driver驱动jar换成mysql-connector-java-8.0.28.jar或更高版本URL改成jdbc:mysql://localhost:3306/db_epidemic?useUnicodetruecharacterEncodingUTF-8useSSLfalseallowPublicKeyRetrievaltrueserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue是什么原因MySQL 8默认用户认证插件是caching_sha2_password客户端首次连接时可能因为安全限制不允许自动获取公钥加上这个参数是明确告诉JDBC允许获取服务端公钥从而完成密码传输加密验证。4.3 中文乱码从Tomcat到数据库全链路处理中文乱码是JSP项目里最烦人的问题因为它在四个环节都可能出现。首先是数据库表本身。如果你导入SQL时用Navicat默认字符集建出来的表可能是latin1插入的中文就会变成问号。解决办法是建表时指定字符集或者在SQL文件头部加上SET NAMES utf8mb4;。其次是JDBC连接串characterEncodingUTF-8必须加不然读写都会按服务器默认字符集转换。第三是Servlet处理请求参数如果过滤了编码request.getParameter读到的中文是乱码所以JSP里表单提交前要先写一遍代码保留编码或者在Servlet第一行加request.setCharacterEncoding(UTF-8)。最后是Tomcat连接器本身。如果浏览器地址栏直接带中文参数需要在server.xml的Connector里加URIEncodingUTF-8。这个项目里JSP页面顶部已经写了pageEncodingUTF-8所以你实际排查时应该先检查数据库有没有乱码数据库没乱码、页面乱码说明是JSP输出编码问题数据库就是乱码说明连接串或表字符集有问题。按照这个顺序定位可以少走很多弯路。4.4 404/500/405的快速定位清单跑JSP课设项目最常见的三个状态码我给你一个可以直接照抄的排查思路。看到404先考虑三件事资源路径是否带上下文、web.xml里Servlet映射是否正确、IDEA里是否真的把这个页面包含进Artifact。看到500就立刻看Tomcat控制台日志JSP项目和Spring Boot一样有完整堆栈但很多新手第一反应是刷新页面其实应该去IDE的Console里找Caused by那一行。看到405通常意味着Servlet只写了doPost没写doGet或者HTML表单的method属性与Servlet方法不匹配。还有一个非常典型的500是空指针异常发生在JSP页面里比如某个list为null就request.getAttribute(list)取出来直接forEach循环——这在JSP页面中是不报编译错但运行时报错控制台会看到NullPointerException at org.apache.jsp.xxx_jsp。这说明后台Servlet没有把数据放进去问题根源在Servlet逻辑而不是JSP页面本身。5. 数据库设计与业务逻辑复盘疫情管理系统的命根子是统计5.1 核心表结构的字段设计逻辑数据库是整个系统的命根子。我打开SQL脚本大致梳理了核心表的字段简化后是这样用户表CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE, password VARCHAR(100), real_name VARCHAR(50), role INT, -- 1管理员 2辅导员 3学生 class_id INT, status INT DEFAULT 1 -- 1正常 0禁用 );上报记录表CREATE TABLE t_report ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT, report_date DATE, temperature DECIMAL(4,1), is_cough TINYINT, -- 0否 1是 is_contact_risk TINYINT, location VARCHAR(100), health_code VARCHAR(20), status INT DEFAULT 0, -- 0正常 1异常 create_time DATETIME, UNIQUE KEY uk_user_date (user_id, report_date) );我特别留意到上报记录表有一个UNIQUE KEY (user_id, report_date)这个设计很关键。它保证同一用户同一天只能提交一条记录从数据库层面防止重复上报。如果你后续手动往表里插测试数据也一定要遵守这个约束否则删数据、改数据都容易把自己绕晕。为什么用户用role字段而不用三张用户表因为三类角色的公共信息高度一致——都是“账号密码姓名”区别只是权限不同。如果拆成三张表登录时先查学生表查不到再查教师表查询逻辑会变得很臃肿。对课设项目来说一个role字段是最省事也最直观的做法。班级、院系表一般是CREATE TABLE t_class ( id INT PRIMARY KEY AUTO_INCREMENT, dept_id INT, class_name VARCHAR(50) );有院系、有班级、有用户才能在统计SQL里按“院系-班级-学生”逐级下钻。5.2 几个关键统计SQL与业务校验这个系统的核心价值在“统计”所以SQL一定要会写。我挑三个最常用的查法答辩时老师也大概率会问。统计某天各班的上报人数SELECT c.class_name, COUNT(DISTINCT r.user_id) AS submitted_count FROM t_class c LEFT JOIN t_user u ON u.class_id c.id LEFT JOIN t_report r ON r.user_id u.id AND r.report_date 2022-11-01 GROUP BY c.id;查未上报的学生名单SELECT u.real_name, u.username FROM t_user u LEFT JOIN t_report r ON r.user_id u.id AND r.report_date 2022-11-01 WHERE u.role 3 AND u.status 1 AND r.id IS NULL;这个SQL的关键是LEFT JOIN再判断r.id IS NULL而不是直接写WHERE r.report_date IS NULL。很多初学者会把“未上报”写成“上报记录表中没有这个人”但拿JOIN想清楚就明白了如果只在t_report里过滤根本查不出没提交过的人。统计异常人数SELECT COUNT(*) FROM t_report WHERE report_date 2022-11-01 AND (temperature 37.3 OR is_cough 1 OR is_contact_risk 1);异常判断的业务规则一般是“体温≥37.3或咳嗽或有接触史”。这个规则在代码里和SQL里各写了一遍维护性不是很理想但课设这样写完全够用。如果后续做优化可以把规则抽成一个常量或者配置表让管理员自己改阈值不用改代码。5.3 从“疫情管理系统”到任意信息管理系统抽象与复用调试完这个项目后我的一个感受是这类系统的骨架高度相似换一层皮肤就是另一个系统。把“学生上报健康状况”换成“学生提交宿舍报修单”就是报修管理系统把“每日上报”换成“图书借阅登记”就是图书管理系统把“查看未上报名单”换成“查看未还书名单”统计逻辑几乎一模一样。用户体系、角色权限、上报记录表、统计列表这四个部分稍作修改就能复用到绝大多数信息管理类项目里。如果要把这个老项目重构成Spring Boot对应关系也很清晰老项目组件Spring Boot对应JSP页面Thymeleaf模板或Vue前端页面ServletController接口DAO类Mapper接口 MyBatisweb.xml配置注解/配置文件DBUtil工具类Druid连接池或HikariCP所以别觉得JSP项目“土”它把一个Web应用最原始的骨架完整暴露给了你。你理解了Servlet的请求-响应模型、理解了DAO的JDBC连接获取、理解了session里的登录用户之后学SpringBoot时就能秒懂为什么Controller会返回视图名、为什么Mybatis的一个Mapper对应一段SQL这些知识是一脉相承的。我个人的习惯是拿到这种老项目第一时间先把它跑起来跑起来之后再带着“如果我重写会怎么做”的想法去读代码。JSP项目最大的价值不在生产而在帮你把Web里的“根”补上。就像练功夫先扎马步虽然不够华丽扎稳了后面学什么都快。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进