本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:一套开箱即用的医院预约管理桌面程序,用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包,servicedao几乎不用动。

这种分层,不是为了炫技,而是为了降低修改成本。当老师要求“增加预约取消功能”时,你只需要:① 在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.javafindAllByDeptId(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原生组件(JButtonJTextField)默认样式确实像Windows 98。jgoodies-forms-1.9.0.jarjgoodies-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/insetsFormLayout的代码可读性高了不止一个数量级。

  • 表单验证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语句)。执行时务必注意三点:

  1. 编码必须是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服务。

  2. 执行顺序不能错:脚本里建表顺序是department → doctor → doctor_schedule → patient → appointment。因为doctor表有dept_id外键指向departmentappointment表有patient_idschedule_id外键。如果先执行appointment建表语句,会报错“Unknown table ‘department’”。

  3. 测试数据要真实:脚本末尾的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.javamain方法。右键它 → 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.loginButtonactionPerformed()里打第一个断点:

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 关于“开箱即用”的真相:你必须手动做的三件事

资源包标榜“开箱即用”,但真实情况是,有三个地方必须你亲手操作,否则必然报错:

  1. MySQL密码同步:SQL脚本里所有GRANT语句用的都是IDENTIFIED BY '123456',但你的MySQL root密码很可能不是这个。解决方案:用mysql -u root -p登录后,执行SET PASSWORD FOR 'root'@'localhost' = PASSWORD('123456');,或者直接修改SQL脚本里所有'123456'为你的真实密码。

  2. 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找不到类。

  3. 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布局上三天。正确的姿势是:

  1. 先确保它能在你电脑上跑起来(按4.1-4.4节操作);
  2. 然后挑一个你最困惑的功能(比如“为什么预约成功后表格没刷新?”),在PatientAppointmentPanel.java里找到refreshTable()方法,打个断点,看它怎么从appointmentService.findAllByPatientId()拿到数据,再怎么用DefaultTableModel更新JTable
  3. 接着,尝试微调:把预约成功的提示框,从JOptionPane改成JLabel状态栏显示;把医生姓名列的宽度,从table.getColumnModel().getColumn(1).setPreferredWidth(100)改成150

只有当你亲手改过一行代码,让它按你的意愿运行,这个项目才算真正属于你。源码不是终点,而是你编程旅程的起点。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:一套开箱即用的医院预约管理桌面程序,用Java Swing开发,后端基于MySQL数据库。系统划分三个角色:病人可凭身份证注册登录,查看医生排班、按时间段预约就诊,并查询自己的预约历史;科室管理员负责维护本科室医生信息(姓名、职称、专长等)、设置每日出诊时段和最大接诊人数,还能导出指定日期的预约明细;超级管理员统一管理病人、医生、科室、管理员等基础数据,支持增删改查全操作。资源包包含完整Eclipse工程(含src源码、bin编译文件、.classpath和.project配置)、MySQL建表及初始化脚本(含测试数据)、配套演示PPT、所需jgoodies第三方jar包。所有功能在本地Windows环境经Eclipse+JDK8+MySQL5.7验证可直接运行,适合数据库原理或Java课程设计作业参考。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

更多推荐