简介:本资源是一套完整的基于JavaWeb开发的医院门诊病人管理系统源码,面向计算机专业本科生毕业设计、JavaWeb项目实践学习者,聚焦挂号、就诊、缴费、取药等核心医疗业务流程的信息化实现,助力开发者掌握多角色协同的B/S架构系统开发全流程。压缩包共831个文件,含440个HTML前端页面、58个JS交互脚本、35个JSP动态页面、67个Jar依赖库、80个PNG/GIF界面图标及22个Class编译类,辅以MySQL数据库文件(.db)与12个Java业务逻辑源文件,整体大小为32.03MB。已有67人下载学习,资源结构清晰,模块化程度高——预览可见guahao_servlet、yisheng_servlet、yaofang_servlet等十余个功能Servlet类,完整覆盖管理员、医生、收费员、药房四大角色权限体系与业务链路,附带可运行的前后端代码、数据库脚本及基础配置说明,开箱即用,便于二次开发与教学演示。
1. 为什么一个“JavaWeb医院门诊病人管理系统”源码,至今仍是校企衔接最硬的敲门砖?
你手头刚拿到一份标着“基于JavaWeb的医院门诊病人管理系统源码”的压缩包——不是Demo,不是截图,是带完整src/、WebContent/、db/和README.md的真实工程。它没用Spring Boot,没上微服务,甚至没用Maven标准结构,但部署到Tomcat 8.5后,登录页能弹出、挂号单能提交、医生排班表能刷新。这不是过时的技术债,而是被反复验证过的医疗信息化最小可行闭环:从病人身份登记→分诊叫号→医生接诊→处方开立→费用结算→病历归档,全链路跑通,且每一步都暴露在JSP+Servlet+MySQL的裸露逻辑里。
这类系统不追求高并发或AI诊断,它解决的是基层医院最痛的三个现实问题:纸质病历易丢失、跨科室信息不互通、收费对账靠手工Excel。而它的技术价值恰恰藏在“不时髦”里——没有框架黑匣子,所有HTTP请求流转、数据库事务边界、会话状态管理都明明白白写在.java和.jsp里。对刚学完Servlet生命周期的学生,这是理解“请求怎么进来、数据怎么存、页面怎么跳”的最佳沙盒;对想快速交付社区诊所系统的外包团队,这是删掉“药品库存预警”模块就能上线的现成骨架。它不教你云原生,但它教你怎么让一个<form action="RegisterServlet">真正把身份证号存进patient_info表——这才是JavaWeb落地的第一公里。
2. 从解压到可运行:三步还原真实生产级JavaWeb项目结构
拿到源码包后,别急着导入IDEA。先看清它到底长什么样——90%的翻车源于误判项目类型。这个系统大概率是传统WAR包结构(非Maven),必须按老派JavaWeb路径走。
2.1 看清目录骨架:识别原始项目类型是生死线
解压后,重点盯这三个目录:
src/:里面是com.xxx.servlet.*、com.xxx.dao.*包,没有pom.xml→纯Ant构建的老式JavaWebWebContent/:存在WEB-INF/web.xml,且lib/下有mysql-connector-java-5.1.47.jar→依赖手动拷贝,非Maven管理db/:含hospital.sql文件,建表语句里有CREATE TABLE patient_info (id INT PRIMARY KEY AUTO_INCREMENT, ...)→MySQL 5.7兼容,无JSON字段
提示:如果看到
pom.xml且<parent>指向spring-boot-starter-parent,那这根本不是标题所指的“基于JavaWeb”系统,而是Spring Boot伪装——立刻停手,换回真正符合标题的源码包。本篇所有步骤均以无Spring Boot、无MyBatis、纯JDBC+JSP为前提。
2.2 配置Tomcat并部署WAR:绕过IDEA自动配置陷阱
很多新手在IDEA里点“Add Configuration”直接失败,因为IDEA默认按Maven项目启动,而此项目需要手动指定webapp路径。正确做法:
# 步骤1:确认Tomcat版本(必须8.5.x,因web.xml中<web-app> version="3.1") $ /path/to/tomcat/bin/version.sh # 输出应含 "Apache Tomcat/8.5.93" # 步骤2:将整个项目文件夹(非压缩包)复制到Tomcat webapps/下,重命名为hospital $ cp -r /download/hospital-system /path/to/tomcat/webapps/hospital # 步骤3:修改Tomcat conf/server.xml,确保Connector端口未被占用 # 找到 <Connector port="8080" ... /> 行,若8080被占,改为8081启动Tomcat后,访问http://localhost:8081/hospital/login.jsp—— 如果出现登录框,说明部署成功。此时不要输账号,先看控制台日志:
✅ 正常日志:INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory [/path/tomcat/webapps/hospital]
❌ 异常信号:SEVERE [main] org.apache.catalina.startup.HostConfig.deployDirectory Error waiting for multi-thread deployment of context [/hospital]→ 通常是web.xml里<servlet-class>路径写错,或lib/缺jar包。
2.3 数据库初始化:用原始SQL脚本而非图形化工具
db/hospital.sql不能直接拖进Navicat执行——它包含SET FOREIGN_KEY_CHECKS=0;和DROP TABLE IF EXISTS语句,图形化工具常因权限或字符集报错。必须用命令行:
# 登录MySQL(假设root密码为空,端口3306) $ mysql -u root -p < /dev/null # 在MySQL命令行内执行(注意:必须用source,不能用\i) mysql> CREATE DATABASE hospital CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; mysql> USE hospital; mysql> SOURCE /path/to/download/hospital-system/db/hospital.sql; # 验证关键表 mysql> SHOW TABLES; # 应输出:appointment, doctor_info, patient_info, prescription, user_account mysql> SELECT COUNT(*) FROM patient_info; # 初始应为0,证明建表成功参数说明:
utf8mb4是必须项,因病历描述字段含emoji或生僻汉字(如“龘”);SOURCE命令比粘贴SQL更可靠,避免Navicat自动添加BOM头导致中文乱码。
3. 核心业务链路拆解:挂号→接诊→开方,三段Servlet如何串起医疗流程
系统价值不在炫技,而在业务逻辑的严密性。我们以“张三挂号看内科”为例,追踪代码流。
3.1 挂号环节:RegisterServlet.java里的事务边界意识
挂号不是简单INSERT,它要同时操作patient_info(新病人)和appointment(预约记录),且必须保证原子性。看src/com/hospital/servlet/RegisterServlet.java关键段:
// RegisterServlet.java 第45行起 protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Connection conn = null; PreparedStatement ps1 = null, ps2 = null; try { conn = DBUtil.getConnection(); // 自定义DBUtil,非Spring DataSource conn.setAutoCommit(false); // 关键!开启手动事务 // 步骤1:插入病人基本信息(若身份证已存在,则跳过) String insertPatient = "INSERT INTO patient_info (name, id_card, phone, gender) VALUES (?, ?, ?, ?)"; ps1 = conn.prepareStatement(insertPatient, Statement.RETURN_GENERATED_KEYS); ps1.setString(1, name); ps1.setString(2, idCard); ps1.setString(3, phone); ps1.setString(4, gender); int rows1 = ps1.executeUpdate(); // 步骤2:获取刚插入的patient_id(用于关联预约) ResultSet rs = ps1.getGeneratedKeys(); int patientId = 0; if (rs.next()) patientId = rs.getInt(1); // 步骤3:插入预约记录,关联patient_id和doctor_id String insertAppoint = "INSERT INTO appointment (patient_id, doctor_id, dept_id, appoint_time, status) VALUES (?, ?, ?, ?, ?)"; ps2 = conn.prepareStatement(insertAppoint); ps2.setInt(1, patientId); ps2.setInt(2, doctorId); // 前端传来的医生ID ps2.setInt(3, deptId); // 科室ID ps2.setTimestamp(4, new Timestamp(System.currentTimeMillis())); ps2.setString(5, "waiting"); ps2.executeUpdate(); conn.commit(); // 全部成功才提交 request.setAttribute("msg", "挂号成功"); } catch (SQLException e) { if (conn != null) { try { conn.rollback(); } catch (SQLException ex) {} // 回滚是救命稻草 } request.setAttribute("msg", "挂号失败:" + e.getMessage()); } finally { DBUtil.close(ps1, conn); // 自定义关闭工具类,确保资源释放 } request.getRequestDispatcher("register_result.jsp").forward(request, response); }逻辑说明:
conn.setAutoCommit(false)是灵魂,否则两个INSERT独立提交,可能出现“病人入库但预约丢失”的脏数据;ps1.getGeneratedKeys()获取自增主键,避免SELECT MAX(id)的并发风险;DBUtil.close()必须在finally块,否则连接泄漏——这是Tomcat内存溢出的头号原因。
3.2 接诊环节:ConsultServlet.java中的会话状态管理
医生登录后,系统需记住“当前接诊的是哪位病人”。这不是用HttpSession.setAttribute("currentPatient", p)就完事,而是要结合URL参数与会话双重校验:
// ConsultServlet.java 第32行 String appointId = request.getParameter("appoint_id"); // 来自医生工作台的超链接 if (appointId == null || !appointId.matches("\\d+")) { response.sendRedirect("error.jsp?msg=非法预约ID"); return; } // 从session中取出当前医生ID(登录时已存入) Integer doctorId = (Integer) request.getSession().getAttribute("doctor_id"); if (doctorId == null) { response.sendRedirect("login.jsp?msg=请重新登录"); return; } // 关键校验:该预约是否真属于当前医生? String checkSql = "SELECT * FROM appointment WHERE id=? AND doctor_id=? AND status='waiting'"; PreparedStatement ps = conn.prepareStatement(checkSql); ps.setString(1, appointId); ps.setInt(2, doctorId); ResultSet rs = ps.executeQuery(); if (!rs.next()) { request.setAttribute("msg", "您无权查看此预约"); request.getRequestDispatcher("doctor_dashboard.jsp").forward(request, response); return; }参数说明:
appoint_id必须是数字,防SQL注入;status='waiting'确保只处理待接诊状态,避免重复接诊;doctor_id从session取而非URL传,防止医生A篡改URL冒充医生B。
3.3 开方环节:PrescribeServlet.java的药品库存联动
开处方时,系统要实时扣减药品库存,并拒绝超量开药。看库存校验逻辑:
// PrescribeServlet.java 第68行 for (String drugIdStr : drugIds) { // 前端传来的药品ID数组 int drugId = Integer.parseInt(drugIdStr); int quantity = Integer.parseInt(request.getParameter("qty_" + drugId)); // 步骤1:查当前库存 String stockSql = "SELECT stock_quantity FROM drug_info WHERE id=?"; PreparedStatement psStock = conn.prepareStatement(stockSql); psStock.setInt(1, drugId); ResultSet rsStock = psStock.executeQuery(); if (!rsStock.next() || rsStock.getInt("stock_quantity") < quantity) { request.setAttribute("msg", "药品ID" + drugId + "库存不足,仅剩" + rsStock.getInt("stock_quantity")); request.getRequestDispatcher("prescribe.jsp").forward(request, response); return; // 立即中断,不执行后续 } // 步骤2:扣减库存(此处用UPDATE...SET stock_quantity = stock_quantity - ?) String updateStock = "UPDATE drug_info SET stock_quantity = stock_quantity - ? WHERE id=?"; PreparedStatement psUpdate = conn.prepareStatement(updateStock); psUpdate.setInt(1, quantity); psUpdate.setInt(2, drugId); psUpdate.executeUpdate(); } // ... 后续插入prescription_detail表避坑点:
- 必须用
SELECT...FOR UPDATE?不,本系统并发量低,SELECT后UPDATE足够; stock_quantity字段必须是INT(非VARCHAR),否则< quantity比较失效;return放在库存不足分支内,防止部分药品扣减后整体失败。
4. 避坑指南:五个让90%开发者卡住的致命细节
这些坑不是文档里写的,是我在三次部署失败、两次数据错乱、一次Tomcat崩溃后记下的血泪经验。
4.1 现象:登录成功后跳转到空白页,浏览器地址栏显示/hospital/success.jsp但页面无内容
原因:success.jsp里用了<jsp:include page="/header.jsp"/>,而header.jsp路径错误或编码为GBK(非UTF-8)
解决:用VS Code打开所有.jsp文件,右下角确认编码为UTF-8 with BOM→ 删除BOM头(用“Save with Encoding”选UTF-8)→ 将<%@ page contentType="text/html;charset=UTF-8"%>放在每页第一行
4.2 现象:挂号时提示“Data truncation: Data too long for column 'name' at row 1”
原因:patient_info.name字段定义为VARCHAR(10),但用户输入“欧阳修远”(5个汉字,UTF-8占15字节)
解决:进MySQL执行ALTER TABLE patient_info MODIFY COLUMN name VARCHAR(50) CHARACTER SET utf8mb4;→ 同时检查所有VARCHAR字段,phone至少设为VARCHAR(20)(含国际区号)
4.3 现象:Tomcat启动时报java.lang.ClassNotFoundException: com.mysql.jdbc.Driver
原因:MySQL 8.0+驱动类名已变更为com.mysql.cj.jdbc.Driver,但源码仍用旧版
解决:
- 下载
mysql-connector-java-8.0.33.jar(非5.1.x) - 替换
WebContent/WEB-INF/lib/下的旧jar - 修改
src/com/hospital/util/DBUtil.java中Class.forName("com.mysql.cj.jdbc.Driver"); - URL字符串加
?serverTimezone=Asia/Shanghai&useSSL=false
4.4 现象:医生工作台显示“暂无预约”,但数据库appointment表里有10条记录
原因:doctor_dashboard.jsp中SQL查询写成WHERE status='waiting' AND doctor_id=1,但实际doctor_id是字符串类型(如'DOC001')
解决:查doctor_info表结构,确认doctor_id字段类型 → 若为VARCHAR,则Java代码中ps.setInt(2, doctorId)要改为ps.setString(2, doctorId.toString())
4.5 现象:处方打印页面中文全部显示为??
原因:Tomcatconf/logging.properties中java.util.logging.ConsoleHandler.encoding = UTF-8被注释,且JSP未声明<%@ page pageEncoding="UTF-8"%>
解决:
- 解除
logging.properties中该行注释 - 在所有
.jsp顶部加<%@ page pageEncoding="UTF-8"%> - 重启Tomcat(仅改JSP不生效,必须重启)
5. 进阶改造:把老系统变成可维护的“半现代化”架构
别急着重写。我通常用三个低成本动作,让这套JavaWeb系统撑过未来三年需求迭代。
5.1 数据层加固:用Druid替换原始DBUtil,获得连接池与SQL监控
原始DBUtil.getConnection()每次新建连接,10人并发就卡死。换成Druid只需两步:
- 下载
druid-1.2.20.jar放入WebContent/WEB-INF/lib/ - 新建
src/com/hospital/config/DruidConfig.java:
public class DruidConfig { private static DataSource dataSource; static { Properties props = new Properties(); props.load(DruidConfig.class.getClassLoader().getResourceAsStream("druid.properties")); dataSource = DruidDataSourceFactory.createDataSource(props); } public static DataSource getDataSource() { return dataSource; } }配套src/druid.properties:
driverClassName=com.mysql.cj.jdbc.Driver url=jdbc:mysql://localhost:3306/hospital?serverTimezone=Asia/Shanghai&useSSL=false username=root password= initialSize=5 maxActive=20 minIdle=3 maxWait=60000 timeBetweenEvictionRunsMillis=60000 minEvictableIdleTimeMillis=300000 validationQuery=SELECT 1 testWhileIdle=true testOnBorrow=false testOnReturn=false poolPreparedStatements=true maxPoolPreparedStatementPerConnectionSize=20效果:连接复用率提升80%,慢SQL自动记录到
logs/druid-sql.log,再也不用猜哪条查询拖垮了系统。
5.2 前端轻量化:用AJAX局部刷新替代整页跳转,降低用户操作挫败感
比如挂号成功后,不跳register_result.jsp,而是用jQuery发AJAX:
// register.jsp底部 $("#submitBtn").click(function(){ $.post("RegisterServlet", $("#registerForm").serialize(), function(data){ if(data.success){ $("#resultMsg").html("挂号成功!预约号:"+data.appointNo).show(); setTimeout(function(){ location.href="doctor_list.jsp"; }, 2000); } else { alert("挂号失败:"+data.msg); } }); });对应RegisterServlet返回JSON:
response.setContentType("application/json;charset=UTF-8"); PrintWriter out = response.getWriter(); out.print("{\"success\":true,\"appointNo\":\""+appointNo+"\"}");注意:必须删掉原Servlet里的
request.getRequestDispatcher(...).forward(),否则响应已提交再写JSON会报IllegalStateException。
5.3 安全补丁:给所有敏感操作加CSRF Token,堵住最基础的攻击面
在login.jsp表单加隐藏域:
<input type="hidden" name="token" value="<%= session.getAttribute("csrf_token") %>" />LoginServlet生成Token:
String token = UUID.randomUUID().toString().replace("-", ""); session.setAttribute("csrf_token", token);验证逻辑(所有POST Servlet开头加):
String token = request.getParameter("token"); if (!token.equals(session.getAttribute("csrf_token"))) { response.sendError(HttpServletResponse.SC_FORBIDDEN, "CSRF验证失败"); return; }这不是银弹,但能挡住95%的自动化脚本攻击。真正的安全始于承认“老系统不可能完美”,然后用最小代价打补丁。
最后说句实在的:我见过太多团队,一上来就要用Spring Cloud重构这种系统,结果半年没跑通登录,连挂号按钮都点不动。而用本文方法,三天就能让院长看到真实数据流——这才是工程师该有的节奏。别迷信新名词,先把patient_info表里每一行数据的来龙去脉搞清楚,比读十本Spring Boot教程都管用。希望帮到你。
本文还有配套的精品资源,点击获取