尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

JSP+Servlet医院预约挂号系统:从源码跑通到Spring Boot迁移

JSP+Servlet医院预约挂号系统:从源码跑通到Spring Boot迁移 简介这是一份基于JspServlet技术栈实现医院预约挂号完整功能的Java Web项目源码面向计算机相关专业毕业设计、课程实训及初中级Java开发学习者。项目覆盖患者注册登录、按科室与医生查询排班、在线预约、后台管理与数据统计等典型业务模块适合用于理解JSP、Servlet、MVC分层、数据库交互等核心知识也可直接作为毕业设计或项目答辩的参考原型。压缩包内共1431个文件其中含78个Jsp界面逻辑、23个Java后台处理、62个jar包依赖库、46个class编译产物、40个css与50个png样式和静态资源并配备SQL数据库脚本方便导入部署整体约30.98MB结构完整且目录层次清晰。目前已有208人学习/下载适合需要一套可直接运行并二次扩展的医院预约系统源码的开发者。完整的源码、静态资源与数据库脚本一并打包在内可帮助学习者在本地快速跑通项目流程同时对照源码梳理预约挂号的业务实现与项目构建思路。1. 基于JspServlet医院预约挂号系统课设毕设常客值得你跑通并改造基于JspServlet的医院预约挂号系统在计算机专业的课程设计和毕业设计里几乎年年出现。它解决的是一条完整的预约挂号管理链路患者注册登录、浏览科室与医生排班、选择时段预约、查询和取消记录再到医生端接诊。这套源码适合三类人正在做基于Jsp的毕设选题的学生刚学完JavaWeb想找一份完整老项目源码读通的人以及需要接手老系统做功能改造的维护者。我的结论和主流观点相反现在就业市场上确实不怎么考JSP了但把JSPServlet这套老栈读透你对HTTP、Session、JDBC、线程并发的理解会比直接上手Spring Boot黑匣子的同学扎实得多。这篇就按我实际跑通、改造这类项目的顺序展开。2. 跑通前的技术体检JSP、Servlet、JDBC 这三层是怎么协作的2.1 Servlet 生命周期和一次挂号请求的完整旅程Servlet生命周期是这套源码里最值得先看的东西也是答辩高频题。一个Servlet从被容器管理到销毁会经历类加载、实例化、init()、service()、destroy()这几个阶段。Tomcat启动时不会把所有Servlet都实例化通常是第一次请求到达时才加载。每次HTTP请求过来容器从映射关系里找到对应Servlet调用service()再按请求方法分发给doGet或doPost。这里最关键的一点是Servlet是单实例、多线程的。容器只创建一个Servlet对象所有请求并发进同一个service()。所以不要在Servlet里写可变的成员变量来存业务数据否则高并发下会出现典型的线程安全问题。就诊卡号、当前用户这类数据应该放在request或session里。很多课设源码会把数据库连接写成Servlet的成员变量这就是隐雷。一个登录请求的典型代码长这样WebServlet(/login) public class LoginServlet extends HttpServlet { private UserDao userDao new UserDao(); Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String username req.getParameter(username); String password req.getParameter(password); User user userDao.findByUsernameAndPassword(username, password); if (user ! null) { req.getSession().setAttribute(loginUser, user); resp.sendRedirect(user/index.jsp); } else { req.setAttribute(error, 用户名或密码错误); req.getRequestDispatcher(login.jsp).forward(req, resp); } } }这里两个细节要注意sendRedirect是重定向会丢失request域里的数据所以登录失败要用forward把error属性带到JSP登录成功用重定向是为了避免刷新页面时表单重复提交。doGet在浏览器直接访问登录页URL时会走到而doPost才是表单提交的入口如果你发现页面报405多半是Servlet只写了doGet没写doPost。2.2 JSP页面、Servlet控制器、JDBC数据层如何串起预约流程JSP本质也是一个Servlet。第一次访问某个JSP时Tomcat会把JSP文件翻译成一个Java类再编译执行所以JSP首次加载会明显变慢。修改JSP之后必须等热部署生效或重启Tomcat否则看到的还是旧页面。这就是为什么很多人改完页面没反应第一反应不是代码写错而是没重启。整套系统的请求链路我一般拆成三条注册登录链路、浏览查询链路、预约业务链路。注册登录链路是患者进入系统的入口register.jsp填表提交给RegisterServletServlet取参数后调UserDao执行INSERT成功就重定向到登录页。浏览查询链路是只读的患者打开科室列表页面JSP里通过DepartmentDao查列表再用循环标签渲染出来。预约业务链路最复杂先查医生排班再查号源余量提交时执行扣减号源并插入预约单中间涉及事务。规范一点的源码会把Servlet、Service、DAO分层。Servlet只做参数接收、类型转换、页面跳转业务规则放在Service或直接写在DAO方法里。不规范的会看到混合写法一段Connection、PreparedStatement、ResultSet紧接着request.getRequestDispatcher再加上out.println拼HTML。后者也能跑但答辩时老师大概率会追问一句“你的分层在哪”所以读源码时先把每条链路走一遍再判断要不要重构成分层。2.3 选型理由这套栈为什么直到今天还在课设里出现医院预约挂号系统选JSPServlet不完全是因为教学体系滞后更核心的原因是它足够透明。Spring Boot把Tomcat、IoC容器、数据库连接池都封装好了学生看不到请求是怎么从浏览器一路走到Mapper的而Servlet生命周期里容器做了什么、Session怎么存、JDBC连接怎么开关每一行代码都有明确归属。从业务复杂度上看预约挂号也适合课设表关系不复杂但五脏俱全有用户、科室、医生、排班、预约单天然带状态流转和并发控制点。既不会像学生管理系统那样简单到没东西可讲也不会像电商系统那样难到做不出来。也有人会问nginx支持jsp吗答案是不支持nginx只处理静态资源和流量转发动态请求还是要转给后端的Tomcat处理这个认知到后面打war包部署时就会真的用到。3. 把源码导入 IDEA 并跑起来解压、建库、配置 Tomcat 的全流程3.1 解压后的包体结构分析先从zip解压开始。这类源码拿到的目录结构通常是hospital-appointment/ ├── src/ // Java 源码Servlet、DAO、实体类 │ └── com/xxx/... ├── WebRoot/ 或 webapp/ // JSP 页面与静态资源 │ ├── WEB-INF/ │ │ ├── web.xml │ │ └── lib/ // 各种 jar 包 │ ├── css/ js/ images/ │ ├── login.jsp │ └── ... ├── sql/ 或 database/ │ └── hospital.sql └── README.txt多数情况下是“纯手写结构”没有Maven或Gradlejar包直接放在WEB-INF/lib里。少数是Maven工程但被打包成zip。导入IDEA时我一般直接Open这个目录如果IDEA没自动识别项目类型就新建一个空项目再把src和webapp手工拷进去如果识别成了Maven工程就等依赖下载完成再跑不要中途取消容易留下残缺的依赖缓存。如果压缩包里没有sql目录只给了Java和JSP那数据库表结构可能藏在某个初期化的Servlet里或者干脆需要你自己根据实体类和DAO反推建表。真遇到这种情况先把所有.java文件里带CREATE TABLE的字符串搜一遍多数课设会写一个DatabaseInitServlet启动时自动建表。3.2 IDEA 配置源码目录、lib 依赖、Tomcat 挂载IDEA导入老项目的核心是让IDEA理解这三件事哪些目录是Java源码、哪些目录是Web资源、lib下的jar怎么进依赖。第一步确认src目录被标记为Sources Rootwebapp或WebRoot被标记为Web Resource Directory。操作路径是右键目录Mark Directory as里选对应类型。第二步把WEB-INF/lib里的所有jar加入项目依赖File → Project Structure → Libraries → → Java选择lib目录。老项目没有中央仓库管理依赖这一步漏了会出现大量红色报错。第三步配置Tomcat路径如下Run → Edit Configurations → 左上角 → Tomcat Server → Local。Server页签选择本地Tomcat安装目录Deployment页签点选Artifact选带_war_exploded的那个。Application context这里会显示访问根路径默认是项目名建议直接改成/hospital这决定了之后浏览器地址怎么敲。注意如果用的是Tomcat 10或以上版本老课设源码大概率直接404或启动报错。原因是Tomcat 10把servlet包名从javax.servlet换成了jakarta.servlet而这套代码是按javax写的。建议使用Tomcat 9跑老项目别在这个问题上浪费半小时。IDEA新建JSP项目从零开始是另一套流程File → New → Project → Java Enterprise → 勾选Web Application就行。但导入现成源码时直接新建容易覆盖原有配置不如Open原目录再补结构。还有一个高频翻车点项目编译正常但启动时ClassNotFoundException打开Project Structure → Artifacts → Output Layout发现lib目录下的jar根本没进Artifact把lib加进去再重建Artifact就好了。3.3 数据库初始化建库、导表、改连接参数建库导表用MySQL命令行最直接Navicat操作同理mysql -uroot -p hospital.sql导入后进MySQL确认表是否齐全SHOW DATABASES; USE hospital; SHOW TABLES;正常应该能看到用户表、科室表、医生表、排班表、预约表这几张核心表。然后找项目里的db.properties或DBUtils.java把连接参数改成你本地环境的jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/hospital?useUnicodetruecharacterEncodingUTF-8serverTimezoneAsia/ShanghaiuseSSLfalse jdbc.usernameroot jdbc.password你的数据库密码连接串里三个参数要搞清楚useSSL设为false是为了消掉MySQL 8默认开SSL带来的告警和潜在握手问题serverTimezone指定时区不加会直接报“The server time zone value”的运行时异常characterEncodingUTF-8保证中文进出数据库不乱码。驱动类名也要注意MySQL 5.x用com.mysql.jdbc.Driver8.x用com.mysql.cj.jdbc.Driver中间那个cj不能丢报ClassNotFoundException时先检查这里。3.4 启动与自检四个验证点启动成功后浏览器打开http://localhost:8080/hospital/看到登录页就算第一步过了。但“能打开”不等于“跑通了”我习惯做四步自检注册一个新账号去数据库user表确认数据真的插进去了用新账号登录看首页是否显示用户名确认Session存取正常进科室选医生、选时段提交预约查appointment表有没有新增记录、doctor_schedule表对应排班的剩余号源有没有减一取消刚预约的记录再查appointment表状态字段是否变成已取消。注册和登录都成功但预约写库失败集中在两个点事务没提交或者排班表里初始剩余号源为0。这两个问题第四章展开讲。4. 读懂预约挂号核心表结构、状态机与并发控制怎么做4.1 数据表建模科室、医生、排班、号源怎么设计拿这类源码第一步不是看页面而是看表。一个标准的医院预约挂号系统至少有五张核心表表名关键字段作用userid, username, password, real_name, phone患者账号信息departmentid, dept_name, dept_desc科室doctorid, dept_id, doctor_name, title医生挂在科室下doctor_scheduleid, doctor_id, work_date, work_time_type, total_num, remain_num排班与号源appointmentid, patient_id, doctor_id, schedule_id, appoint_date, status预约单表关系一句话讲完department一对多doctordoctor一对多doctor_scheduleuser一对多appointmentdoctor_schedule一对多appointment。外键在多数课设源码里并不会真正建约束而是靠DAO查询时的JOIN关联这也意味着如果你手工往appointment里插一条不存在的schedule_id数据库不会拦你业务层必须自己保证数据一致。这里最典型的建模坑是把号源数量写死在doctor表里。正确做法是号源只跟排班走每天每个时段一条排班记录total_num是当天该时段的放号量remain_num是当前余号。患者能不能约取决于doctor_schedule表里remain_num是否大于0。如果源码里没有排班表而是直接在doctor表存总号数那这个系统顶多叫“挂号登记”不叫“预约挂号”改造时要补排班维度。4.2 预约状态流转从待就诊到完成、取消、爽约appointment表的status字段一般用整数存状态0表示待就诊1表示已完成2表示已取消3表示爽约。状态变化有方向约束不是任意能跳的。只有待就诊状态能取消、能标记完成已完成不能再取消已取消不能反手改成就诊。业务代码往往写在CancelAppointmentServlet或者AppointmentDao里核心判断是这段// 先查当前状态再做状态迁移 Appointment apt appointmentDao.findById(id); if (apt.getStatus() ! 0) { // 只有待就诊状态允许取消其他状态直接拒绝 throw new IllegalStateException(当前状态不可取消); } appointmentDao.updateStatus(id, 2); // 预约单置为已取消 doctorScheduleDao.increaseRemain(scheduleId); // 号源加回去这里有个业务边界值得想清楚取消预约要不要把号源加回去课设里一般要加否则患者取消后号就白白流失了。更精细的做法是限制“就诊时间前24小时内不允许取消”那就在Servlet里先拿当前时间和排班日期做差超过阈值才放行。你如果做毕设把这个规则加进去答辩就能多讲一个业务点。源码里一般还会有一个jsp个人信息展示页面用来列出当前患者的历史预约。这个页面就是查appointment表按patient_id过滤再关联排班表和医生表把日期、时段、医生姓名、状态拼出来。状态是整数页面显示时要做映射0显示“待就诊”1显示“已完成”2显示“已取消”3显示“爽约”这个映射放在JSP标签里还是放在Java工具类里决定了你的页面代码乱不乱。4.3 并发抢号JSPServlet时代如何防止号源超卖预约挂号的核心是扣减号源难点是并发下不能超卖。两个患者同时提交预约都读到remain_num为1都执行插入就会出现一张票卖给两个人的事故。最简单可靠的做法是SQL原子更新UPDATE doctor_schedule SET remain_num remain_num - 1 WHERE id ? AND remain_num 0;受影响行数是1代表抢到了0代表没号了或者已经被别人抢先扣掉。把“判断余量”和“扣减”合成一条UPDATE语句数据库层面就保证了同一时刻只有一个请求能把remain_num减到0以下不需要显式加锁也不需要读改写三步包一个大事务。对应到Java代码完整逻辑长这样public synchronized boolean book(int scheduleId, int patientId) throws SQLException { // 原子扣减余号返回0说明号源已空或已被抢走 String sql UPDATE doctor_schedule SET remain_num remain_num - 1 WHERE id ? AND remain_num 0; try (Connection conn getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setInt(1, scheduleId); int rows ps.executeUpdate(); if (rows ! 1) { return false; // 没抢到号 } // 扣减成功后再插入预约记录 String insert INSERT INTO appointment(patient_id, schedule_id, status) VALUES (?, ?, 0); try (PreparedStatement ps2 conn.prepareStatement(insert)) { ps2.setInt(1, patientId); ps2.setInt(2, scheduleId); ps2.executeUpdate(); } return true; } }方法上的synchronized是把当前JVM进程内的请求串行化多台Tomcat部署时挡不住但课设和单点部署够用。真正生产级做法是把扣减和判断完全收敛到那一条UPDATE上业务层不放大锁。这两层逻辑你要能在答辩时讲清楚已经是超过大多数课设的水平了。另一个注意点是这两条SQL应该在一个事务里否则扣了号但插入预约记录失败余号就无故少了一个排查时会觉得相当玄学。5. 避坑指南从导入到部署的 5 个高频问题与排查方法5.1 Tomcat 正常启动但浏览器 404现象IDEA控制台出现Tomcat started浏览器打开地址却是404页面。原因访问路径没对齐。要么是IDEA里Application context配置的根路径和实际URL不匹配要么是web.xml里servlet-mapping的url-pattern写错。解决先确认浏览器地址是http://localhost:8080/hospital/login.jsp而不是http://localhost:8080/login.jsp带不带项目名取决于Deployment里的Application context。再查web.xml常见错误是url-pattern/login/url-pattern写成了login缺少前导斜杠Tomcat匹配不上直接404。排查顺序是先静态页面后动态Servlet用login.jsp能开但login请求404问题就在Servlet映射。5.2 中文乱码页面、请求、数据库三个环节都要管现象注册页输入中文姓名保存到数据库后变成问号或者页面上显示乱码。原因JSP页面编码、POST请求编码、MySQL连接编码、表结构字符集四处不一致。任何一个环节错中文都会在某一站丢一次。解决JSP第一行写% page contentTypetext/html;charsetUTF-8 pageEncodingUTF-8%POST请求处理前执行request.setCharacterEncoding(UTF-8)JDBC连接串带characterEncodingUTF-8建表时用DEFAULT CHARSETutf8mb4。如果不想在每个Servlet里重复写编码设置就在web.xml里注册一个CharacterEncodingFilter对所有请求统一设置UTF-8这是老项目里最省事的后悔药。5.3 WEB-INF/lib 里放了不该放的 jar现象Tomcat启动时控制台报LinkageError或NoSuchMethodError页面渲染有时正常有时异常。原因lib目录里塞了servlet-api.jar、jsp-api.jar这类Tomcat本身已经提供的jar两套同名的类同时存在类加载时就冲突了。解决把javax.servlet相关的API jar从WEB-INF/lib里删掉。数据库驱动、连接池这些第三方jar保留判断标准只有一个这个类Tomcat自己带不带带了就删。这正是很多源码zip里最常见的问题从课程设计资源站下载的包经常自带一份老旧的servlet-api导入IDEA后第一件事就是清理。5.4 MySQL 8 数据库配上老驱动连接直接翻车现象运行时抛ClassNotFoundException: com.mysql.jdbc.Driver或者报CommunicationsException连接被拒。原因驱动jar版本太老MySQL 8连接需要新版驱动和额外的时区参数。老驱动类名和新驱动类名不一样连接串里的参数要求也不同。解决把mysql-connector-java换成8.0.x驱动类名改成com.mysql.cj.jdbc.Driver连接串加上serverTimezoneAsia/ShanghaiuseSSLfalse。改完以后先运行一个独立的JDBC测试类验证连接而不是直接启动Tomcat把问题域缩小到数据库这一层。驱动下载后放WEB-INF/lib记得在Artifacts里确认它被打进了war或exploded目录。5.5 打成 war 包部署后样式全丢、登录跳转不对现象本地IDEA里跑一切正常把项目打成war扔到服务器Tomcat的webapps下打开页面CSS全裸、图片不显示、登录成功后跳回登录页。原因本地Application context配的是/hospital服务器上默认按war包名带路径。JSP页面里用了相对路径或硬编码/css/main.css部署后路径全错位。还有表单action和Servlet重定向的路径没带contextPath导致请求找不到对应Servlet。解决JSP页面统一用${pageContext.request.contextPath}拼路径前缀CSS引用写成${pageContext.request.contextPath}/css/main.css表单action同理。Servlet里redirect和forward的路径用req.getContextPath()拼接。如果想省掉路径层干脆把打出来的war改名ROOT.war放进webapps访问就没带项目名的麻烦。传统JSP项目打包war这一步路径处理是最容易漏的代码里凡是出现以/开头的跳转都要过一遍contextPath。6. 进阶改造Servlet 和 JSP 平移到 Spring Boot Thymeleaf6.1 迁移顺序先列接口清单再动手把JSPServlet老项目改造成Spring Boot Thymeleaf不要边看边改。先把web.xml里所有Servlet的url-pattern和它们对应的JSP页面整理成一张接口清单再按“登录注册→科室医生展示→预约提交→记录查询→管理端”这个顺序平移。每迁移完一段就启动一次项目把这条链路完整走一遍再继续下一段避免改到一半项目起不来。6.2 技术替换对照表老项目写法Spring Boot 对应写法Servlet 类 web.xml url-patternController GetMapping/PostMappingrequest.getParameter(username)RequestParam(username) String usernamereq.getRequestDispatcher(xxx.jsp).forwardreturn xxxThymeleaf模板名resp.sendRedirect(user/index.jsp)return redirect:/user/indexsession.setAttribute / getAttributeHttpSession 直接注入方法参数JDBC 手工连接暂留原 DAO稳定后再换 MyBatis / JdbcTemplateLoginServlet平移到Controller基本长这样Controller public class LoginController { private final UserDao userDao new UserDao(); PostMapping(/login) public String login(RequestParam String username, RequestParam String password, HttpSession session, Model model) { User user userDao.findByUsernameAndPassword(username, password); if (user null) { model.addAttribute(error, 用户名或密码错误); return login; // Thymeleaf 渲染 login.html } session.setAttribute(loginUser, user); return redirect:/user/index; } }老项目里的UserDao暂时保留原JDBC实现等所有页面迁移通了再决定要不要接数据库连接池或ORM。一次性把DAO也换掉出问题时分不清是页面迁移的锅还是数据层的锅。6.3 验证迁移后没缺功能迁移完成的验证标准不是页面能打开而是原系统的每条业务路径都能走完并真实写库。我习惯准备四组自测数据新用户注册、老用户登录、抢最后一个号、取消已预约记录。每跑一组就查一次appointment表和doctor_schedule表确认预约状态和余号变动符合第四章的状态机规则。这个验证方法同样适用于接手任何老JavaWeb项目跑通不算数能写库、状态能流转才算数。这套迁移我做过不止一次最大的教训是别急着删旧代码。旧的Servlet和JSP先留着新Controller和旧Servlet共存一段时间跑通一项删一项等于给自己留后悔药。这个过程比推倒重来省时间得多希望帮到你。本文还有配套的精品资源点击获取
返回列表