
简介这是一套基于MySQL与Qt开发的医疗预约系统完整项目源码面向计算机相关专业学生及企业员工适用于课程大作业、毕业设计、初期项目立项演示等场景也可作为Qt界面开发与数据库设计的实战练习素材。压缩包共22个文件约707KB包含2个cpp源文件、1个h头文件、1个ui界面文件与1个qrc资源文件另有15张png及1张jpg截图覆盖ER图、思维导图、科室选择、约满提示、患者与医生信息等界面与数据表设计并附README说明文档。项目围绕挂号预约核心流程涉及科室、医生、患者等数据表结构配合界面截图可直观理解系统功能划分与交互逻辑。目前已有79人学习下载适合希望掌握Qt与MySQL结合开发、快速搭建医疗类管理系统原型的读者参考借鉴。1. 从一份「基于MySQL和Qt的医疗预约系统源码」说起它到底能解决什么问题如果你正在做课程设计、毕业设计或者想找一个能跑通「客户端 数据库」完整链路的桌面项目练手那基于 MySQL 和 Qt 的医疗预约系统源码大概率会出现在你的候选清单里。它要解决的核心问题很具体把挂号、排班、预约、取消这些线下窗口动作搬到一个 C 桌面客户端里数据落到 MySQL界面用 Qt Widgets 搭出来。适合谁适合已经学过 C 基础、想搞明白「一个真实业务系统怎么分层、怎么连库、怎么处理并发预约」的人也适合需要交付一个可演示、可答辩项目的同学。但这里有个反直觉的地方很多人拿到源码压缩包第一反应是双击 .pro 文件、点运行然后卡在 MySQL 驱动加载失败或者中文乱码上折腾两天就放弃了。真正决定这个项目能不能跑起来的不是业务代码写得多漂亮而是环境链路——Qt 版本、编译器、MySQL 驱动、字符集这四样东西必须对齐。这篇笔记就按「先立住原理再动手复现最后讲坑」的顺序把这份医疗预约系统源码从环境到核心业务拆一遍让你拿到包之后知道先看哪、先改哪、哪里最容易翻车。2. 医疗预约系统的技术选型为什么是 MySQL Qt 而不是别的组合2.1 桌面端选 Qt 的四个现实理由医疗预约系统本质上是一个带界面的数据库应用选型时最该问的是「我要不要跨平台、要不要跟硬件打交道、团队会不会 C」。Qt 在这三点上给出的答案比较干脆一套代码可以在 Windows 和 Linux 上编译Qt Widgets 的控件体系足够撑起表单、表格、日历这类医疗场景常见界面而且 Qt 对串口、打印、本地文件这些医院外设场景有现成支持。从源码结构上看一个典型的 Qt 医疗预约系统会分成这么几层main.cpp负责启动和登录窗口widget或ui目录放各个界面类dao或db目录封装数据库访问model目录放医生、患者、预约记录这些实体。这种分层不是摆设它决定了你后面改需求时是改一个文件还是改十个文件。我一般会先看db目录里连接是怎么建的因为那里藏着整个项目能不能跑起来的钥匙。另一个现实理由是 Qt 的信号槽机制。预约系统里「选完科室刷新医生列表」「选完医生刷新可预约时段」这种联动用信号槽写起来比手写回调干净得多。源码里如果用了connect把下拉框的currentIndexChanged接到查询函数上那说明作者是按 Qt 的正常思路写的这种代码你读起来会顺很多。2.2 MySQL 在预约场景里承担什么角色MySQL 在这个系统里干三件事存基础数据科室、医生、排班、存业务数据患者、预约记录、保证预约不冲突。第三件事最关键也是很多人忽略的地方。预约冲突的本质是「同一个医生、同一个时段不能被两个人同时占上」这在数据库层面要么靠唯一索引要么靠事务加行锁。常见做法是给预约表建一个联合唯一索引比如(doctor_id, schedule_id, patient_id)或者(doctor_id, appoint_date, time_slot)具体看排班怎么设计。有了这个索引即使两个客户端同时提交数据库也会挡住第二条。源码里如果只靠界面按钮置灰来防重复那是有漏洞的因为按钮状态管不住并发请求。这一点在后面避坑章节会展开。MySQL 的版本选择上5.7 和 8.0 都能用但驱动配置不一样。8.0 默认认证插件是caching_sha2_password老版本 Qt 的 MySQL 驱动可能连不上需要改成mysql_native_password。这不是源码的问题是环境的问题但足以让你卡半天。2.3 源码包到手后的第一件事先看目录再动手拿到基于MySQL和Qt的医疗预约系统源码项目说明.zip之后别急着编译。先解压把目录结构过一遍重点确认四样东西.pro文件在哪、数据库脚本通常是.sql在哪、Qt 版本要求写在哪个文件里、有没有 README 或项目说明。这一步花五分钟能省后面两小时。目录/文件作用你要确认的点*.proQt 工程文件QT 模块是否包含sqlLIBS是否指向 MySQL 驱动*.sql建库建表脚本字符集是否utf8mb4有没有初始数据db/*.cpp数据库连接封装主机、端口、库名、用户名、密码ui/*.ui界面文件控件 objectName 是否和代码里一致README项目说明Qt 版本、编译器、依赖库确认完这些你心里就有底了这个项目是 Qt 5 还是 Qt 6、是 MSVC 还是 MinGW、数据库叫什么名字。这些信息决定了你后面装什么、配什么。3. 把源码跑起来Qt 环境、MySQL 建库、驱动配置的最小步骤3.1 Qt 版本与编译器怎么选才不返工Qt 的版本和编译器组合是这类项目第一个大坑。源码如果是 Qt 5.15.2 MSVC2019 写的你拿 Qt 6 MinGW 去开大概率一堆编译错误。所以第一步是看.pro或者项目说明里写的版本然后装对应的。安装时在 Qt Online Installer 里勾选组件注意这几项对应版本的Qt主模块、Qt Creator、以及和编译器匹配的套件。如果你用 MSVC还要装 Visual Studio 的 C 生成工具如果用 MinGWQt 安装器里自带。选错编译器的典型症状是链接阶段报LNK2019或者找不到Qt5Sql.dll。提示Qt 5.15 之后的开源版本在线安装器需要注册账号下载慢的话可以换国内镜像源但组件选择逻辑不变。装完之后用 Qt Creator 打开.pro文件先别点运行点「构建」看能不能过。构建过了再谈运行构建不过就是环境问题跟业务代码无关。3.2 MySQL 建库与字符集设置数据库这一步核心是把.sql脚本导进去并且保证字符集对。医疗系统里患者姓名、科室名称都是中文字符集错了就是一堆问号。推荐统一用utf8mb4排序规则用utf8mb4_general_ci或utf8mb4_unicode_ci。-- 创建数据库显式指定字符集避免中文乱码 CREATE DATABASE medical_appointment DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci; USE medical_appointment; -- 医生表存医生基本信息和所属科室 CREATE TABLE doctor ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, department_id INT NOT NULL, title VARCHAR(30), INDEX idx_dept (department_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 预约表联合唯一索引防止同一医生同一时段被重复预约 CREATE TABLE appointment ( id INT PRIMARY KEY AUTO_INCREMENT, patient_id INT NOT NULL, doctor_id INT NOT NULL, appoint_date DATE NOT NULL, time_slot VARCHAR(20) NOT NULL, status TINYINT DEFAULT 0, UNIQUE KEY uk_doctor_slot (doctor_id, appoint_date, time_slot) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这段脚本里有两个关键点。第一ENGINEInnoDB必须写因为只有 InnoDB 支持事务和行锁MyISAM 挡不住并发预约。第二uk_doctor_slot这个联合唯一索引是防重复预约的最后一道防线界面层可以提示但真正兜底的是它。status字段用TINYINT表示预约状态0 待就诊、1 已就诊、2 已取消比用字符串省空间也好比较。导入脚本可以用命令行也可以用 Navicat 这类工具。命令行方式# 登录 MySQL 并导入脚本注意 -u 和 -p 之间不要有空格 mysql -u root -p medical_appointment medical_appointment.sql # 验证字符集是否生效 mysql -u root -p -e SHOW VARIABLES LIKE character_set_database;如果导入后中文显示为乱码先查数据库字符集再查连接字符集。连接字符集在 Qt 代码里设置下一节讲。3.3 Qt 连接 MySQL驱动加载与连接参数Qt 连 MySQL 靠的是QSqlDatabase和QMYSQL驱动。这里最常见的翻车是「QMYSQL driver not loaded」原因通常是 Qt 自带的 MySQL 驱动和本机 MySQL 客户端库版本不匹配或者根本没编驱动。先确认 Qt 有没有 QMYSQL 驱动。在代码里打印一下可用驱动#include QSqlDatabase #include QDebug // 在 main 或初始化处调用列出当前 Qt 支持的数据库驱动 void listDrivers() { QStringList drivers QSqlDatabase::drivers(); qDebug() Available drivers: drivers; // 如果输出里没有 QMYSQL说明驱动没装好需要单独编译 }如果输出里没有QMYSQL两条路一是用 Qt 维护工具重新编译 MySQL 驱动二是换用QODBC走 ODBC 数据源。前者更干净后者配置麻烦但应急可用。编译驱动的核心是把 MySQL 的libmysql.dll和头文件路径喂给 Qt 的驱动工程具体命令随 Qt 版本不同这里不展开但思路是「让 Qt 找到 MySQL 的客户端库」。驱动有了之后连接代码长这样#include QSqlDatabase #include QSqlError #include QDebug bool connectDb() { QSqlDatabase db QSqlDatabase::addDatabase(QMYSQL); db.setHostName(127.0.0.1); // 本机用 127.0.0.1 比 localhost 更稳 db.setPort(3306); // MySQL 默认端口 db.setDatabaseName(medical_appointment); db.setUserName(root); db.setPassword(your_password); // 关键设置连接字符集和数据库保持一致 db.setConnectOptions(MYSQL_OPT_RECONNECT1); if (!db.open()) { qDebug() Connect failed: db.lastError().text(); return false; } // 连接建立后立刻设置字符集防止中文乱码 QSqlQuery query(db); query.exec(SET NAMES utf8mb4); return true; }setConnectOptions(MYSQL_OPT_RECONNECT1)是为了断线重连医疗系统长时间开着连接被 MySQL 超时断开是常事。SET NAMES utf8mb4必须在连接后立刻执行否则后面插入的中文可能变问号。lastError().text()一定要打出来它是你排查连接问题的黑匣子比猜有用得多。3.4 编译运行与首次登录验证环境配好之后构建运行应该能看到登录窗口。首次登录用数据库脚本里的初始账号通常是 admin 之类。登录成功后进主界面重点验证三件事科室列表能不能加载、选科室后医生列表能不能刷新、提交一条预约后数据库里有没有记录。如果界面能开但数据加载不出来先看qDebug输出里有没有 SQL 错误再直接去 MySQL 里手动执行那条查询对比结果。这一步能把「SQL 写错」和「代码没执行到」区分开。我一般会在 DAO 层的查询函数里加一行日志把最终执行的 SQL 打出来复制到 MySQL 客户端里跑一遍问题基本就定位了。4. 预约业务的核心实现排班查询、并发防重与状态流转4.1 排班查询与可预约时段生成预约系统的入口是「选科室 → 选医生 → 选日期 → 选时段」。前两步是简单查询第三步开始有业务逻辑医生不是每天都有排班排班表里存的是「某医生某天上午/下午出诊」可预约时段要根据排班和已有预约算出来。常见做法是两张表schedule存排班医生、日期、时段、总号源appointment存预约记录。查询可预约时段时用排班号源减去已预约数量。SQL 大致是这样-- 查询某医生某天各时段剩余号源 SELECT s.time_slot, s.total_quota, s.total_quota - COUNT(a.id) AS remaining FROM schedule s LEFT JOIN appointment a ON a.doctor_id s.doctor_id AND a.appoint_date s.schedule_date AND a.time_slot s.time_slot AND a.status ! 2 -- 已取消的不占号源 WHERE s.doctor_id ? AND s.schedule_date ? GROUP BY s.time_slot, s.total_quota;这里LEFT JOIN保证没有预约的时段也能显示status ! 2把取消的记录排除掉否则取消一次号源就少一个越用越少。remaining算出来如果小于等于 0界面上那个时段就该置灰。注意GROUP BY里要把total_quota带上不然某些 MySQL 模式下会报错。在 Qt 侧把这段查询封装成一个函数返回一个QList或QVector界面拿到之后填充下拉框或按钮组。信号槽的接法是日期选择器的dateChanged信号连到刷新时段的槽函数槽函数里调 DAO 查询再把结果刷到界面。4.2 并发预约防重唯一索引 事务的配合前面提过界面置灰挡不住并发。真正的防线是数据库唯一索引加事务。提交预约的流程应该是开启事务 → 插入预约记录 → 提交。如果唯一索引冲突插入会失败捕获异常后提示「该时段已被预约」。bool makeAppointment(int patientId, int doctorId, const QString date, const QString slot) { QSqlDatabase db QSqlDatabase::database(); QSqlQuery query(db); // 开启事务保证插入的原子性 if (!db.transaction()) { qDebug() Begin transaction failed; return false; } query.prepare(INSERT INTO appointment (patient_id, doctor_id, appoint_date, time_slot, status) VALUES (?, ?, ?, ?, 0)); query.addBindValue(patientId); query.addBindValue(doctorId); query.addBindValue(date); query.addBindValue(slot); if (!query.exec()) { // 唯一索引冲突会走到这里回滚并提示 qDebug() Insert failed: query.lastError().text(); db.rollback(); return false; } if (!db.commit()) { qDebug() Commit failed; db.rollback(); return false; } return true; }prepareaddBindValue是防 SQL 注入的标准写法别用字符串拼接。transaction和commit成对出现失败必须rollback否则连接会一直挂着未提交事务后面操作全乱。唯一索引冲突时exec返回 falselastError().text()里会有Duplicate entry字样可以据此给用户更精确的提示。注意如果用的是 MySQL 8.0 且隔离级别是默认的 REPEATABLE READ唯一索引冲突检测依然有效不需要额外加锁。但如果你在插入前先SELECT判断是否存在那个判断在并发下不可靠别依赖它。4.3 预约状态流转与取消逻辑预约不是插进去就完事还有就诊、取消这些状态变化。状态字段用整数表示流转规则要写清楚0 待就诊 → 1 已就诊0 待就诊 → 2 已取消1 和 2 是终态不能再变。取消操作不是删除记录而是把status改成 2这样历史可追溯号源也能释放。-- 取消预约只允许从待就诊状态取消 UPDATE appointment SET status 2 WHERE id ? AND status 0;AND status 0这个条件很重要它保证已就诊的记录不能被取消也防止重复取消。执行后检查numRowsAffected()如果是 0说明这条记录状态不对或者不存在界面要给出对应提示而不是笼统报「操作失败」。状态流转的代码最好集中在一个函数里别散落在各个界面类中。我一般会在 DAO 层写一个updateStatus(int id, int from, int to)所有状态变更都走它这样以后加「改期」功能时只改一处。5. 医疗预约系统源码落地避坑五个高频翻车现场5.1 坑一QMYSQL driver not loaded现象程序启动后连接数据库失败qDebug输出QMYSQL driver not loaded但数据库明明能连。原因Qt 自带的sqldrivers目录里没有qsqlmysql.dll或者该 dll 依赖的libmysql.dll版本和本机 MySQL 不一致。Qt 出于许可原因不预编译 MySQL 驱动需要自己编。解决先确认 Qt 安装目录下plugins/sqldrivers有没有qsqlmysql.dll。没有就用 Qt 的驱动源码编译编译时指定 MySQL 的头文件和库路径。编好后把qsqlmysql.dll放到sqldrivers目录把libmysql.dll放到可执行文件同目录。用QSqlDatabase::drivers()验证是否出现QMYSQL。5.2 坑二中文乱码患者姓名变问号现象界面输入中文正常存进数据库变成???或者数据库里正常但界面显示乱码。原因字符集链路某一环没对齐。MySQL 服务端、数据库、表、连接、Qt 字符串转换五处只要有一处不是utf8mb4就可能出问题。解决按链路逐段查。数据库和表用utf8mb4连接后执行SET NAMES utf8mb4Qt 里QString转std::string用toUtf8()而不是toLocal8Bit()。用SHOW VARIABLES LIKE character%看服务端设置用SHOW CREATE TABLE appointment看表字符集。5.3 坑三预约重复同一时段被约两次现象两个客户端同时提交同一医生同一时段两条都成功了。原因只靠界面判断或先查后插并发下判断失效。解决给预约表加联合唯一索引(doctor_id, appoint_date, time_slot)插入用事务捕获唯一索引冲突。界面置灰只是体验优化不是防线。加索引前如果已有重复数据要先清理再建索引否则建不上。5.4 坑四Qt 版本不匹配导致编译报错现象打开.pro构建报一堆undefined reference或找不到头文件。原因源码用的 Qt 版本和你装的不一致比如源码是 Qt 5.15 用了QRegExp你装 Qt 6 里已经移除或改了。解决看项目说明或.pro里的版本要求装对应版本。如果必须用新版本把废弃 API 逐个替换QRegExp换QRegularExpressionQString::split的行为差异也要注意。别硬扛版本对齐是最省时间的做法。5.5 坑五数据库连接超时后界面卡死现象程序开久了点查询没反应或者报连接已断开。原因MySQL 默认wait_timeout是 8 小时但有些环境更短空闲连接被服务端断开Qt 这边不知道下次查询就失败。解决连接时加MYSQL_OPT_RECONNECT1让驱动自动重连或者在 DAO 层每次操作前检查db.isOpen()没开就重连。更稳的做法是加一个定时器定期执行SELECT 1保活。界面卡死往往是查询在等一个已经断掉的连接加超时设置也能缓解。6. 从能跑到好用给医疗预约系统加一层可验证的自动化检查源码能跑起来只是起点真正让它「好用」的是你能验证它没坏。我一般会给这类项目加一个轻量的自检入口不依赖界面直接跑数据库层的核心逻辑这样每次改完代码点一下就知道有没有回归。具体做法是写一个SelfTest类覆盖四个检查点数据库连接是否正常、科室和医生数据能否查出、预约插入和唯一索引是否生效、取消后号源是否释放。用QTest或者干脆一个main函数跑断言都行。下面是一个不依赖测试框架的最小版本#include QSqlQuery #include QSqlError #include QDebug // 返回通过的检查项数量方便快速判断 int runSelfTest() { int passed 0; QSqlQuery q; // 检查 1连接可用 if (QSqlDatabase::database().isOpen()) { qDebug() [PASS] db connected; passed; } else { qDebug() [FAIL] db not connected; return passed; // 连接都没有后面不用跑了 } // 检查 2基础数据存在 if (q.exec(SELECT COUNT(*) FROM doctor) q.next() q.value(0).toInt() 0) { qDebug() [PASS] doctor data exists; passed; } else { qDebug() [FAIL] doctor table empty or query error: q.lastError().text(); } // 检查 3唯一索引生效重复插入应失败 q.exec(INSERT INTO appointment (patient_id, doctor_id, appoint_date, time_slot, status) VALUES (1, 1, 2025-01-01, AM, 0)); bool firstOk (q.numRowsAffected() 1); q.exec(INSERT INTO appointment (patient_id, doctor_id, appoint_date, time_slot, status) VALUES (2, 1, 2025-01-01, AM, 0)); bool secondBlocked (q.numRowsAffected() 0); if (firstOk secondBlocked) { qDebug() [PASS] unique index blocks duplicate; passed; } else { qDebug() [FAIL] duplicate appointment was not blocked; } // 清理测试数据避免污染 q.exec(DELETE FROM appointment WHERE appoint_date 2025-01-01); return passed; }这段自检的价值在于它把「我以为没问题」变成「跑一遍就知道」。检查 3 尤其关键它直接验证了防重复预约这道防线还在不在。测试数据用完立刻删掉别留在正式库里。返回的passed数量可以接到启动日志里每次启动看一眼三项全过心里就踏实。参数上唯一要注意的是测试用的doctor_id和patient_id得是真实存在的否则外键约束会让插入失败误判成索引问题。如果你的表没建外键那倒无所谓但建议还是用真实 ID。最后一个习惯每次改完 DAO 层的 SQL先跑自检再开界面点。界面能骗人自检不会。这套东西花不了半小时但能帮你省下无数次「明明刚才还好好的」的抓狂。希望帮到你。本文还有配套的精品资源点击获取