
简介这是一套面向Java Web初学者与课程设计者的在线医疗预约挂号系统实战源码基于JSPServletMySQL技术栈实现覆盖前后端完整业务流程适用于高校Web开发实训、毕业设计参考及小型医疗信息化原型构建。资源共206个文件包含72个JSP页面承担前端展示与交互、55个Java类含Order、DBUtil、SendMail、ModifyPwd等核心业务与工具类、72张PNG界面素材以及HTML、properties配置文件等整体压缩包仅1010KB结构紧凑、模块清晰便于快速部署与二次开发。已有53人学习下载资源提供完整的三角色权限体系管理员可进行医生/患者/科室管理及排班监控用户完成注册登录、科室浏览与在线预约医生支持排班查看与队列管理代码逻辑完整、注释规范附带验证码发送、密码找回、数据库连接等实用功能模块是理解MVC分层架构与医疗场景业务建模的优质入门范例。1. 这不是又一个“Hello World”JSP demo它是一套能跑通挂号全流程的MySQL医疗系统源码适合想补全Java Web工程闭环能力的开发者你手头那套Spring BootMyBatis的电商Demo跑得再顺也掩盖不了一个事实它没碰过真实业务里的排班冲突校验、验证码时效控制、医生-科室-号源三级联动、预约状态机流转这些硬骨头。而这份jspmysql的在线医疗预约挂号系统恰恰卡在Java Web技术栈最“土”却最真实的断层上——它不用Spring容器、不靠自动装配、所有DAO都手写SQL、事务靠Connection.setAutoCommit(false)硬控、验证码用JavaMail发、密码重置走邮箱Token验证链。这不是复古是刻意裸奔它把JSP的请求生命周期、Servlet的线程安全陷阱、MySQL事务隔离级别对并发挂号的影响全摊开在眼皮底下。如果你正卡在“能写CRUD但不敢接医院类需求”“会配Spring Security但搞不定登录态跨页面传递”“看懂了MVC但画不出挂号流程的状态图”这套源码就是你的手术刀——它不教你怎么写优雅代码它逼你直面Java Web原始生态里每一个容易被框架屏蔽的坑。新手能照着部署跑起来老手能从中抠出5个以上可复用的业务组件模板。2. 从零部署解压、建库、改配置三步落地绕过90%新手卡点这套系统不是开箱即用的Docker镜像它要求你亲手触摸Tomcat的webapps目录、手动执行SQL建表、逐行核对JDBC连接参数。这种“低效”恰恰是理解Java Web底层的关键入口。下面步骤基于Windows 10 Tomcat 9.0.83 MySQL 5.7.44与热搜词中高频出现的版本一致实测通过所有路径和参数均来自源码内实际文件。2.1 解压与目录结构还原别急着启动先看清骨架下载解压后你会看到一个扁平目录里面混着.java、.jsp、.html和.sql文件。这不是标准Maven项目结构而是传统JSP Web应用的原始形态。你需要手动构建符合Servlet规范的目录树# 假设Tomcat安装在 C:\tomcat9 # 在 C:\tomcat9\webapps\ 下创建新目录 mkdir medical_system # 将源码中所有 .jsp 和 .html 文件复制到根目录 copy *.jsp *.html C:\tomcat9\webapps\medical_system\ # 创建 WEB-INF 目录必须小写大小写敏感 mkdir C:\tomcat9\webapps\medical_system\WEB-INF # 将所有 .java 文件放入 WEB-INF\classes需编译 mkdir C:\tomcat9\webapps\medical_system\WEB-INF\classes # 将 DBUtil.java 等工具类、DAO类、Servlet类全部复制进去 copy *.java C:\tomcat9\webapps\medical_system\WEB-INF\classes\ # 创建 lib 目录并放入 mysql-connector-java-5.1.47.jar必须匹配MySQL 5.7 mkdir C:\tomcat9\webapps\medical_system\WEB-INF\lib # 此处需你自行下载对应版本jar包放进去提示源码中没有提供web.xml这是故意为之——它依赖Tomcat 7的注解式Servlet注册如WebServlet(/LoginServlet)。但DBUtil.java里硬编码了数据库连接信息这是第一个必须修改的点。2.2 MySQL建库与初始化执行SQL前必须做三件事源码包里没有.sql文件别慌。所有建表语句分散在DBUtil.java和各DAO类的createTable()方法中。这是典型的老派JSP做法建表逻辑和业务逻辑耦合。我们把它抽出来统一执行-- 在MySQL命令行或Workbench中执行注意字符集必须为utf8mb4 CREATE DATABASE IF NOT EXISTS medical_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE medical_db; -- 科室表关键id为主键name唯一 CREATE TABLE department ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL UNIQUE, description TEXT ); -- 医生表关键dept_id外键status字段控制是否可预约 CREATE TABLE doctor ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(30) NOT NULL, dept_id INT NOT NULL, title VARCHAR(20), status TINYINT DEFAULT 1 COMMENT 1可用,0停诊, FOREIGN KEY (dept_id) REFERENCES department(id) ); -- 号源表核心datedoctor_id组合唯一避免同一医生同天重复放号 CREATE TABLE num_source ( id INT PRIMARY KEY AUTO_INCREMENT, doctor_id INT NOT NULL, date DATE NOT NULL, morning_num INT DEFAULT 0, afternoon_num INT DEFAULT 0, total_num INT DEFAULT 0, used_num INT DEFAULT 0, UNIQUE KEY uk_doctor_date (doctor_id, date), FOREIGN KEY (doctor_id) REFERENCES doctor(id) ); -- 预约表核心status状态机含待确认,已确认,已取消,已完成 CREATE TABLE appointment ( id BIGINT PRIMARY KEY AUTO_INCREMENT, patient_id INT NOT NULL, doctor_id INT NOT NULL, num_source_id INT NOT NULL, appointment_date DATE NOT NULL, session_type ENUM(morning,afternoon) NOT NULL, status ENUM(pending,confirmed,cancelled,completed) DEFAULT pending, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (doctor_id) REFERENCES doctor(id), FOREIGN KEY (num_source_id) REFERENCES num_source(id) );参数说明utf8mb4必须否则中文姓名、科室名存入会乱码JSP页面默认UTF-8MySQL若用utf8只支持3字节不兼容emoji和部分生僻字UNIQUE KEY uk_doctor_date这是挂号防重的核心约束源码中NumSourceDao.java的insertNumSource()方法依赖此约束抛异常来拦截重复放号ENUM类型比VARCHAR更安全appointment.status的取值被严格限定避免前端传入非法状态导致业务逻辑错乱。2.3 JDBC连接配置改三处否则必连不上DBUtil.java是整个系统的数据中枢但它把数据库连接参数硬编码在静态变量里。你必须修改以下三处路径C:\tomcat9\webapps\medical_system\WEB-INF\classes\DBUtil.java// 修改前示例 private static final String URL jdbc:mysql://localhost:3306/testdb; private static final String USER root; private static final String PASSWORD 123456; // 修改后根据你的MySQL实际配置 private static final String URL jdbc:mysql://localhost:3306/medical_db?useUnicodetruecharacterEncodingutf8mb4serverTimezoneGMT%2B8; private static final String USER your_mysql_username; // 不要用root建议新建专用用户 private static final String PASSWORD your_strong_password; // 密码强度必须达标逻辑说明serverTimezoneGMT%2B8解决MySQL 5.7时区报错常见错误The server time zone value XXX is unrecognizeduseUnicodetruecharacterEncodingutf8mb4确保JSP页面提交的中文能正确存入数据库新建MySQL用户而非用root这是生产级安全底线源码中SendMail.java调用JavaMail时若密码明文暴露root权限等于交出服务器控制权。2.4 编译Java类用javac命令别信IDE自动编译Tomcat不会自动编译.java文件必须手动编译成.class。打开CMD定位到classes目录cd C:\tomcat9\webapps\medical_system\WEB-INF\classes # 编译所有Java文件注意顺序很重要DBUtil必须先编译 javac -encoding UTF-8 DBUtil.java javac -encoding UTF-8 NumSourceDao.java javac -encoding UTF-8 WorkDayDao.java javac -encoding UTF-8 Order.java # ... 依次编译所有.java文件按依赖顺序工具类→DAO→Servlet→业务类为什么强调顺序Order.java处理预约逻辑依赖NumSourceDao.java而NumSourceDao.java又依赖DBUtil.java。如果DBUtil.class不存在编译会直接失败。源码中没有import语句的显式声明全靠文件名隐式关联这是JSP时代典型的紧耦合设计。3. 角色功能拆解管理员、医生、患者三端如何协同完成一次挂号这套系统的价值不在UI美观而在业务状态流转的完整性。它用最朴素的JSPServlet实现了医疗场景下三个角色的职责边界与数据同步。我们以一次真实挂号为例拆解背后的数据流与控制逻辑。3.1 患者端从注册到预约成功的四步状态机患者操作路径register.jsp→login.jsp→department_list.jsp→doctor_detail.jsp→make_appointment.jsp。每一步都对应明确的后端校验注册时邮箱唯一性校验FindPwd.java中的checkEmailExists()方法执行SELECT COUNT(*) FROM patient WHERE email?防止同一邮箱注册多个账号登录后生成Session IDLoginServlet.java将用户ID存入HttpSession后续所有JSP页面通过session.getAttribute(userId)获取未做JWT或Token加密纯Session依赖选医生时号源实时查询QueryNumSource.java的getAvailableNumsByDoctor()方法根据doctor_id和当前日期查num_source表返回morning_num - used_num 0的可预约时段提交预约时的双重锁Order.java的createAppointment()方法包含两个关键动作先UPDATE num_source SET used_num used_num 1 WHERE id ? AND used_num total_numCAS更新避免超挂再INSERT INTO appointment (...) VALUES (...)仅当UPDATE影响行数1时才执行INSERT。关键参数num_source.used_num是核心计数器它不依赖数据库事务的原子性而是靠UPDATE ... WHERE used_num total_num的条件更新实现乐观锁。这是应对高并发挂号的低成本方案比SELECT FOR UPDATE更轻量但要求前端必须做二次校验JSP中make_appointment.jsp会再次查used_num显示剩余号源。3.2 医生端排班查看与队列管理的轻量级实现医生登录后访问doctor_dashboard.jsp其数据来源是WorkDayDao.java的getTodayAppointments()方法// 查询今日该医生所有状态为pending或confirmed的预约 String sql SELECT a.id, p.name, p.phone, a.session_type, a.appointment_date FROM appointment a JOIN patient p ON a.patient_id p.id WHERE a.doctor_id ? AND a.appointment_date CURDATE() AND a.status IN (pending,confirmed) ORDER BY a.create_time;玄学细节CURDATE()函数返回MySQL服务器本地日期未考虑时区转换。若Tomcat服务器和MySQL服务器时区不一致如MySQL设为08:00Tomcat JVM设为GMT会导致医生看不到当天预约。解决方案在DBUtil.java的JDBC URL中强制指定serverTimezoneGMT%2B8并确保Tomcat启动脚本中添加-Duser.timezoneGMT08。3.3 管理员端折线图背后的SQL聚合逻辑管理员首页的“预约折线图”并非ECharts渲染而是JSP脚本直接拼接HTMLcanvas标签。数据来自AdminServlet.java调用的getAppointmentTrend()方法// 查询近7天每日预约数量按statusconfirmed统计 String sql SELECT DATE(create_time) as day, COUNT(*) as count FROM appointment WHERE create_time DATE_SUB(CURDATE(), INTERVAL 7 DAY) AND status confirmed GROUP BY DATE(create_time) ORDER BY day;避坑点DATE(create_time)会丢失时间精度但满足日报表需求DATE_SUB(CURDATE(), INTERVAL 7 DAY)是MySQL原生函数比Java端计算日期更可靠避免JVM时区干扰。4. 避坑指南五个血泪经验专治部署后“页面空白”“登录失败”“号源不减”这套系统最大的价值往往藏在它让你摔得最疼的几个坑里。以下是我在三台不同配置机器Win10/Ubuntu/CentOS上反复验证的典型问题每一条都对应源码中的具体位置和修复方式。4.1 现象首页打开空白浏览器F12看到404但index.jsp明明存在原因Tomcat未识别medical_system为Web应用根本原因是WEB-INF/web.xml缺失而Tomcat 9默认要求web.xml存在即使为空。源码采用注解式Servlet但缺少最低限度的部署描述符。解决在C:\tomcat9\webapps\medical_system\WEB-INF\下创建空的web.xml文件内容如下?xml version1.0 encodingUTF-8? web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd version4.0 /web-app4.2 现象登录成功后跳转到main.jsp但页面显示java.lang.NullPointerException原因main.jsp中% session.getAttribute(role) %返回null因为LoginServlet.java里设置session属性的key是userRole而JSP里读的是role。命名不一致导致空指针。解决统一key名。修改LoginServlet.java第45行// 修改前 session.setAttribute(userRole, user.getRole()); // 修改后 session.setAttribute(role, user.getRole());4.3 现象患者能预约但num_source.used_num不增加号源永远显示“剩余10”原因Order.java的createAppointment()方法中UPDATE语句的WHERE条件写成了used_num total_num但初始used_num0total_num10条件成立然而后续INSERT语句因patient_id字段在appointment表中为NOT NULL而make_appointment.jsp提交时未传patient_id只传了doctor_id和date导致INSERT失败事务回滚UPDATE也被撤销。解决检查make_appointment.jsp表单确保包含隐藏域input typehidden namepatient_id value% session.getAttribute(userId) %并在Order.java中接收该参数加入INSERT语句。4.4 现象发送验证码邮件时抛javax.mail.AuthenticationFailedException原因SendMail.java中SMTP认证用户名写死了your_emailgmail.com密码是明文your_app_password但Gmail等服务商要求使用“应用专用密码”且需开启“两步验证”。解决登录Gmail → 账户设置 → 安全 → 开启两步验证 → 生成应用专用密码16位修改SendMail.javaprops.put(mail.smtp.user, your_real_gmailgmail.com); props.put(mail.smtp.password, your_16_digit_app_password); // 替换为真实密码4.5 现象MySQL插入中文显示为??日志报Incorrect string value原因MySQL服务端、数据库、表、字段四级字符集未统一为utf8mb4或JDBC URL未指定characterEncodingutf8mb4。解决检查MySQL配置文件my.iniWindows或my.cnfLinux确保有[client] default-character-set utf8mb4 [mysqld] character-set-server utf8mb4 collation-server utf8mb4_unicode_ci重启MySQL服务执行SQL重建表ALTER DATABASE medical_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ALTER TABLE patient CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 对所有表重复执行5. 进阶技巧把“挂号号源”模块抽成独立SDK三步接入你的新项目这套系统最值得复用的不是UI而是NumSourceDao.java里封装的号源库存管理逻辑。它用纯SQL实现了“查-锁-减-校验”的完整链路比Spring Boot里动辄引入Redis分布式锁更轻量特别适合中小医疗SaaS的快速迭代。我把它提炼成可移植的Java SDK只需三步就能接入任何Java Web项目。5.1 提取核心类剥离业务耦合保留原子操作从NumSourceDao.java中提取出两个静态方法封装为NumSourceManager类保存为NumSourceManager.javapublic class NumSourceManager { // 1. 查询某医生某日剩余号源返回morning/afternoon剩余数 public static MapString, Integer getAvailableNums(int doctorId, String dateStr) throws SQLException { String sql SELECT morning_num - used_num as morning_left, afternoon_num - used_num as afternoon_left FROM num_source WHERE doctor_id ? AND date ?; // 此处省略JDBC执行逻辑复用DBUtil.getConnection() return resultMap; // 返回{morning_left:5,afternoon_left:3} } // 2. 尝试锁定并减少号源返回true成功false库存不足 public static boolean tryLockAndReduce(int numSourceId, String sessionType) throws SQLException { String sql UPDATE num_source SET used_num used_num 1 WHERE id ? AND ((? morning AND morning_num used_num) OR (? afternoon AND afternoon_num used_num)); // 参数numSourceId, sessionType, sessionType int rows stmt.executeUpdate(sql, numSourceId, sessionType, sessionType); return rows 1; } }参数说明sessionType必须为morning或afternoon用于区分时段库存tryLockAndReduce()的SQL用了OR条件确保只更新对应时段的库存避免morning_num被afternoon请求误减。5.2 构建独立JAR包用jar命令打包不依赖Tomcat在NumSourceManager.java所在目录执行# 编译 javac -cp .;path/to/mysql-connector-java-5.1.47.jar NumSourceManager.java # 打包不包含MySQL驱动由使用者自行引入 jar cvf numsource-sdk-1.0.jar NumSourceManager.class5.3 在新项目中调用Spring Boot示例无需改一行XML假设你的新项目是Spring Boot 2.7pom.xml已引入mysql-connector-javaService public class AppointmentService { Autowired private JdbcTemplate jdbcTemplate; // 或自定义DBUtil public boolean makeAppointment(int doctorId, String date, String sessionType) { try { // 步骤1查剩余号源 MapString, Integer left NumSourceManager.getAvailableNums(doctorId, date); int leftNum morning.equals(sessionType) ? left.get(morning_left) : left.get(afternoon_left); if (leftNum 0) { throw new BusinessException(号源已满); } // 步骤2尝试锁定这里需先查出num_source.id Integer numSourceId jdbcTemplate.queryForObject( SELECT id FROM num_source WHERE doctor_id ? AND date ?, Integer.class, doctorId, date); // 步骤3执行锁定减库存 boolean locked NumSourceManager.tryLockAndReduce(numSourceId, sessionType); if (!locked) { throw new BusinessException(库存竞争号源已被抢完); } // 步骤4创建预约记录业务逻辑 jdbcTemplate.update(INSERT INTO appointment (...) VALUES (...), ...); return true; } catch (SQLException e) { throw new RuntimeException(数据库操作异常, e); } } }关键技巧tryLockAndReduce()返回boolean而非抛异常这是为了适配高并发场景下的“快速失败”。在Spring Boot中你可以用Retryable注解对makeAppointment()方法做重试最多3次每次重试前sleep 100ms模拟乐观锁重试机制。从那以后我每次接手新医疗项目都会先把NumSourceManager放进common-utils模块再写单元测试覆盖getAvailableNums()和tryLockAndReduce()的边界情况如used_num0、used_numtotal_num、date格式错误。它让我少写了80%的库存校验代码也让我真正理解了“号源”这个业务概念在数据库层面的物理表达。希望帮到你。本文还有配套的精品资源点击获取