Java Swing+MySQL医院预约系统源码(病人/科室管理员/超级管理员三角色)
简介:一套开箱即用的医院预约管理桌面程序,用Java Swing开发,后端基于MySQL数据库。系统划分三个角色:病人可凭身份证注册登录,查看医生排班、按时间段预约就诊,并查询自己的预约历史;科室管理员负责维护本科室医生信息(姓名、职称、专长等)、设置每日出诊时段和最大接诊人数,还能导出指定日期的预约明细;超级管理员统一管理病人、医生、科室、管理员等基础数据,支持增删改查全操作。资源包包含完整Eclipse工程(含src源码、bin编译文件、.classpath和.project配置)、MySQL建表及初始化脚本(含测试数据)、配套演示PPT、所需jgoodies第三方jar包。所有功能在本地Windows环境经Eclipse+JDK8+MySQL5.7验证可直接运行,适合数据库原理或Java课程设计作业参考。
1. 项目概述:为什么这个课设值得你花时间细读?
如果你正在为《数据库原理》或《Java程序设计》课程设计发愁,或者刚学完Swing想找个有血有肉的桌面项目练手,那这套“医院预约系统”大概率就是你翻遍CSDN、GitHub和各种课程作业仓库后,最可能停下来认真看下去的那个。它不是那种只有一张登录界面、点进去就弹窗“功能开发中”的半成品;也不是堆砌了二十个JFrame却连数据库连接都写死在代码里的教学Demo。它是一个真正跑得起来、角色分得清、逻辑压得实、数据对得上的小型生产级桌面应用——虽然规模小,但麻雀虽小,五脏俱全。
我带过三届Java实训课,每年都有学生卡在“权限怎么分”“Swing怎么和MySQL联动不卡UI”“医生排班和预约冲突怎么校验”这几个坎上。而这个项目,恰恰把这三块最难啃的骨头,用最朴素、最贴近教学场景的方式啃下来了。病人注册时身份证号必填、编号自动生成——这不是为了炫技,而是逼你去理解主键策略与业务规则的耦合;科室管理员设置“每日最大接诊人数”,系统在预约提交瞬间做实时校验——背后是事务隔离级别+SELECT FOR UPDATE的实战落地;超级管理员能删医生,但删之前必须检查该医生名下是否有未完成预约——这是外键约束与业务层双重防护的真实体现。
关键词里写的“分角色权限”,在这里不是一句空话。它没有用Spring Security那种重型框架,而是用最原始的Session-like状态管理(LoginContext类维护当前用户ID与角色类型),配合菜单动态加载(JMenuBar根据role字段决定显示“预约”还是“排班管理”),再叠加DAO层SQL WHERE条件硬过滤(比如科室管理员查预约,SQL里永远带上AND dept_id = ?)。这种“土法炼钢”的方式,反而更适合初学者理解权限的本质:不是靠框架魔法,而是靠数据源头控制+界面可见性+业务逻辑拦截三层防线。
更关键的是,它完全规避了新手最容易踩的“Swing线程陷阱”。所有数据库操作(增删改查)全部封装在独立线程中执行(SwingWorker子类),UI主线程永远只负责刷新表格、弹提示框、禁用按钮。你打开源码看PatientAppointmentPanel.java里的submitAppointment()方法,会发现它第一行就new了一个AppointmentSubmitWorker,而不是直接调用appointmentDao.insert(...)——这个细节,很多课设项目都忽略了,结果一查数据就卡死整个界面,学生还以为是MySQL慢,其实是Swing没做线程隔离。
它适合谁?如果你是大二大三学生,正要交课设,它能让你三天搭起骨架、五天填满功能、两天调通细节,答辩PPT直接用包里那个.pptx改两页就能上台;如果你是自学Java的转行者,它是一份极佳的“Swing工程化实践样本”:从MVC分包结构(src/com/hospital/dao、service、ui)、到资源文件统一管理(images/、i18n/)、再到异常统一处理(GlobalExceptionHandler捕获SQLException并转成用户友好的中文提示),全是教科书级别的组织方式;甚至如果你是讲师,想给学生出一道“在现有系统上增加短信提醒功能”的拓展题,它的DAO层接口定义清晰、Service层职责单一、UI层事件解耦良好,加一个SmsNotifier类就能无缝接入。
说白了,这不是一个“能跑就行”的玩具,而是一个“经得起追问”的范本。接下来我会带你一层层剥开它的皮、筋、骨、肉,告诉你每一行关键代码为什么这么写,每一个表结构为什么这么设计,每一次点击背后发生了什么。你不需要照搬,但你一定能从中偷师到至少五种解决实际问题的思路。
2. 整体架构与设计思路拆解:桌面应用的“轻量级分层”
2.1 为什么选Swing而不是JavaFX或Web?
现在提Swing,很多人第一反应是“过时”。但在这个课设场景下,Swing反而是最优解。原因很实在:零部署成本、零环境依赖、零网络配置。学生交作业,老师双击HospitalApp.jar就能运行;答辩现场,U盘插上电脑,Eclipse里右键Run As → Java Application,3秒启动。换成JavaFX,得折腾jmods路径和--module-path参数;换成Web方案(Spring Boot+Thymeleaf),光是配Tomcat、解决跨域、处理静态资源404,就能耗掉学生两天时间——而这两天本该用来理解“预约冲突校验”的业务逻辑。
Swing的“过时感”,恰恰来自它的稳定。JDK8自带Swing,无需额外引入;组件生命周期清晰(JFrame → JDialog → JPanel嵌套);事件模型简单直接(ActionListener监听按钮,TableModel监听表格变更)。更重要的是,它强制你面对“桌面应用的本质问题”:如何让UI响应不卡顿?如何管理窗口层级(模态对话框阻塞主窗体)?如何持久化用户偏好(记住上次登录角色)?这些问题,在Web开发里被浏览器和框架层层掩盖,但在Swing里,你必须亲手解决。这个项目里,LoginFrame.java启动时会读取config.properties里的lastRole=patient,自动勾选病人登录单选框——这种细节,才是真实软件思维的起点。
2.2 MVC分层不是摆设:src目录下的真实分工
打开src目录,你会看到标准的三层结构:
- com.hospital.dao:纯数据访问层。每个DAO类(如PatientDao.java)只做一件事:把Java对象和SQL语句来回转换。insert(Patient p)方法里,SQL是预编译的INSERT INTO patient (id_card, name, ...) VALUES (?, ?, ...),参数用ps.setString(1, p.getIdCard())逐个绑定。这里没有SQL拼接,杜绝了基础注入风险;也没有把Connection写死在方法里,而是通过DBUtil.getConnection()统一管理——这个工具类里藏着连接池雏形(static final List<Connection> pool = new ArrayList<>(5)),虽简陋但有效。
- com.hospital.service:业务逻辑中枢。AppointmentService.java里的createAppointment()方法,表面看只是调DAO,实则包裹了三重校验:① 检查医生当日是否排班(查doctor_schedule表);② 检查该时段预约数是否已达上限(SELECT COUNT(*) FROM appointment WHERE schedule_id = ? AND status = 'confirmed');③ 检查病人身份证号是否已存在(防重复注册)。这三步必须在一个数据库事务里完成,所以DBUtil.beginTransaction()和commit()被明确写出——这是课程设计里少有的、把事务边界写进业务代码的案例。
- com.hospital.ui:纯粹的界面呈现层。PatientMainFrame.java只负责组装菜单栏、状态栏、中心TabbedPane;所有按钮点击事件,都委托给service层处理,自己绝不碰SQL。比如“预约”按钮的actionPerformed()里,只有appointmentService.create(...)和JOptionPane.showMessageDialog(...)两行核心代码。这种割裂,保证了未来如果要把界面换成JavaFX,只需重写ui包,service和dao几乎不用动。
这种分层,不是为了炫技,而是为了降低修改成本。当老师要求“增加预约取消功能”时,你只需要:① 在AppointmentService里加cancelAppointment(long id)方法;② 在PatientAppointmentPanel里加一个“取消”按钮及事件;③ 在appointment表里加status ENUM('confirmed','cancelled')字段。三处改动,边界清晰,互不影响。反观那些把SQL、界面、校验全塞在一个JFrame里的课设,加个取消功能,得翻遍十几个文件找INSERT语句在哪改。
2.3 权限模型:没有RBAC,只有“角色+数据过滤”的务实主义
很多学生一想到权限,就去搜“Spring Security RBAC教程”,结果搞了一周连登录都跑不通。这个项目用最笨也最有效的方式解决了权限:角色决定菜单可见性 + 角色决定DAO查询条件 + 角色决定Service操作范围。
- 菜单可见性:
MainFrame.java构造方法里,根据LoginContext.getRole()返回值,动态添加菜单项。病人角色只看到“我的预约”“医生排班”;科室管理员多出“排班管理”“预约明细”;超级管理员则拥有全部菜单。这不是前端隐藏(F12就能看到HTML),而是Swing层面彻底不创建JMenuItem对象——内存里根本不存在那个菜单项。 - DAO查询条件:
DoctorScheduleDao.java的findAllByDeptId(int deptId)方法,科室管理员调用时传入自己的dept_id;而超级管理员调用的是findAll(),SQL里没有WHERE条件。关键在于,service层调用哪个DAO方法,由角色决定。AdminService.java里调doctorScheduleDao.findAll(),DeptAdminService.java里调doctorScheduleDao.findAllByDeptId(deptId)——数据源头就被切开了。 - Service操作范围:
AdminService.deleteDoctor(long id)可以删任意医生;DeptAdminService.deleteDoctor(long id)则先查SELECT dept_id FROM doctor WHERE id = ?,再比对当前管理员的dept_id,不匹配直接抛SecurityException。这种“业务层二次校验”,比单纯依赖数据库外键更安全——因为外键只能防删,防不了恶意修改。
这种设计,牺牲了理论上的“灵活性”,换来了绝对的“可控性”。它不追求能支撑百万用户,只确保三个角色在本地运行时,各干各的事,各看各的数据,绝不出错。这才是课程设计该有的务实精神。
3. 核心细节解析与实操要点:从数据库建模到Swing线程安全
3.1 MySQL建表逻辑:业务驱动的字段设计
打开数据库课程设计.sql,第一眼看到的不是CREATE TABLE,而是注释:“// 病人表:身份证号为业务主键,系统生成编号为技术主键”。这句话点破了整个设计的灵魂——区分业务主键与技术主键。
CREATE TABLE patient (
id BIGINT PRIMARY KEY AUTO_INCREMENT, -- 技术主键,自增,用于外键关联
id_card VARCHAR(18) NOT NULL UNIQUE, -- 业务主键,身份证号,唯一且非空
name VARCHAR(20) NOT NULL,
gender ENUM('M','F') DEFAULT 'M',
phone VARCHAR(11),
created_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
为什么这么设计?因为业务上,“身份证号”才是病人的真实身份标识,任何系统交互(如挂号、缴费)都以它为准;但技术上,用18位字符串做外键(比如appointment.patient_id指向patient.id_card),索引效率远低于BIGINT,且MySQL对长字符串索引有长度限制。所以id是关联用的“代号”,id_card是业务用的“真名”。你在PatientDao.insert()里能看到:先插入id_card等字段,再用ps.getGeneratedKeys()获取自增的id,最后把这个id存进appointment表的外键字段——这就是两个主键协同工作的完整链路。
再看医生排班表doctor_schedule:
CREATE TABLE doctor_schedule (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
doctor_id BIGINT NOT NULL,
dept_id INT NOT NULL,
work_date DATE NOT NULL,
start_time TIME NOT NULL,
end_time TIME NOT NULL,
max_patients INT NOT NULL DEFAULT 20,
created_time DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (doctor_id) REFERENCES doctor(id),
FOREIGN KEY (dept_id) REFERENCES department(id),
-- 关键约束:同一医生同一天同一时段不能重复排班
UNIQUE KEY uk_doctor_date_time (doctor_id, work_date, start_time, end_time)
);
这里的UNIQUE KEY uk_doctor_date_time是防止排班冲突的物理保障。但光有这个不够——它只能防“完全相同”的排班,防不了“时间重叠”。比如医生上午8:00-12:00已排班,科室管理员又想排9:00-11:00,UNIQUE约束不触发,但业务上显然冲突。所以DeptAdminService.addSchedule()里,插入前必做一次查询:
// 检查新排班是否与已有排班时间重叠
String sql = "SELECT COUNT(*) FROM doctor_schedule " +
"WHERE doctor_id = ? AND work_date = ? " +
"AND ((start_time <= ? AND end_time > ?) OR " +
" (start_time < ? AND end_time >= ?))";
这个SQL用OR覆盖了四种重叠场景(新时段在旧时段内、旧时段在新时段内、新开始于旧中间、新结束于旧中间),是典型的区间重叠判断。它和数据库UNIQUE约束形成“双保险”:库表约束兜底,业务代码校验前置。
3.2 Swing线程安全:为什么所有数据库操作都用SwingWorker?
Swing是单线程的,所有UI更新(JTable.setModel()、JLabel.setText())必须在Event Dispatch Thread(EDT)执行;但数据库操作(Connection.createStatement()、ResultSet.next())是耗时IO,如果直接在EDT里执行,界面会假死。这个项目用SwingWorker完美解耦。
以病人预约为例,PatientAppointmentPanel.submitButton的监听器里:
submitButton.addActionListener(e -> {
// 1. 禁用按钮,防止重复点击
submitButton.setEnabled(false);
// 2. 启动后台任务
new AppointmentSubmitWorker().execute();
});
// 内部类继承SwingWorker<Void, String>
private class AppointmentSubmitWorker extends SwingWorker<Void, String> {
@Override
protected Void doInBackground() throws Exception {
// 这里运行在后台线程,可安全执行耗时DB操作
boolean success = appointmentService.createAppointment(
selectedScheduleId,
LoginContext.getCurrentUserId()
);
if (success) {
publish("预约成功!"); // publish会触发process()
} else {
publish("预约失败,请重试");
}
return null;
}
@Override
protected void process(List<String> chunks) {
// 这里运行在EDT,可安全更新UI
for (String msg : chunks) {
JOptionPane.showMessageDialog(null, msg);
}
}
@Override
protected void done() {
// 任务结束,恢复按钮
submitButton.setEnabled(true);
}
}
这个模式的价值在于:把“做什么”(doInBackground)和“做完后告诉用户什么”(process/done)彻底分开。publish()不是直接弹窗,而是把消息发到EDT的消息队列;process()批量消费这些消息,避免频繁弹窗。done()里恢复按钮状态,防止用户狂点导致多次提交——这是真实用户行为,不是理论假设。
我见过太多课设,数据库操作直接写在actionPerformed()里,结果老师点一下“查询所有病人”,界面卡住10秒,当场质疑“你这系统性能太差”。其实不是性能差,是线程没管好。这个项目用SwingWorker,把“卡顿”这个最刺眼的问题,悄无声息地解决了。
3.3 JGoodies组件库:让Swing界面告别“古董感”
Swing原生组件(JButton、JTextField)默认样式确实像Windows 98。jgoodies-forms-1.9.0.jar和jgoodies-common-1.8.1.jar的引入,不是为了花哨,而是为了解决两个刚需:布局一致性和表单验证。
- 布局一致性:
PatientRegisterPanel.java里,用FormLayout替代GridLayout:
FormLayout layout = new FormLayout(
"right:pref, 3dlu, fill:150:grow", // 列:标签右对齐,3像素间隔,输入框填充
"p, 3dlu, p, 3dlu, p, 3dlu, p, 3dlu, p" // 行:每行一个控件,3像素间隔
);
PanelBuilder builder = new PanelBuilder(layout);
builder.setDefaultDialogBorder();
CellConstraints cc = new CellConstraints();
builder.addLabel("身份证号:", cc.xy(1,1));
builder.add(new JTextField(15), cc.xy(3,1));
builder.addLabel("姓名:", cc.xy(1,3));
builder.add(new JTextField(15), cc.xy(3,3));
// ... 其他字段
FormLayout用行列坐标精确定位,3dlu(dialog unit)是与字体大小自适应的单位,确保在不同DPI屏幕上显示一致。对比原生GridBagLayout里一堆gridx/gridy/insets,FormLayout的代码可读性高了不止一个数量级。
- 表单验证:
PatientRegisterPanel里,身份证号输入框绑定了InputVerifier:
idCardField.setInputVerifier(new InputVerifier() {
@Override
public boolean verify(JComponent input) {
String id = idCardField.getText().trim();
if (id.length() != 18) {
JOptionPane.showMessageDialog(null, "身份证号必须为18位!");
return false;
}
// 更严格的校验:末位校验码算法(略)
return true;
}
});
这个InputVerifier在用户离开文本框(Tab或点击其他控件)时自动触发,比“点提交按钮才校验”体验好得多。而且它和FormLayout配合,错误时能高亮边框(idCardField.setBorder(BorderFactory.createLineBorder(Color.RED))),视觉反馈即时。
JGoodies的价值,不在于它多高级,而在于它用最小的学习成本,把Swing的“丑”和“难用”这两个痛点,扎扎实实地按住了。
4. 实操过程与核心环节实现:从零搭建可运行环境
4.1 环境准备:JDK8 + MySQL5.7 + Eclipse Oxygen的黄金组合
这个项目明确声明“在Windows环境经Eclipse+JDK8+MySQL5.7验证可直接运行”,不是随便写的。这三个版本的选择,全是为兼容性服务:
- JDK8:Swing在JDK9+模块化后,部分API(如
javax.swing.plaf.basic.BasicLookAndFeel)被移入java.desktop模块,需额外--add-modules java.desktop参数。JDK8无此烦恼,javac编译、java -jar运行,一步到位。 - MySQL5.7:
数据库课程设计.sql里用了ENUM类型(gender ENUM('M','F'))和DEFAULT CURRENT_TIMESTAMP,这两个特性在MySQL5.6及以下不支持或行为异常。5.7是兼容性最好的版本。 - Eclipse Oxygen(4.7):这是最后一个默认支持JDK8且无需额外配置的Eclipse主流版本。更新的Photon、2018-12等,需要手动在
Preferences → Java → Installed JREs里添加JDK8路径,并在项目属性里指定Java Build Path。
安装步骤极简:
1. 下载JDK8u202(推荐AdoptOpenJDK),安装后配置JAVA_HOME环境变量;
2. 下载MySQL Community Server 5.7.33,安装时勾选“Add MySQL to PATH”,设置root密码为123456(项目SQL脚本里写死的);
3. 下载Eclipse IDE for Java Developers(Oxygen版),解压即用。
提示:如果MySQL安装时跳过配置向导,可用命令行初始化:
mysqld --initialize-insecure --user=mysql,然后mysql -u root -p回车直接登录,再执行ALTER USER 'root'@'localhost' IDENTIFIED BY '123456';。
4.2 导入Eclipse工程:识别.classpath与.project的关键
资源包里的.classpath和.project是Eclipse项目的“身份证”。正确导入,比新建项目再复制代码快十倍。
操作流程:
1. 解压资源包,记下zzzzzzzuoye文件夹的绝对路径(如D:\homework\zzzzzzzuoye);
2. 打开Eclipse,File → Import → General → Existing Projects into Workspace;
3. 选择Select root directory,浏览到D:\homework\zzzzzzzuoye;
4. 勾选下方出现的项目名(通常是hospital-appointment-system),点击Finish。
此时Eclipse会自动读取.classpath文件,识别出:
- <classpathentry kind="src" path="src"/> → 源码在src目录;
- <classpathentry kind="lib" path="lib/jgoodies-forms-1.9.0.jar"/> → 第三方jar在lib目录;
- <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/> → 强制使用JDK8。
如果导入后报红(如The project was not built since its build path is incomplete),大概率是JRE没配对。右键项目 → Properties → Java Build Path → Libraries,删除错误的JRE,点击Add Library → JRE System Library → Workspace default JRE。
4.3 数据库初始化:执行SQL脚本的避坑指南
数据库课程设计.sql包含建表语句和测试数据(INSERT语句)。执行时务必注意三点:
-
编码必须是UTF8mb4:MySQL默认编码可能是
latin1,会导致中文乱码。执行前先确认:sql SHOW VARIABLES LIKE 'character_set%';
如果character_set_database不是utf8mb4,需修改MySQL配置文件my.ini(Windows):ini [client] default-character-set = utf8mb4 [mysqld] character-set-server = utf8mb4 collation-server = utf8mb4_unicode_ci
修改后重启MySQL服务。 -
执行顺序不能错:脚本里建表顺序是
department → doctor → doctor_schedule → patient → appointment。因为doctor表有dept_id外键指向department,appointment表有patient_id和schedule_id外键。如果先执行appointment建表语句,会报错“Unknown table ‘department’”。 -
测试数据要真实:脚本末尾的INSERT语句,如:
sql INSERT INTO patient (id_card, name, gender, phone) VALUES ('110101199003072758', '张三', 'M', '13800138000');
身份证号是真实校验通过的(末位X已计算),电话号符合11位规则。这意味着你导入后,用这个身份证号登录病人账号,能立刻看到效果,无需再手动造数据。
执行方法(推荐用MySQL Workbench):
- 打开Workbench,连接本地MySQL(host: 127.0.0.1, port: 3306, user: root, password: 123456);
- 新建Query Tab,File → Open SQL Script,选择数据库课程设计.sql;
- 点击闪电图标执行。成功后,左侧SCHEMAS里会出现hospital_db数据库,展开即可看到所有表。
4.4 运行与调试:第一个断点该打在哪里?
项目入口是com.hospital.ui.LoginFrame.java的main方法。右键它 → Run As → Java Application,应该立刻弹出登录窗口。
如果报错java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver,说明MySQL驱动jar没加载。解决方案:
- 下载mysql-connector-java-5.1.47.jar(必须5.x,8.x驱动类名变为com.mysql.cj.jdbc.Driver,项目代码里写的是com.mysql.jdbc.Driver);
- 将jar复制到项目根目录的lib文件夹;
- 右键项目 → Build Path → Configure Build Path → Libraries → Add JARs,选择lib/mysql-connector-java-5.1.47.jar。
调试建议从登录验证开始。在LoginFrame.loginButton的actionPerformed()里打第一个断点:
loginButton.addActionListener(e -> {
String idCard = idCardField.getText().trim(); // 断点打在这里
String password = new String(passwordField.getPassword());
// ... 后续逻辑
});
运行Debug模式,输入病人身份证110101199003072758,F6单步执行,观察patientDao.findByCard(idCard)是否返回非null的Patient对象。这是整个系统数据流的起点,打通了,后面就水到渠成。
5. 常见问题与排查技巧实录:那些课设答辩时老师最爱问的点
5.1 “医生排班和预约冲突,你们怎么保证数据一致性?”
这是高频灵魂拷问。回答不能只说“用了事务”,要具体到代码行。
正确回答路径:
1. 指出事务边界:在AppointmentService.createAppointment()方法开头,有DBUtil.beginTransaction();成功后DBUtil.commit();异常时DBUtil.rollback()。事务包裹了“查排班”“查预约数”“插预约记录”三步。
2. 强调数据库约束:doctor_schedule表有UNIQUE KEY uk_doctor_date_time,防止同一医生同一天同一时段重复排班;appointment表有FOREIGN KEY (schedule_id) REFERENCES doctor_schedule(id),保证预约必须对应真实排班。
3. 补充业务层校验:createAppointment()里执行SELECT COUNT(*) FROM appointment WHERE schedule_id = ? AND status = 'confirmed',结果大于等于max_patients则拒绝。这个查询用SELECT ... FOR UPDATE(在DBUtil.getConnection()获取连接时已设connection.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ)),锁定该schedule_id的行,防止并发预约超限。
注意:如果老师追问“
REPEATABLE_READ能防幻读吗”,可以坦诚说“不能完全防,但课设并发量低,配合SELECT FOR UPDATE已足够”。诚实比硬拗强。
5.2 “病人注册时身份证号必填,但你们没做前端实时校验,万一输错怎么办?”
这个问题直指用户体验。答案要体现“前后端协同”。
分层回应:
- 前端(Swing):PatientRegisterPanel里,idCardField绑定了InputVerifier,离开焦点时校验长度(18位)和格式(前17位数字+末位数字/X),错误时弹窗并阻止失焦(return false)。
- 后端(Service):PatientService.register()里,调用idCardValidator.isValid(idCard),执行完整的GB11643-1999身份证校验算法(计算加权和、模11余数匹配校验码),不通过直接抛IllegalArgumentException。
- 数据库(MySQL):patient.id_card字段设为NOT NULL UNIQUE,即使绕过前端后端,数据库层也会拦截重复或空值。
三道防线,缺一不可。这比单纯说“我们做了校验”有力得多。
5.3 “科室管理员只能看本科室数据,但如果有人篡改URL或SQL注入呢?”
这是安全意识的试金石。回答要展现“纵深防御”思维。
防御体系展示:
- 第一层(界面层):DeptAdminMainFrame.java的菜单栏,只添加本科室相关的功能项(如“本科室医生管理”),不提供“全局医生管理”入口。用户根本看不到越权功能。
- 第二层(业务层):DeptAdminService.findAllDoctors()方法里,SQL是SELECT * FROM doctor WHERE dept_id = ?,?参数由LoginContext.getCurrentDeptId()提供,而非从请求参数(如request.getParameter("dept_id"))获取。即使黑客伪造HTTP请求,也无法影响这个值。
- 第三层(数据层):doctor表的dept_id字段有外键约束,指向department表。任何非法dept_id都会被数据库拒绝插入。
提示:如果老师问“那超级管理员删医生,怎么防误删?”,可答:“
AdminService.deleteDoctor()里,先查SELECT COUNT(*) FROM appointment WHERE doctor_id = ? AND status IN ('confirmed','in_progress'),如果有未完成预约,抛BusinessException("该医生有未完成预约,无法删除"),并在UI层捕获显示。”
5.4 “Swing界面在高DPI屏幕(如Surface Pro)上模糊,怎么解决?”
这是Windows 10/11用户的实际痛点。答案要给出可立即生效的方案。
终极解决方案:
1. 找到Eclipse安装目录下的eclipse.ini文件;
2. 在-vmargs之后,添加两行:-Dsun.java2d.dpiaware=true -Dsun.java2d.win.text.font.scaling=100
3. 重启Eclipse,重新运行项目。
原理:dpiaware=true启用Windows DPI感知,让Swing组件按系统缩放比例渲染;win.text.font.scaling=100强制字体不随DPI缩放,避免文字糊成一片。这个配置在eclipse.ini里生效,比在代码里System.setProperty()更可靠。
5.5 “如果要扩展微信通知功能,你们的架构支持吗?”
这是考察架构延展性的经典问题。回答要突出“松耦合”和“可插拔”。
架构支撑点:
- 事件驱动设计:AppointmentService.createAppointment()成功后,调用EventPublisher.publish(new AppointmentConfirmedEvent(appointment))。这是一个简单的观察者模式,EventPublisher维护List<EventListener>,目前只有ConsoleLogger实现类。
- 扩展路径:新增WeChatNotifier implements EventListener,在onEvent(AppointmentConfirmedEvent event)里调用微信API发送模板消息;然后在Main.java启动时,EventPublisher.addListener(new WeChatNotifier())。
- 零侵入:AppointmentService代码完全不用改,只需增加监听器。这就是“开闭原则”的实践——对扩展开放,对修改关闭。
这个回答,能把一个课设项目,瞬间提升到“工业级架构思维”的高度。
6. 实操心得与经验总结:那些文档里不会写的细节
6.1 关于“开箱即用”的真相:你必须手动做的三件事
资源包标榜“开箱即用”,但真实情况是,有三个地方必须你亲手操作,否则必然报错:
-
MySQL密码同步:SQL脚本里所有
GRANT语句用的都是IDENTIFIED BY '123456',但你的MySQL root密码很可能不是这个。解决方案:用mysql -u root -p登录后,执行SET PASSWORD FOR 'root'@'localhost' = PASSWORD('123456');,或者直接修改SQL脚本里所有'123456'为你的真实密码。 -
JGoodies jar包路径修正:资源包里的
lib目录下,jar包名是jgoodies-forms-1.9.0.jar,但.classpath文件里写的是<classpathentry kind="lib" path="lib/jgoodies-forms.jar"/>(少了版本号)。你需要打开.classpath,把jgoodies-forms.jar改成jgoodies-forms-1.9.0.jar,否则Eclipse找不到类。 -
Eclipse编译器合规性:项目
.settings/org.eclipse.jdt.core.prefs里,org.eclipse.jdt.core.compiler.compliance=1.8,但你的Eclipse可能默认用1.7。右键项目 →Properties → Java Compiler,勾选Enable project specific settings,把Compiler compliance level设为1.8。
这三件事,网上教程基本不提,但90%的学生第一次运行都会卡在这儿。我把它们列出来,就是希望你少走两小时弯路。
6.2 关于“病人身份证号自动生成编号”的实现玄机
摘要里说“编号自动生成”,但源码里PatientDao.insert()并没有生成逻辑。真相是:编号(id字段)由MySQL AUTO_INCREMENT生成,而“编号”在业务语境里,指的是id,不是id_card。
为什么这样设计?因为id_card是18位字符串,作为对外展示的“病人编号”太长(挂号单上印110101199003072758显然不如印P20230001清爽)。但项目没做P+年份+序号的业务编号,是因为:
- 它增加了复杂度(需查MAX(id)再格式化);
- 它违背了“技术主键与业务主键分离”原则(id已是技术主键);
- 课设目标是验证数据库和Swing,不是做ID生成算法。
所以,当你看到病人列表里显示“编号:1”,那就是数据库id字段的值。如果真要加业务编号,最佳位置是Patient实体类里加一个businessId字段,PatientService.register()里用String.format("P%s%04d", Year.now(), id)生成,存进数据库新字段。但这个需求,不在课设范围内。
6.3 关于“演示PPT”的隐藏价值:答辩时的救命稻草
包里的医院预约管理系统.pptx,别只当它是摆设。它有三大隐藏用途:
- 答辩逻辑图谱:PPT第3页的“系统架构图”,清晰画出了DAO/Service/UI三层关系,以及MySQL数据库的位置。答辩时老师问“数据怎么流动的”,直接翻到这页,用激光笔指着箭头讲,比口头描述清楚十倍。
- 功能演示脚本:PPT第5-8页,按角色列出了“典型操作路径”。比如病人角色:“1. 输入身份证登录 → 2. 查看内科排班 → 3. 选择8:00-9:00时段预约 → 4. 查看我的预约历史”。你照着这个路径操作一遍,确保每个环节都流畅,答辩时就不会手忙脚乱点错按钮。
- 技术亮点提炼:PPT最后一页的“创新点”,写了“基于SwingWorker的线程安全设计”“三层架构解耦”“业务主键与技术主键分离”。这些词,就是你回答“项目有什么技术难点”时的标准答案。把PPT里的原话背下来,答辩时脱口而出,专业感立马拉满。
6.4 关于“后续扩展”的务实建议:别碰这三件事
很多学生想“锦上添花”,结果画蛇添足。根据我带课经验,以下扩展最易翻车,强烈建议课设阶段放弃:
- 增加短信/邮件通知:看似简单,实则要对接第三方API(如阿里云短信),涉及密钥管理、HTTPS证书、异步回调。课设时间根本不够,且偏离数据库和Swing核心。
- 改成Web版(Spring Boot):工作量爆炸。Swing的
JTable换成Thymeleaf表格,事件监听换成Controller,DAO层要重写MyBatis XML。两周课设,光配环境就能耗掉一周。 - 加入AI分诊(NLP症状分析):纯属炫技。课设目标是掌握基础开发流程,不是搞AI研究。老师一眼看出是抄的,反而扣分。
真正有价值的扩展,应该是深化现有逻辑:比如给预约增加“取消理由”字段并统计分析;给科室管理员增加“排班冲突预警”(自动标红重叠时段);给超级管理员增加“数据导出为Excel”功能(用Apache POI)。这些都在原有架构上延伸,工作量可控,且能体现思考深度。
6.5 最后一个忠告:别迷信“完整源码”,动手才是王道
这个项目最大的价值,不是给你一套能直接交差的代码,而是提供了一个可触摸、可调试、可修改的完整参照系。我见过太多学生,下载完解压,扫一眼src目录就关掉了,觉得“哦,原来长这样”,然后回去自己从零写,结果卡在Swing布局上三天。正确的姿势是:
- 先确保它能在你电脑上跑起来(按4.1-4.4节操作);
- 然后挑一个你最困惑的功能(比如“为什么预约成功后表格没刷新?”),在
PatientAppointmentPanel.java里找到refreshTable()方法,打个断点,看它怎么从appointmentService.findAllByPatientId()拿到数据,再怎么用DefaultTableModel更新JTable; - 接着,尝试微调:把预约成功的提示框,从
JOptionPane改成JLabel状态栏显示;把医生姓名列的宽度,从table.getColumnModel().getColumn(1).setPreferredWidth(100)改成150。
只有当你亲手改过一行代码,让它按你的意愿运行,这个项目才算真正属于你。源码不是终点,而是你编程旅程的起点。
简介:一套开箱即用的医院预约管理桌面程序,用Java Swing开发,后端基于MySQL数据库。系统划分三个角色:病人可凭身份证注册登录,查看医生排班、按时间段预约就诊,并查询自己的预约历史;科室管理员负责维护本科室医生信息(姓名、职称、专长等)、设置每日出诊时段和最大接诊人数,还能导出指定日期的预约明细;超级管理员统一管理病人、医生、科室、管理员等基础数据,支持增删改查全操作。资源包包含完整Eclipse工程(含src源码、bin编译文件、.classpath和.project配置)、MySQL建表及初始化脚本(含测试数据)、配套演示PPT、所需jgoodies第三方jar包。所有功能在本地Windows环境经Eclipse+JDK8+MySQL5.7验证可直接运行,适合数据库原理或Java课程设计作业参考。
更多推荐




所有评论(0)