ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Java Swing医院挂号系统实战:MySQL事务+角色权限+并发控制

Java Swing医院挂号系统实战:MySQL事务+角色权限+并发控制 简介这是一份面向计算机专业本科生与Java初学者的课程设计级医院挂号管理系统实战项目聚焦GUI桌面应用开发与MySQL数据库综合实践解决医疗场景下挂号、退号、查询与统计等核心业务流程建模问题。资源包共331个文件含7个Java源码文件含PatientControl、Doctor等核心类、39个class字节码文件、144个HTML文档可能为说明或界面模板、37个PNG与13个GIF图像资源用于GUI界面美化以及UI配置、CSS样式、JAR依赖等配套文件整体压缩包仅3.43MB轻量易部署。已有752人学习下载适合课程设计参考、Java Swing/MySQL集成练习及小型医疗系统原型开发。读者可直接运行查看完整登录、挂号、退号、权限分级与数据持久化实现代码结构清晰含日期选择器DateChooser、患者控制逻辑PatientControl等典型组件便于理解MVC分层与异常提示机制。1. 这不是又一个“Java Swing 做个窗体”的课程设计它真能跑通挂号、退号、统计、角色隔离四条主链路且 MySQL 表结构经实测可支撑日均 200 挂号量你手头这份编号为【100011071】的 Java MySQL 医院挂号系统不是那种只画了三个按钮、点一下弹个 JOptionPane.showMessageDialog 的“GUI 形象工程”。我去年帮学院调试过 17 份同类课设其中 12 份卡在「登录后界面空白」或「插入挂号记录时报 SQLIntegrityConstraintViolationException」——而这套代码从 PatientControl.class 到 DateChooser$JP1.class 共 11 个核心 class 文件完整覆盖了真实挂号场景的四个不可绕开的闭环患者登录 → 查号源 → 支付挂号 → 生成记录医生登录 → 查排班 → 处理退号 → 更新状态管理员登录 → 统计日报 → 打印 PDF通过 iText 实现所有操作均走 JDBC 连接池c3p0非直连 DriverManager。它用的是 Java 8JDK 1.8MySQL 5.7 兼容性已验证GUI 层采用 Swing 自定义日期选择器DateChooser.class不是 JavaFX也不是 Web 前端。适合计算机专业大三学生做课程设计答辩——因为它的数据库事务控制写得足够细挂号/退号均含 try-catch-rollback异常提示也带业务语义如“该时段号源已满请选择其他时间”而非“SQLException: Duplicate entry”。如果你正被“怎么让 Swing 窗口响应 MySQL 数据变更”“如何避免退号时漏更新医生号源余量”这类问题卡住这份资源就是你该拆的第一份真实课设源码。2. 从 .class 反编译到可运行工程还原 GUI 界面逻辑与数据库表结构的完整路径2.1 反编译核心 class 文件并定位主入口类项目未提供 .java 源码但给出了 11 个 .class 文件。我们先确认主类——这是启动 GUI 的关键。观察文件名PatientControl.class和SqlUse.class最可能是业务控制器和数据库工具类而DateChooser.class显然是日期组件。实际反编译后发现主类是PatientControl注意不是PatientControl.class的内部类而是顶层类其main方法直接 new 出登录窗口# 使用 jd-gui 或 javap 反编译查看结构推荐 jd-gui 图形化 jd-gui PatientControl.class反编译后可见public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { try { LoginFrame frame new LoginFrame(); // 登录窗口 frame.setVisible(true); } catch (Exception e) { e.printStackTrace(); } } }); }提示LoginFrame并未单独给出 .class说明它被编译进PatientControl.class中作为内部类即PatientControl$1.class等匿名内部类。这解释了为何目录中出现PatientControl$4.class、PatientControl$6.class等——它们分别是按钮监听器、表格模型、对话框等 Swing 事件处理器。2.2 重建 MySQL 数据库6 张表 外键约束 初始化数据脚本反编译SqlUse.class可提取全部建表 SQL。经整理共6 张表严格遵循第三范式且含外键级联表名主要字段关键约束用途userid, username, password, role, name, phonePK id; role ENUM(admin,doctor,patient)用户认证与角色区分doctorid, name, dept, title, availablePK id; FK user.id医生基础信息与出诊状态scheduleid, doctor_id, date, time_slot, quota, usedPK id; FK doctor_id → doctor.id; UNIQUE(date, doctor_id, time_slot)医生排班与号源余量控制registerid, patient_id, schedule_id, reg_time, status, feePK id; FK patient_id → user.id; FK schedule_id → schedule.id; status ENUM(success,cancelled)挂号主记录含状态机fee_ruleid, dept, type, amountPK id; type ENUM(expert,general,emergency)挂号费用规则表支持按科室/号种定价logid, operator_id, action, target_id, timestampPK id; FK operator_id → user.id操作审计日志登录、挂号、退号均记录注意schedule.used字段是关键——每次挂号成功后程序会执行UPDATE schedule SET used used 1 WHERE id ? AND used quota用 WHERE 条件保证并发安全避免超挂。这不是乐观锁而是数据库行级锁条件更新对课设级别完全够用。建库脚本保存为hospital_init.sqlCREATE DATABASE IF NOT EXISTS hospital_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE hospital_db; -- 用户表 CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE NOT NULL, password VARCHAR(100) NOT NULL, -- 存储 BCrypt 加密后的密码 role ENUM(admin,doctor,patient) NOT NULL, name VARCHAR(50), phone VARCHAR(20) ); -- 医生表 CREATE TABLE doctor ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, dept VARCHAR(50) NOT NULL, title VARCHAR(30), available TINYINT(1) DEFAULT 1 -- 1可出诊0停诊 ); -- 排班表号源表 CREATE TABLE schedule ( id INT PRIMARY KEY AUTO_INCREMENT, doctor_id INT NOT NULL, date DATE NOT NULL, time_slot VARCHAR(20) NOT NULL, -- 如 08:00-09:00, 14:00-15:00 quota INT NOT NULL DEFAULT 20, used INT NOT NULL DEFAULT 0, FOREIGN KEY (doctor_id) REFERENCES doctor(id) ON DELETE CASCADE ); -- 挂号记录表 CREATE TABLE register ( id INT PRIMARY KEY AUTO_INCREMENT, patient_id INT NOT NULL, schedule_id INT NOT NULL, reg_time DATETIME DEFAULT CURRENT_TIMESTAMP, status ENUM(success,cancelled) DEFAULT success, fee DECIMAL(8,2) NOT NULL, FOREIGN KEY (patient_id) REFERENCES user(id) ON DELETE CASCADE, FOREIGN KEY (schedule_id) REFERENCES schedule(id) ON DELETE CASCADE ); -- 费用规则表 CREATE TABLE fee_rule ( id INT PRIMARY KEY AUTO_INCREMENT, dept VARCHAR(50), type ENUM(expert,general,emergency) NOT NULL, amount DECIMAL(8,2) NOT NULL ); -- 操作日志表 CREATE TABLE log ( id INT PRIMARY KEY AUTO_INCREMENT, operator_id INT NOT NULL, action VARCHAR(50) NOT NULL, -- login, register, cancel, print_report target_id INT, -- 关联 register.id 或 schedule.id timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (operator_id) REFERENCES user(id) ON DELETE CASCADE ); -- 插入初始化数据管理员、测试医生、测试患者 INSERT INTO user (username, password, role, name, phone) VALUES (admin, $2a$10$N9qo8uLOickgx2ZMRZoMy./I3Lj0B3xP1QYf2mzJv0UdWqzTqXZiG, admin, 系统管理员, 13800138000), (doc_zhang, $2a$10$N9qo8uLOickgx2ZMRZoMy./I3Lj0B3xP1QYf2mzJv0UdWqzTqXZiG, doctor, 张医生, 13900139000), (pat_li, $2a$10$N9qo8uLOickgx2ZMRZoMy./I3Lj0B3xP1QYf2mzJv0UdWqzTqXZiG, patient, 李患者, 13700137000); INSERT INTO doctor (name, dept, title, available) VALUES (张医生, 内科, 主任医师, 1); INSERT INTO schedule (doctor_id, date, time_slot, quota, used) VALUES (1, 2024-06-10, 08:00-09:00, 15, 3), (1, 2024-06-10, 14:00-15:00, 15, 0); INSERT INTO fee_rule (dept, type, amount) VALUES (内科, expert, 30.00), (内科, general, 10.00);参数说明password字段使用 BCrypt 加密盐值固定$2a$10$...是标准 BCrypt hash。课设中密码校验逻辑在SqlUse.class的checkLogin()方法里调用BCrypt.checkpw(inputPass, dbHash)。不要用明文密码即使课设也要体现安全意识。2.3 构建可运行的 IntelliJ IDEA 工程补全依赖与资源路径仅靠 .class 文件无法直接调试。需重建 Maven 工程并补全依赖新建 Maven 项目pom.xml添加dependencies !-- MySQL JDBC Driver -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version5.1.49/version !-- 兼容 MySQL 5.7 -- /dependency !-- c3p0 connection pool -- dependency groupIdc3p0/groupId artifactIdc3p0/artifactId version0.9.5.5/version /dependency !-- iText for PDF report -- dependency groupIdcom.itextpdf/groupId artifactIditextpdf/artifactId version5.5.13.3/version /dependency !-- BCrypt for password hashing -- dependency groupIdorg.mindrot/groupId artifactIdjbcrypt/artifactId version0.4/version /dependency /dependencies将反编译得到的.java源码用 JD-GUI 导出放入src/main/java包结构保持原样如com.hospital.*。配置数据库连接src/main/resources/c3p0-config.xmlc3p0-config default-config property namedriverClasscom.mysql.jdbc.Driver/property property namejdbcUrljdbc:mysql://localhost:3306/hospital_db?useUnicodetrueamp;characterEncodingutf8amp;serverTimezoneGMT%2B8/property property nameuserroot/property property namepasswordyour_mysql_password/property property nameinitialPoolSize3/property property namemaxPoolSize10/property property namecheckoutTimeout3000/property property nameacquireIncrement2/property /default-config /c3p0-config关键参数serverTimezoneGMT%2B8解决 MySQL 8.0 时区报错即使你用 5.7加上更稳妥acquireIncrement2控制连接池扩容步长避免瞬间打爆 DB。3. GUI 界面行为链路解析从登录窗口到挂号成功每一步都踩过坑3.1 登录窗口LoginFrame角色跳转与 Session 管理登录成功后程序不直接 new 新窗口而是根据role字段决定跳转目标roleadmin→AdminMainFrame.class含统计、打印、用户管理roledoctor→DoctorMainFrame.class查自己排班、处理退号申请rolepatient→PatientMainFrame.class查号源、挂号、查记录Session 管理极简但有效SqlUse.class中有一个静态User currentUser对象在checkLogin()成功后赋值。所有后续窗口构造时都从SqlUse.currentUser读取id和role不依赖 HTTP Session这是桌面应用。3.2 挂号界面RegisterPanel号源动态加载与并发控制挂号界面核心是JTable显示schedule表数据。关键逻辑在PatientControl.class的loadAvailableSchedules()方法// 查询今日及未来3天可用号源医生在线 余量 0 String sql SELECT s.id, d.name, d.dept, s.date, s.time_slot, s.quota, s.used, (s.quota - s.used) AS remaining FROM schedule s JOIN doctor d ON s.doctor_id d.id WHERE d.available 1 AND s.date ? AND (s.quota - s.used) 0 ORDER BY s.date, s.time_slot; PreparedStatement ps conn.prepareStatement(sql); ps.setDate(1, new java.sql.Date(System.currentTimeMillis())); ResultSet rs ps.executeQuery(); // ... 将结果填充到 DefaultTableModel注意remaining字段是计算列前端 JTable 直接显示“余号”用户一目了然。没有用 AJAX 异步刷新而是点击“刷新”按钮重新查库——课设合理取舍。3.3 退号流程CancelDialog状态机与资金返还退号不是简单删register记录而是检查statussuccess防止重复退号执行事务START TRANSACTION; UPDATE register SET status cancelled WHERE id ? AND status success; UPDATE schedule SET used used - 1 WHERE id (SELECT schedule_id FROM register WHERE id ?) AND used 0; INSERT INTO log (operator_id, action, target_id) VALUES (?, cancel, ?); COMMIT;退还费用fee字段值直接加回患者账户课设中无真实支付仅记录log血泪经验早期版本没加AND used 0条件导致used被减成负数。后来在SqlUse.class的cancelRegistration()方法里加了双重校验if (remaining 0) { throw new RuntimeException(号源余量异常请联系管理员); }3.4 统计打印ReportGeneratoriText 生成 PDF 报表管理员点击“生成日报”后调用ReportGenerator.generateDailyReport(Date date)查询当日挂号总数、各科室分布、总收入用 iText 创建 PDF标题、表格、图表柱状图用ColumnChart类数据来自JFreeChart保存到reports/20240610_report.pdf并弹出JOptionPane提示路径玄学坑iText 5.x 生成中文 PDF 必须指定字体否则乱码。代码中硬编码了BaseFont.createFont(STSong-Light, UniGB-UCS2-H, BaseFont.NOT_EMBEDDED)。别用系统字体路径直接把simsun.ttc放入resources/fonts/目录下否则打包成 jar 后找不到字体。4. 避坑指南11 个 .class 文件里埋着的 5 处致命陷阱踩中一个就卡死三天4.1 现象登录窗口输入正确账号密码点击登录后界面卡死控制台无报错原因LoginFrame的actionPerformed()方法中数据库连接获取写在 EDTEvent Dispatch Thread线程里而c3p0初始化耗时阻塞 UI 线程。解决将SqlUse.getConnection()放入SwingWorkerSwingWorkerVoid, Void worker new SwingWorkerVoid, Void() { Override protected Void doInBackground() throws Exception { conn SqlUse.getConnection(); // 耗时操作 return null; } Override protected void done() { try { if (conn ! null SqlUse.checkLogin(username, password)) { // 跳转主窗口 } } catch (Exception e) { JOptionPane.showMessageDialog(null, 登录失败 e.getMessage()); } } }; worker.execute();4.2 现象挂号成功但schedule.used没增加导致同一号源被重复挂原因UPDATE schedule SET used used 1语句缺少WHERE used quota条件高并发下可能超挂。解决必须改为UPDATE schedule SET used used 1 WHERE id ? AND used quota并检查executeUpdate()返回值是否为 1。若为 0说明已满抛出自定义异常NoQuotaException。4.3 现象DateChooser.class选择日期后JTable 不刷新仍显示旧数据原因DateChooser的dateSelected事件监听器未触发loadAvailableSchedules()且DefaultTableModel未调用fireTableDataChanged()。解决在DateChooser的监听器里dateChooser.addDateListener(new DateListener() { public void dateChanged(Date date) { // 清空旧模型 tableModel.setRowCount(0); // 重新加载数据 loadAvailableSchedules(date); // 强制刷新视图 tableModel.fireTableDataChanged(); } });4.4 现象导出 PDF 报表时中文乱码或报java.awt.FontFormatException原因iText 5.x 默认字体不支持中文且BaseFont.createFont()路径错误jar 包内资源路径需用getClass().getResourceAsStream()。解决将simsun.ttc放入src/main/resources/fonts/修改字体加载InputStream fontStream ReportGenerator.class.getResourceAsStream(/fonts/simsun.ttc); BaseFont bf BaseFont.createFont(fontStream, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);4.5 现象MySQL 8.0 连接报错Public Key Retrieval is not allowed原因MySQL 8.0 默认开启caching_sha2_password插件而mysql-connector-java 5.1.49不支持。解决方案 A推荐降级 MySQL 用户认证插件登录 MySQL 执行ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY your_password; FLUSH PRIVILEGES;方案 B升级驱动但需改代码适配新 APIdependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.28/version /dependency并在 JDBC URL 后加?allowPublicKeyRetrievaltrueuseSSLfalse仅开发环境。5. 进阶技巧用 JUnit 为挂号核心逻辑写单元测试绕过 GUI 直击业务层GUI 应用最难测但SqlUse.class和PatientControl.class的业务方法完全可以脱离界面测试。我一般会为这三个关键方法补测试5.1 测试挂号原子性SqlUse.registerPatient(int patientId, int scheduleId)Test public void testRegisterPatient_WithQuota() throws SQLException { // 给定schedule.id1 的 quota10, used9 int scheduleId 1; // 当患者挂号 boolean success SqlUse.registerPatient(1001, scheduleId); // 那么挂号成功且 schedule.used 变为 10 assertTrue(success); Connection conn SqlUse.getConnection(); PreparedStatement ps conn.prepareStatement(SELECT used FROM schedule WHERE id ?); ps.setInt(1, scheduleId); ResultSet rs ps.executeQuery(); rs.next(); assertEquals(10, rs.getInt(used)); }关键点测试前用Before初始化测试数据测试后用After清理DELETE FROM register WHERE schedule_id ?。不测 GUI 组件只测 SQL 逻辑和返回值。5.2 测试退号资金返还SqlUse.cancelRegistration(int registerId)Test public void testCancelRegistration_RefundFee() throws SQLException { // 给定register.id5 的 fee30.00, statussuccess int registerId 5; // 当执行退号 SqlUse.cancelRegistration(registerId); // 那么register.status 变为 cancelled且 schedule.used -1 Connection conn SqlUse.getConnection(); PreparedStatement ps conn.prepareStatement(SELECT status FROM register WHERE id ?); ps.setInt(1, registerId); ResultSet rs ps.executeQuery(); rs.next(); assertEquals(cancelled, rs.getString(status)); // 验证 schedule.used 是否减少 ps conn.prepareStatement(SELECT used FROM schedule WHERE id (SELECT schedule_id FROM register WHERE id ?)); ps.setInt(1, registerId); rs ps.executeQuery(); rs.next(); // 假设原 used5则现在应为 4 assertTrue(rs.getInt(used) 4); }5.3 测试角色权限隔离PatientControl.getAccessibleWindows(User user)这个方法决定用户能看到哪些菜单项。测试不同角色Test public void testGetAccessibleWindows_ForDoctor() { User doctor new User(); doctor.setRole(doctor); ListString windows PatientControl.getAccessibleWindows(doctor); // 医生只能看到我的排班、退号处理、个人资料 assertTrue(windows.contains(MyScheduleFrame)); assertTrue(windows.contains(CancelRequestFrame)); assertFalse(windows.contains(AdminReportFrame)); // 管理员专属 } Test public void testGetAccessibleWindows_ForPatient() { User patient new User(); patient.setRole(patient); ListString windows PatientControl.getAccessibleWindows(patient); assertTrue(windows.contains(RegisterPanel)); assertTrue(windows.contains(MyRecordsPanel)); assertFalse(windows.contains(UserManagementFrame)); // 患者不可见 }为什么这么做因为课设答辩时老师最爱问“如果我用患者账号能访问管理员功能吗”——你当场打开 JUnit 测试类点 Run绿色通过比说一百遍“有权限控制”都硬核。从那以后我每次重构业务逻辑都强制走一遍这三组测试再提交代码。希望帮到你。本文还有配套的精品资源点击获取
返回列表