☰
JSP在线医疗预约挂号系统源码解析与二次开发实战
2026/9/26 12:29:54 网站建设 项目流程

简介:这是一套基于JSP与MySQL开发的在线医疗预约挂号管理系统源码,面向计算机专业学生、Java Web初学者及需要课程设计或毕业设计参考的开发者,帮助解决医院挂号流程数字化、科室排班与预约管理等实际问题。压缩包共206个文件,约1010KB,其中72个JSP页面承担前后端交互与界面展示,55个Java类实现业务逻辑与数据库操作,另有72张PNG图片用于页面素材,配合少量HTML、properties配置与说明文档,结构完整、层次清晰。系统区分管理员、用户与医生三种角色:管理员可查看预约折线图、管理医生患者与科室信息、审核排班申请;用户可注册登录、接收验证码、浏览科室简介并完成预约;医生可查看排班、患者队列并修改个人信息。已有53人学习下载,适合作为JSP+MySQL技术栈的实战练手项目,读者可从中掌握Servlet与DAO分层设计、邮件验证码发送、排班与预约数据建模等关键实现思路,快速搭建可运行的医疗预约系统原型。

1. 从一份 JSP 挂号系统源码说起:它到底能跑出什么

很多人第一次拿到「基于jsp+mysql的JSP在线医疗预约挂号管理系统源码.zip」这类压缩包,第一反应是解压、丢进 IDE、点运行,然后被一堆 404 和 500 打回原形。它本质上是一套典型的 JavaWeb 传统架构项目:JSP 负责页面渲染,Servlet 或轻量控制器处理请求,MySQL 存科室、医生、排班、号源和预约记录。它解决的是「患者在线选科室、选医生、选时段、提交预约,后台统一管理号源」这条完整链路,而不是一个静态展示页。

这套东西适合谁?一是做 java课程设计案例源码 或基于 jsp 的毕设选题 的学生,二是想快速搭一个可演示的院内预约原型的小团队,三是想借传统 JSP 项目理解 MVC 分层、Session 管理和数据库事务的开发者。它不适合直接当生产级 HIS 用,号源并发、实名核验、支付对接这些都得自己补。下面我按「先跑起来、再拆结构、再改功能、最后避坑」的顺序,把这份源码从解压到二次开发的路径讲透,中间会给出可直接抄的命令和参数。

2. 把 JSP 挂号系统在本地跑起来:环境、建库与部署

2.1 环境选型:为什么是 JDK8 + Tomcat8 + MySQL5.7

传统 JSP 项目对版本很敏感。我一般会锁定 JDK8、Tomcat8.5、MySQL5.7 这套组合,原因是这份源码大概率用了javax.servlet而不是jakarta.servlet,Tomcat10 直接跑会报类找不到;MySQL8 的驱动类名和时区参数又和 5.7 有差异,新手很容易在这里翻车。IDEA 新建 jsp 项目时也要注意,别选成 Jakarta EE 模板,否则 web.xml 的命名空间对不上。

先把依赖版本列清楚,避免你反复试错:

组件推荐版本关键原因
JDK1.8兼容 javax.servlet,编译无警告
Tomcat8.5.x支持 JSP 2.3,部署 war 稳定
MySQL5.7.x驱动com.mysql.jdbc.Driver可用
JDBC 驱动mysql-connector-java 5.1.x与 5.7 匹配,避免 SSL 报错
构建Maven 或直接 war传统 jsp 项目打包 war 最省事

提示:如果你的机器上已经装了 MySQL8,不必卸载,但连接串要加useSSL=false&serverTimezone=Asia/Shanghai,否则会抛error 2002或时区异常。

2.2 建库与导入 SQL:三步把表结构立起来

解压后一般能在sql/或db/目录找到.sql文件。先登录 MySQL 建库,再导入,最后确认字符集是 utf8mb4,否则中文科室名会变问号。

# 1. 登录 MySQL(Linux 下 socket 连接失败时加 -h127.0.0.1) mysql -uroot -p -h127.0.0.1 # 2. 建库并指定字符集 CREATE DATABASE hospital_reg DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; # 3. 导入源码自带的 SQL 文件 mysql -uroot -p -h127.0.0.1 hospital_reg < ./sql/hospital_reg.sql # 4. 验证表是否齐全 mysql -uroot -p -h127.0.0.1 -e "USE hospital_reg; SHOW TABLES;"

逻辑说明:第一步用-h127.0.0.1强制走 TCP,能绕开Can't connect to local MySQL server through socket '/tmp/mysql.sock'这个经典报错。第二步字符集必须是 utf8mb4,因为医生简介、科室描述里常有生僻字。第三步导入时如果 SQL 文件里带了CREATE DATABASE,可以去掉那行只留建表语句。第四步SHOW TABLES应该能看到user、doctor、department、schedule、appointment这几张核心表,缺表说明导入中断了,去看 SQL 文件里有没有外键顺序问题。

参数说明:hospital_reg是库名,源码里db.properties或DBUtil.java会引用它,改库名就要同步改配置。utf8mb4_general_ci排序规则对挂号场景够用,不需要换成unicode_ci。

2.3 改数据库连接与部署到 Tomcat

源码里的连接配置通常藏在src/db.properties或src/com/xxx/util/DBUtil.java。找到后按你的本地环境改,改完再打包 war 丢进 Tomcat 的webapps。

# db.properties 典型内容 jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://127.0.0.1:3306/hospital_reg?useUnicode=true&characterEncoding=utf8&useSSL=false jdbc.username=root jdbc.password=你的密码
# 传统 jsp 项目打包 war 并部署 mvn clean package -DskipTests cp target/*.war $TOMCAT_HOME/webapps/hospital.war $TOMCAT_HOME/bin/startup.sh # 浏览器访问 # http://localhost:8080/hospital/

逻辑说明:jdbc.url里的useUnicode=true&characterEncoding=utf8保证中文参数不乱码,useSSL=false避免 5.7 驱动在无证书时反复握手。打包成 war 后 Tomcat 会自动解压,访问路径就是 war 包名。如果访问根路径 404,检查web.xml里的welcome-file是不是login.jsp或index.jsp。

参数说明:-DskipTests跳过测试加快打包,源码里如果有单元测试连不上库会卡住。Tomcat 端口默认 8080,被占用就改conf/server.xml里的Connector port。部署后如果页面能开但登录报 500,九成是数据库连接没通,去看logs/catalina.out里的堆栈。

3. 拆开源码看结构:JSP、Servlet 与 MySQL 是怎么串起来的

3.1 目录分层与请求流转路径

一份规范的 JSP 挂号系统源码,目录结构基本长这样:WebContent或webapp下放jsp页面和WEB-INF,src下按dao、service、servlet、entity分包。请求从浏览器发出,先到web.xml或注解映射的 Servlet,Servlet 调 Service,Service 调 DAO,DAO 用 JDBC 查 MySQL,结果塞进 request 或 session,最后转发回 JSP 渲染。

理解这条链路,你改功能才不会乱。比如「预约挂号」这个动作,入口是AppointmentServlet,它先校验 session 里有没有登录用户,再调ScheduleService查该医生该时段剩余号源,扣减成功后写appointment表,最后重定向到「我的预约」页。任何一环断了都会表现为页面报错或数据对不上。

3.2 号源扣减的 SQL 与事务边界

挂号系统最核心也最容易出 bug 的地方就是号源扣减。如果只是先查再更新,两个用户同时抢最后一个号就会超卖。正确做法是用带条件的 UPDATE,把判断和扣减合并成一条原子语句。

-- 号源扣减:只有剩余号源大于 0 才扣,返回受影响行数判断是否成功 UPDATE schedule SET remain_count = remain_count - 1 WHERE id = #{scheduleId} AND remain_count > 0; -- 写入预约记录 INSERT INTO appointment (user_id, doctor_id, schedule_id, status, create_time) VALUES (#{userId}, #{doctorId}, #{scheduleId}, 'BOOKED', NOW());

逻辑说明:第一条 UPDATE 的remain_count > 0是并发安全的关键,数据库行锁保证同一时刻只有一个事务能扣成功,返回值为 1 表示抢到号,为 0 表示号已满。第二条 INSERT 必须在同一个事务里,否则扣了号却没记录,号源就凭空消失了。在 Java 侧用Connection.setAutoCommit(false)手动控制,两条都成功再 commit,任何一条异常就 rollback。

参数说明:#{scheduleId}是排班主键,#{userId}从 session 取,别信前端传的用户 ID,否则越权挂号。status字段建议用枚举字符串而不是数字,排查问题时一眼能看懂。create_time用NOW()让数据库生成,避免应用服务器时间不一致。

3.3 用 JDBC 连接池替掉裸连接

源码里如果每次请求都DriverManager.getConnection,并发一上来就崩。常见做法是换成 Druid 或 C3P0 连接池,改造成本很低,但稳定性提升明显。

// Druid 连接池初始化示例 DruidDataSource ds = new DruidDataSource(); ds.setUrl("jdbc:mysql://127.0.0.1:3306/hospital_reg?useUnicode=true&characterEncoding=utf8&useSSL=false"); ds.setUsername("root"); ds.setPassword("你的密码"); ds.setInitialSize(5); // 初始连接数 ds.setMaxActive(20); // 最大活跃连接 ds.setMaxWait(3000); // 获取连接超时 3 秒 ds.setValidationQuery("SELECT 1");

逻辑说明:initialSize设 5 够本地开发用,maxActive20 是中小型预约系统的合理上限,maxWait3000 毫秒能避免请求无限阻塞。validationQuery让连接池定期探活,防止 MySQL 8 小时空闲断连后拿到死连接。改完后 DAO 里从ds.getConnection()取连接,用完 close 其实是归还池子。

参数说明:生产环境maxActive要结合 Tomcat 线程数和 MySQLmax_connections一起算,别让连接池把数据库连接打满。maxWait别设太大,快速失败比拖死线程好。

4. 二次开发:把挂号系统改成你要的样子

4.1 新增一个「科室排班」管理页

毕设或课设经常要求加功能。以新增科室排班为例,你需要动四处:建表、写实体、写 DAO、加 JSP 页面和 Servlet。先在数据库加排班表字段,再在entity包加Schedule类,字段和表列一一对应。

// Schedule 实体,字段名与表列对应 public class Schedule { private Integer id; private Integer doctorId; private String workDate; // 格式 yyyy-MM-dd private String timeSlot; // 如 09:00-09:30 private Integer totalCount; private Integer remainCount; // getter/setter 省略 }

逻辑说明:实体类字段用包装类型Integer而不是int,因为数据库列可能为 NULL,用基本类型会踩空指针。workDate用 String 接收,JSP 表单提交的日期字符串直接映射,省去格式转换。DAO 里写insert、update、selectByDoctor三个方法就够支撑排班管理页。

参数说明:timeSlot建议固定粒度,比如 30 分钟一段,前端用下拉框而不是自由输入,避免出现「9 点」和「09:00」两种格式导致查询不到。remainCount初始等于totalCount,每次预约递减。

4.2 个人信息展示页面怎么接进来

热搜里常有人问 jsp个人信息展示页面 怎么做。在这套系统里,就是加一个profile.jsp,从 session 取当前用户,再查一次数据库拿最新信息,渲染到页面。别直接把 session 里的对象整个打印,session 可能是旧数据。

<%@ page contentType="text/html;charset=UTF-8" %> <% User user = (User) session.getAttribute("loginUser"); if (user == null) { response.sendRedirect("login.jsp"); return; } %> <table> <tr><td>用户名</td><td><%= user.getUsername() %></td></tr> <tr><td>手机号</td><td><%= user.getPhone() %></td></tr> <tr><td>注册时间</td><td><%= user.getCreateTime() %></td></tr> </table>

逻辑说明:开头的登录校验必须放在最前面,未登录直接跳登录页,否则会空指针。用<%= %>输出前最好做 HTML 转义,防止用户名里带脚本造成 XSS。如果要做「编辑资料」,再加一个updateProfile的 Servlet,用UPDATE user SET phone=? WHERE id=?更新,注意WHERE条件用 session 里的用户 ID。

参数说明:contentType必须是text/html;charset=UTF-8,否则中文乱码。session 属性名loginUser要和登录 Servlet 里setAttribute的 key 完全一致,大小写都不能错。

4.3 从 JSP 迁移到 SpringBoot 的取舍

现在新项目基本不用纯 JSP 了,热搜里 springboot2 集成jsp 目录 也是这个趋势。但这份源码的价值在于逻辑清晰,迁移时可以保留 DAO 和实体,把 Servlet 换成 Controller,JSP 放src/main/webapp/WEB-INF/jsp/下,加spring-boot-starter-tomcat和jasper依赖即可。迁移的收益是配置集中、依赖注入省心,代价是要重写映射和拦截器。如果只是交课设,纯 JSP 反而更省事,别为了技术新而新。

5. 避坑与排查:这份源码最容易翻车的五个地方

5.1 中文乱码:现象是页面问号,原因是编码链没统一

现象:科室名、医生姓名显示成???或乱码。原因:JSP 页面、request 解码、数据库连接、表字符集四层里有一层不是 UTF-8。解决:JSP 顶部加<%@ page contentType="text/html;charset=UTF-8" %>,Servlet 里request.setCharacterEncoding("UTF-8"),连接串加characterEncoding=utf8,建库用 utf8mb4。四层对齐后乱码必消。

5.2 数据库连不上:error 2002 与 socket 报错

现象:启动报Can't connect to local MySQL server through socket '/tmp/mysql.sock'。原因:客户端默认走 socket,而 MySQL 没在该路径生成,或服务没启动。解决:连接串改用-h127.0.0.1强制 TCP,并确认systemctl status mysql是 running。如果是 Docker 里的 MySQL,端口映射要写对,别用 localhost。

5.3 号源超卖:现象是预约数超过总号源

现象:同一时段预约人数大于排班总数。原因:先查后改,两条 SQL 之间有并发窗口。解决:改成UPDATE ... WHERE remain_count > 0的原子扣减,并用事务包住扣减和插入。压测时用 JMeter 开 50 线程打同一排班,看最终 remain_count 是否为 0 且不为负。

5.4 部署后 404:war 包名与访问路径不匹配

现象:Tomcat 启动正常,访问报 404。原因:war 包名和浏览器路径不一致,或web.xml的welcome-file指向了不存在的页面。解决:war 包名就是上下文路径,hospital.war对应/hospital/;检查web.xml里welcome-file-list的第一个文件是否真实存在。

5.5 时间字段差 8 小时:时区没对齐

现象:预约时间比实际早或晚 8 小时。原因:MySQL 时区是 UTC,Java 应用是东八区,或连接串没指定serverTimezone。解决:连接串加serverTimezone=Asia/Shanghai,MySQL 里SET GLOBAL time_zone = '+8:00',实体时间字段统一用java.util.Date并在展示层格式化。

6. 让这套源码真正可用的两个进阶技巧

第一个技巧是给号源加一层「预占」。真实挂号场景里,用户从选号到支付有几分钟窗口,直接扣减会导致选了不付、号被占死。我一般会加一张appointment_lock表,用户点预约时先写锁定记录并扣减号源,设 5 分钟过期;支付成功转正式预约,超时则由定时任务把号源加回去。定时任务用Timer或ScheduledExecutorService都行,SQL 就是UPDATE schedule SET remain_count = remain_count + 1 WHERE id IN (SELECT schedule_id FROM appointment_lock WHERE expire_time < NOW()),再删掉过期锁。这样既防超卖,又不让号源被无效占用。

第二个技巧是给关键操作留日志。挂号、退号、改排班这三类动作,我都会在 Service 层加一条operation_log记录,字段包括操作人、动作、目标 ID、时间、结果。排查线上问题时,日志比翻数据库快得多。验证方法很简单:手动挂一个号,去operation_log表看有没有对应记录,字段是否完整;再模拟一次退号,确认号源加回且日志成对出现。

-- 操作日志表,排查问题的后悔药 CREATE TABLE operation_log ( id BIGINT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, action VARCHAR(32) NOT NULL, -- BOOK / CANCEL / UPDATE_SCHEDULE target_id INT, result VARCHAR(16), -- SUCCESS / FAIL create_time DATETIME DEFAULT CURRENT_TIMESTAMP );

逻辑说明:action用短字符串枚举,方便按类型统计;target_id指向被操作的排班或预约;result记录成败,失败也要记,否则你不知道用户为什么没挂上号。这张表只增不改,定期归档即可。

参数说明:create_time用数据库默认值,避免应用时间不准。日志表别加外键,否则删用户时会被约束卡住。

我自己踩过最深的坑,是早期图省事把号源扣减写在 JSP 脚本片段里,结果页面一刷新就重复扣,查了半天才发现是 JSP 被重复执行。从那以后,凡是写库的操作一律放 Servlet 或 Service,JSP 只负责展示。这个习惯帮我省了无数个加班的夜晚。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询