资讯动态

Qt+MySQL机房预约系统课程设计实战指南

发布时间:2026/10/22 5:01:46 来源:尧图企业网站定制
简介本资源是一套完整的C课程设计级机房预约系统实现面向计算机专业本科生及C语言进阶学习者聚焦QT图形界面开发与MySQL数据库集成实践适用于毕业设计参考、课程大作业提交及GUIDB综合项目练手。压缩包共47个文件含11个头文件.h定义类结构与全局接口、10个源文件.cpp实现登录、管理员/教师/学生三端核心逻辑、4个.ui文件构建可视化界面、1个.pro工程配置及Makefile等编译支撑文件整体仅2.8MB轻量易部署。目前已有75人学习下载资源经本地全环境编译验证可直接运行配套README.md说明清晰包含数据库建表语句、连接配置要点及模块调用关系代码结构分层明确涵盖connection.h数据库封装、ui_*.h界面绑定、orderInfo.log运行日志记录等实用设计助读者深入理解MVC雏形与跨模块协作机制。1. 为什么一个机房预约系统值得用 Qt MySQL 做成课程设计大作业这不是又一个“登录增删改查”的练手 Demo。某高校计算机系连续三年把「机房预约系统」列为《C 面向对象程序设计》和《数据库原理与应用》两门课的联合大作业背后有非常实在的教学逻辑它天然覆盖了GUI 交互完整性、本地业务逻辑闭环、数据持久化强一致性、多用户并发场景模拟这四个本科生最容易“纸上谈兵”却最难落地的关键能力断层。学生常卡在“界面能点但数据不存”“预约成功但别人还能抢同一时段”“导出报表时中文全乱码”这类问题上——而这些问题恰恰是工业级桌面应用每天要面对的真实约束。用 Qt 写界面不是为了炫酷动画而是因为它原生支持信号槽机制能把“点击预约按钮 → 校验时段冲突 → 插入预约记录 → 刷新日历视图”这一串动作写成可读、可调试、可单步跟踪的线性逻辑用 MySQL 不是为了堆技术名词而是因为它的事务隔离级别尤其是REPEATABLE READ能真实模拟“两人同时刷屏抢最后一台 i7 主机”时谁该成功、谁该失败、错误提示是否精准。如果你正被课程设计 deadline 追着跑又不想交一个只能本地单机运行、一加用户就崩的“假系统”这篇笔记就是你从压缩包解压后真正能跑通、能答辩、能写进简历项目经历的实操路径。2. 从 .zip 解压到第一个可运行窗口Qt 环境搭建与主界面骨架生成2.1 解压后先看懂目录结构别急着编译先认文件拿到课程设计大作业机房预约系统QTMySQL.zip后解压得到的典型目录如下实际可能略有差异但核心结构一致/room_booking_system/ ├── src/ # C 源码主目录 │ ├── main.cpp # 程序入口创建 QApplication 和主窗口 │ ├── mainwindow.h/.cpp # 主窗口类定义与实现含菜单栏、工具栏、中心部件 │ ├── login.h/.cpp # 登录对话框类独立模态窗 │ ├── bookingdialog.h/.cpp # 预约操作对话框选择机房、时段、填写信息 │ └── database.h/.cpp # 数据库连接与基础查询封装类 ├── resources/ # 图标、SQL 初始化脚本、样式表 │ ├── init_db.sql # 创建表结构与初始数据机房信息、管理员账号等 │ └── style.qss # Qt 样式表可选用于美化界面 ├── build/ # 编译输出目录首次需手动创建 └── room_booking_system.pro # Qt 项目配置文件关键决定编译选项和依赖提示room_booking_system.pro是 Qt 项目的“宪法”。它声明了哪些.h/.cpp文件参与编译、链接哪些 Qt 模块QT core gui widgets sql必须包含sql、是否启用 C17 标准CONFIG c17推荐。如果编译报错QSqlDatabase: No such file90% 是这里漏了sql。2.2 Qt Creator 中导入并配置项目三步确认法打开 Qt Creator → “Open Project” → 选择room_booking_system.pro不要直接打开.cpp文件Qt Creator 会自动读取.pro文件配置构建环境。确认 Kit 配置关键在左下角“Projects”模式中检查Compiler: 推荐 MSVC 2019Windows或 ClangmacOS避免 MinGW对 MySQL 驱动兼容性差Qt Version: 必须 ≥ 5.12低版本QSqlQuery::execBatch()支持不完善Build Directory: 设为./build与源码分离避免污染。添加 MySQL 驱动支持Windows 下最常翻车点Qt 默认不带 MySQL 插件。需手动复制qsqlmysql.dll到 Qt 安装目录的plugins/sqldrivers/下。获取方式下载 MySQL Connector/C 安装后在lib/目录找到libmysql.dll再用 Qt 自带的windeployqt工具或手动将qsqlmysql.dll位于Qt/5.x/MSVC2019_64/plugins/sqldrivers/复制到项目build/目录同级的sqldrivers/文件夹中。验证是否生效在main.cpp中临时加一行qDebug() QSqlDatabase::drivers();运行后应看到QSQLITE, QMYSQL, QODBC等输出。2.3 运行第一个窗口修改main.cpp并处理登录跳转逻辑// src/main.cpp #include QApplication #include QMessageBox #include login.h // 引入登录对话框类 #include mainwindow.h int main(int argc, char *argv[]) { QApplication a(argc, argv); // 1. 先弹出登录窗口模态阻塞后续执行 LoginDialog login; if (login.exec() ! QDialog::Accepted) { return -1; // 用户取消登录直接退出 } // 2. 登录成功后才创建主窗口 MainWindow w; w.show(); return a.exec(); }逻辑说明login.exec()是模态对话框用户不关闭它程序不会执行w.show()QDialog::Accepted表示用户点击了“登录”按钮且验证通过登录类内部需实现用户名密码校验逻辑并调用accept()若登录失败exec()返回QDialog::Rejected程序直接退出避免未授权访问主界面。参数说明exec()与show()的区别exec()阻塞当前线程适合登录、设置等一次性操作show()非阻塞适合主窗口QApplication::exec()是事件循环入口必须放在最后否则界面无响应。3. 让数据真正落盘MySQL 表结构设计与 Qt 数据库连接封装3.1 从init_db.sql看懂核心四张表为什么这样设计解压后打开resources/init_db.sql你会看到类似以下建表语句已简化关键字段-- 机房基础信息表room CREATE TABLE room ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL COMMENT 机房名称如 A301, capacity INT NOT NULL COMMENT 总座位数, status ENUM(available, maintenance) DEFAULT available ); -- 预约记录表booking CREATE TABLE booking ( id BIGINT PRIMARY KEY AUTO_INCREMENT, room_id INT NOT NULL, user_id INT NOT NULL, start_time DATETIME NOT NULL, end_time DATETIME NOT NULL, purpose VARCHAR(100), status ENUM(pending, confirmed, canceled) DEFAULT pending, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (room_id) REFERENCES room(id) ON DELETE CASCADE ); -- 用户表user—— 注意课程设计通常只需区分管理员/普通用户 CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(30) UNIQUE NOT NULL, password_hash VARCHAR(128) NOT NULL, -- 存储 bcrypt 或 SHA256 加盐哈希 role ENUM(admin, student) DEFAULT student, real_name VARCHAR(20) ); -- 预约时段规则表time_slot—— 控制可预约的起止时间与粒度 CREATE TABLE time_slot ( id INT PRIMARY KEY AUTO_INCREMENT, start_hour TINYINT NOT NULL, -- 如 8 表示 08:00 end_hour TINYINT NOT NULL, -- 如 22 表示 22:00 duration_min SMALLINT DEFAULT 60 -- 每次预约最小单位分钟 );设计理由深度拆解booking表中start_time和end_time用DATETIME而非DATE TIME避免跨日预约如 22:00–02:00时日期计算错误FOREIGN KEY ... ON DELETE CASCADE当某个机房停用DELETE FROM room WHERE id5其所有历史预约自动清除防止脏数据time_slot表独立存在课程设计答辩时老师常问“如何限制学生只能预约 8:00–22:00 之间且每次至少 1 小时”此表即答案——前端加载时段时只查此表而非硬编码status字段用ENUM比VARCHAR节省空间且数据库层强制取值范围避免代码里传入非法字符串如approved。3.2database.h封装一个安全、可复用的数据库单例// src/database.h #ifndef DATABASE_H #define DATABASE_H #include QSqlDatabase #include QSqlQuery #include QSqlError #include QString class Database { public: static Database instance() { static Database inst; return inst; } bool init(const QString host, const QString dbname, const QString user, const QString pwd, int port 3306); QSqlQuery execQuery(const QString queryStr); bool execTransaction(const QStringList queries); // 批量执行支持回滚 private: Database() default; // 私有构造禁止外部实例化 QSqlDatabase db; }; #endif // DATABASE_H// src/database.cpp #include database.h #include QSqlError #include QDebug bool Database::init(const QString host, const QString dbname, const QString user, const QString pwd, int port) { db QSqlDatabase::addDatabase(QMYSQL); db.setHostName(host); db.setDatabaseName(dbname); db.setUserName(user); db.setPassword(pwd); db.setPort(port); if (!db.open()) { qCritical() 数据库连接失败: db.lastError().text(); return false; } qDebug() 数据库连接成功; return true; } QSqlQuery Database::execQuery(const QString queryStr) { QSqlQuery query(db); if (!query.exec(queryStr)) { qWarning() SQL 执行失败: query.lastError().text() SQL: queryStr; } return query; }关键参数说明QSqlDatabase::addDatabase(QMYSQL)必须指定驱动名大小写敏感db.setPort(port)显式传入端口默认 3306避免因 MySQL 服务监听非标准端口导致连接超时qCritical()与qWarning()比qDebug()优先级更高qCritical()用于不可恢复错误如连接失败qWarning()用于可恢复异常如某条 UPDATE 影响 0 行为什么用单例整个程序生命周期内只需一个数据库连接避免频繁 open/close 开销多个窗口登录、主界面、预约对话框都能通过Database::instance()访问同一连接。3.3 在登录模块中验证用户凭据参数化查询防 SQL 注入// src/login.cpp 中的登录验证逻辑 bool LoginDialog::validateUser(const QString username, const QString password) { // 1. 使用占位符 ?而非字符串拼接 QString sql SELECT id, role, real_name FROM user WHERE username ? AND password_hash ?; // 2. 获取数据库实例并执行 QSqlQuery query Database::instance().execQuery(sql); query.addBindValue(username); query.addBindValue(QCryptographicHash::hash(password.toUtf8(), QCryptographicHash::Sha256).toHex()); if (query.next()) { currentUser.id query.value(0).toInt(); currentUser.role query.value(1).toString(); currentUser.name query.value(2).toString(); return true; } return false; }血泪经验绝对禁止QString(SELECT ... WHERE username username )—— 这是 SQL 注入温床QCryptographicHash::hash(..., Sha256)是 Qt 内置哈希函数无需额外引入 OpenSSLquery.next()返回true表示查到一行此时才能安全调用query.value(0)若直接query.value(0)会返回空QVariant导致后续逻辑崩溃。4. 预约功能落地冲突检测、事务提交与界面实时反馈4.1 冲突检测的核心 SQL一条语句查清“这个时段这个机房是否已被约”这是整个系统最易出错、也最体现设计水平的部分。常见错误是“先查再插”导致并发时两个用户同时查到“空闲”然后都插入成功——这就是典型的竞态条件Race Condition。正确做法用一条SELECT COUNT(*)语句在数据库层面原子性判断-- 检测是否存在冲突同一机房新预约时段与任一已存在预约时段有重叠 SELECT COUNT(*) FROM booking WHERE room_id ? AND status confirmed AND NOT (end_time ? OR start_time ?);参数说明按?顺序绑定room_id用户选择的机房 IDnew_end_time用户想预约的结束时间如2024-05-20 10:30:00new_start_time用户想预约的开始时间如2024-05-20 09:30:00为什么这个条件能检测重叠数学上两时间段[A, B]与[C, D]不重叠的充要条件是B ≤ C OR D ≤ A。所以“有重叠”就是NOT (B ≤ C OR D ≤ A)即B C AND D A—— 这正是 SQL 中NOT (end_time ? OR start_time ?)的含义。4.2 在 Qt 中执行冲突检测与预约插入事务保证原子性// src/bookingdialog.cpp bool BookingDialog::bookRoom(int roomId, const QDateTime start, const QDateTime end, const QString purpose) { Database db Database::instance(); // 1. 开启事务 if (!db.db.transaction()) { qWarning() 事务开启失败: db.db.lastError().text(); return false; } // 2. 检测冲突 QString conflictSql R( SELECT COUNT(*) FROM booking WHERE room_id ? AND status confirmed AND NOT (end_time ? OR start_time ?) ); QSqlQuery conflictQuery db.execQuery(conflictSql); conflictQuery.addBindValue(roomId); conflictQuery.addBindValue(end.toString(yyyy-MM-dd hh:mm:ss)); conflictQuery.addBindValue(start.toString(yyyy-MM-dd hh:mm:ss)); if (!conflictQuery.next()) { db.db.rollback(); return false; } int conflictCount conflictQuery.value(0).toInt(); if (conflictCount 0) { QMessageBox::warning(this, 预约失败, 所选时段该机房已被预约请选择其他时段。); db.db.rollback(); return false; } // 3. 插入新预约 QString insertSql R( INSERT INTO booking (room_id, user_id, start_time, end_time, purpose, status) VALUES (?, ?, ?, ?, ?, confirmed) ); QSqlQuery insertQuery db.execQuery(insertSql); insertQuery.addBindValue(roomId); insertQuery.addBindValue(currentUser.id); insertQuery.addBindValue(start.toString(yyyy-MM-dd hh:mm:ss)); insertQuery.addBindValue(end.toString(yyyy-MM-dd hh:mm:ss)); insertQuery.addBindValue(purpose); if (!insertQuery.exec()) { qWarning() 预约插入失败: insertQuery.lastError().text(); db.db.rollback(); return false; } // 4. 提交事务 if (!db.db.commit()) { qWarning() 事务提交失败: db.db.lastError().text(); return false; } QMessageBox::information(this, 预约成功, 您的预约已提交可在主界面查看。); return true; }逻辑说明db.transaction()/db.commit()/db.rollback()构成事务边界所有QSqlQuery必须使用同一个QSqlDatabase实例即db.db否则事务无效rollback()在任何一步失败时都必须调用否则连接会处于“事务中”状态后续查询可能被阻塞。4.3 主界面日历视图实时刷新用QTableViewQSqlQueryModel绑定数据// src/mainwindow.cpp 中初始化预约列表 void MainWindow::initBookingTable() { model new QSqlQueryModel(this); ui-bookingTableView-setModel(model); // 设置列标题中文友好 model-setHeaderData(0, Qt::Horizontal, ID); model-setHeaderData(1, Qt::Horizontal, 机房); model-setHeaderData(2, Qt::Horizontal, 预约人); model-setHeaderData(3, Qt::Horizontal, 开始时间); model-setHeaderData(4, Qt::Horizontal, 结束时间); model-setHeaderData(5, Qt::Horizontal, 事由); model-setHeaderData(6, Qt::Horizontal, 状态); // 关键设置查询自动绑定到表格 refreshBookingView(); } void MainWindow::refreshBookingView() { QString sql R( SELECT b.id, r.name, u.real_name, b.start_time, b.end_time, b.purpose, b.status FROM booking b JOIN room r ON b.room_id r.id JOIN user u ON b.user_id u.id WHERE b.status confirmed ORDER BY b.start_time DESC ); model-setQuery(sql, Database::instance().db); }避坑点QSqlQueryModel是只读模型不能直接编辑单元格符合预约系统“查看只读、操作走对话框”的业务逻辑setQuery()必须传入QSqlDatabase实例Database::instance().db否则模型无法获取连接ORDER BY b.start_time DESC确保最新预约排在最上面符合用户直觉。5. 避坑指南课程设计中最常踩的 5 个深坑及解决方案5.1 现象编译通过运行时报错QSqlDatabase: QMYSQL driver not loaded原因Qt 编译时链接了 MySQL 库但运行时找不到qsqlmysql.dll或其依赖的libmysql.dll。解决确认qsqlmysql.dll已复制到build/目录下的sqldrivers/子目录不是 Qt 安装目录使用 Dependency Walker 检查qsqlmysql.dll是否缺失libmysql.dll若缺失将 MySQL Connector/C 的libmysql.dll复制到build/目录与room_booking_system.exe同级Windows 系统 PATH 中不要包含其他版本的libmysql.dll避免 DLL 劫持。5.2 现象中文插入 MySQL 后显示为???或从数据库读出中文在界面上是方块原因Qt、MySQL 服务端、MySQL 数据库/表字符集三者不统一。解决MySQL 服务端配置my.ini中添加[mysqld] character-set-serverutf8mb4 collation-serverutf8mb4_unicode_ci创建数据库时显式指定CREATE DATABASE room_booking CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;Qt 连接时设置编码在Database::init()中db.open()前添加db.setConnectOptions(MYSQL_INIT_COMMANDSET NAMES utf8mb4);5.3 现象两个用户几乎同时预约同一机房同一时段结果都成功了竞态条件原因没有使用事务或冲突检测与插入分成了两条独立 SQL非原子。解决必须用transaction()包裹整个检测插入流程冲突检测 SQL 必须用NOT (end_time ? OR start_time ?)这种数学严谨写法禁用模糊的BETWEEN在 MySQL 中为booking表的(room_id, start_time, end_time)字段建立复合索引加速冲突查询CREATE INDEX idx_room_time ON booking(room_id, start_time, end_time);5.4 现象点击“预约”按钮后界面卡死几秒再弹出成功/失败提示原因数据库操作尤其是QSqlQuery::exec()在 GUI 主线程同步执行阻塞了事件循环。解决对于简单查询如登录验证、冲突检测保持同步即可课程设计数据量小影响不大若需优化可将耗时操作移至QThread或QThreadPool但需注意QSqlDatabase不能跨线程共享每个线程需单独addDatabase更推荐方案在按钮点击后立即设置ui-bookButton-setEnabled(false)操作完成后恢复给用户明确反馈。5.5 现象导出预约记录为 Excel 时日期格式混乱如2024-05-20T09:30:00原因QDateTime直接转QString未指定格式Qt 默认使用 ISO 格式。解决导出前统一格式化QString formattedTime dateTime.toString(yyyy-MM-dd hh:mm);使用QXlsx库需在.pro中添加LIBS -L$$PWD/lib -lQXlsx替代手动拼 CSV它原生支持QDateTime自动映射为 Excel 日期类型。6. 答辩加分技巧让老师眼前一亮的三个细节实现6.1 用QCalendarWidget实现可视化机房占用日历非简单表格很多同学只用QTableView列出预约记录但老师更想看到“直观的占用热力图”。QCalendarWidget可以自定义每日背景色我们用它展示某机房一周内的占用情况// src/mainwindow.cpp void MainWindow::updateRoomCalendar(int roomId) { QCalendarWidget *cal ui-calendarWidget; cal-setGridVisible(true); // 清除之前高亮 cal-setDateTextFormat(QDate(), QTextCharFormat()); // 查询该机房本周所有 confirmed 预约 QString sql R( SELECT DATE(start_time) as date_day, COUNT(*) as cnt FROM booking WHERE room_id ? AND status confirmed AND start_time ? AND start_time ? GROUP BY DATE(start_time) ); QSqlQuery query Database::instance().execQuery(sql); query.addBindValue(roomId); query.addBindValue(QDate::currentDate().toString(yyyy-MM-dd)); query.addBindValue(QDate::currentDate().addDays(7).toString(yyyy-MM-dd)); QTextCharFormat occupiedFormat; occupiedFormat.setBackground(Qt::red); occupiedFormat.setForeground(Qt::white); while (query.next()) { QDate date QDate::fromString(query.value(0).toString(), yyyy-MM-dd); cal-setDateTextFormat(date, occupiedFormat); } }效果日历上被预约的日期显示为红色底色空白日期为白色一目了然。老师提问“如何快速查看某机房下周是否空闲”时你可以直接点开日历切换机房当场演示。6.2 预约对话框中动态加载可用时段基于time_slot表 当前时间过滤避免让用户手动输入时间易错而是提供下拉框选择“可预约时段”// src/bookingdialog.cpp void BookingDialog::loadAvailableSlots() { ui-startTimeCombo-clear(); ui-endTimeCombo-clear(); QString sql R( SELECT start_hour, end_hour, duration_min FROM time_slot WHERE start_hour ? ORDER BY start_hour ); QSqlQuery query Database::instance().execQuery(sql); query.addBindValue(QTime::currentTime().hour()); // 只显示当前时间之后的时段 while (query.next()) { int startH query.value(0).toInt(); int endH query.value(1).toInt(); int dur query.value(2).toInt(); QString slotStr QString(%1:00 - %2:00 (%3分钟)) .arg(startH, 2, 10, QChar(0)) .arg(endH, 2, 10, QChar(0)) .arg(dur); ui-startTimeCombo-addItem(slotStr, QVariantList() startH endH dur); } }价值业务规则如“最早只能约当前小时之后”由数据库控制修改只需改time_slot表无需改代码QVariantList存储原始数值addItem的userData参数让界面与逻辑解耦。6.3 用QSettings保存用户最近一次登录的用户名提升体验课程设计虽小但细节见真章。记住用户名是基本用户体验// src/login.cpp void LoginDialog::on_loginButton_clicked() { QString username ui-usernameEdit-text().trimmed(); QString password ui-passwordEdit-text(); // 登录成功后保存用户名 if (validateUser(username, password)) { QSettings settings(MyCompany, RoomBooking); settings.setValue(lastUsername, username); accept(); } } // src/login.cpp 构造函数中读取 LoginDialog::LoginDialog(QWidget *parent) : QDialog(parent), ui(new Ui::LoginDialog) { ui-setupUi(this); QSettings settings(MyCompany, RoomBooking); QString lastUser settings.value(lastUsername).toString(); if (!lastUser.isEmpty()) { ui-usernameEdit-setText(lastUser); ui-usernameEdit-setFocus(); } }注意QSettings在 Windows 下存注册表HKEY_CURRENT_USER\Software\MyCompany\RoomBooking无需额外配置开箱即用。我带过三届课程设计见过太多同学花两周做界面美化却在答辩前夜发现“并发预约会重复”这种底层问题。其实只要守住三个底线数据库操作必用事务、字符串操作必用参数化查询、时间处理必用QDateTime而非字符串拼接你的系统就已超过 80% 的同龄人。那些看似玄学的“界面卡顿”“中文乱码”追根溯源都是对 Qt 事件循环、MySQL 字符集、SQL 原子性这些基础概念的敬畏不足。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑