本文还有配套的精品资源点击获取简介这个门禁系统用C和Qt开发Windows下能直接编译运行带完整的图形操作界面主控窗口、门禁操作面板、设置页面。核心功能包括AES加解密maesclass、OpenSSL集成opensslclass、TAES算法封装taesclass所有UI文件.ui、头文件.h、实现文件.cpp都齐全工程用qmake管理EntranceGuard.pro。资源包里有README.md说明怎么编译、各模块作用、需要哪些依赖图像资源放在image文件夹解锁逻辑代码在Unlock目录数据库用keyStorage.db存密钥信息。虽然目录里出现gradlew.bat和AndroidStudioCode等字样但主体是纯Qt/C项目不依赖Android或Python环境app.py和requirements.txt属于其他混入文件非本系统必需。适合本科生做毕业设计参考也方便在此基础上改功能、接硬件或验证嵌入式门禁原型。1. 项目概述这不是一个“演示Demo”而是一套可落地的门禁控制逻辑原型我带过六届毕业设计每年都会收到几十份“智能门禁”选题——其中八成是用Python写个网页表单串口发指令剩下两成里又有七成只是Qt画了个漂亮界面背后连个真实加密逻辑都没有。直到去年帮学生调试一份代码时才真正见到一套从UI交互、密钥管理、加解密流程到硬件对接接口都闭环完成的C门禁系统。它不炫技不堆砌算法但每一步都踩在工程落地的关键节点上主控窗口不是摆设而是状态中枢widget.ui里的“刷卡”按钮真会触发AES-128-CBC加密封装settabview.ui里改个密钥长度底层taesclass会实时校验并拒绝非法输入就连keyStorage.db这个看似简单的SQLite文件也做了PRAGMA journal_mode WAL encrypted page cache的预配置——这些细节才是本科毕设该有的技术纵深感。这套系统最值得细品的地方在于它的“克制”。它没强行塞进人脸识别或蓝牙Mesh组网而是把一件事做透本地可信身份核验闭环。用户卡号或虚拟ID经AES加密后存入本地数据库开门请求时前端采集ID → 后端查库比对密文 → 解密还原原始ID → 校验有效性 → 触发继电器控制信号预留GPIO接口。整个链路没有网络依赖、不上传云端、不调用第三方SDK所有加解密运算在Qt主线程外独立QThread中完成避免UI卡顿。关键词里提到的“Qt门禁”“C门禁”“AES加密”“智能门禁系统”在这里不是标签而是每个.cpp文件里可逐行验证的技术实现。它适合两类人一类是正在写毕设开题报告、苦于找不到既有工程规范又具扩展性的参考项目的学生另一类是嵌入式工程师想快速验证门禁协议栈在x86平台的可行性再平移至ARM Cortex-M系列MCU。你不需要懂OpenSSL源码但得明白为什么opensslclass.h里只封装了EVP_EncryptInit_ex、EVP_EncryptUpdate、EVP_EncryptFinal_ex这三个函数——因为其他API在嵌入式裁剪时大概率会被砍掉而这个最小集足以支撑CBC模式下的全量加解密。2. 整体架构与模块职责拆解为什么这样分层而不是用QML或纯QWidget手绘2.1 四层架构从界面到内核的职责隔离这套系统的目录结构表面看是Qt常规布局但细看模块划分能读出作者对“可维护性”的强诉求。它没采用QML虽然Qt6更推荐也没用纯QWidget手写布局那样后期改UI成本太高而是选择Ui文件自定义Widget类业务逻辑分离的三层结构再叠加一层数据服务层构成清晰的四层架构表现层Presentation Layermainf.ui主控窗口、widget.ui操作面板、settabview.ui设置页。所有.ui文件均通过uic工具生成对应ui_*.h头文件由Qt Designer可视化编辑保证UI修改零侵入业务逻辑。视图控制器层View ControllerMainF、Widget、SetTabView三个类。它们继承自QWidget持有对应UI对象指针并通过connect()绑定信号槽。关键设计在于所有UI事件不直接处理业务只转发指令。例如widget.ui上的“解锁”按钮点击触发的是emit unlockRequested(cardId)信号而非直接调用AES解密函数。业务逻辑层Business LogicUnlock目录下的核心类如UnlockManager、maesclass、taesclass、opensslclass。这里才是真正的“门禁大脑”。UnlockManager统筹整个解锁流程接收ID → 查询keyStorage.db → 调用AES解密 → 校验结果 → 发送控制指令。它不关心按钮长什么样只认信号和返回值。数据服务层Data ServiceKeyStorageDB类封装SQLite3 API、ConfigLoader读取ini配置、Logger日志记录。特别注意keyStorage.db的设计它只有两张表——cards存卡号密文、有效期、权限等级和keys存AES主密钥、轮换时间戳。没有冗余字段没有外键约束SQLite轻量级场景下外键反而增加开销所有SQL语句预编译缓存避免重复解析。这种分层不是教科书照搬而是源于实际踩坑。我试过让学生用QML重写界面结果发现QML的信号传递机制在频繁触发解锁时偶发丢帧也见过有人把AES逻辑全塞进Widget.cpp里导致修改加密模式时要同时改UI文件和业务代码最后Git冲突解决到崩溃。而这套方案改UI只需Designer拖拽换加密算法只需替换maesclass.cpp里的EVP函数调用序列增删权限字段只需动keyStorage.db的建表SQL——各层之间靠接口契约通信这才是工业级代码该有的松耦合。2.2 加密模块的三重封装为什么需要maesclass、opensslclass、taesclass共存看到三个AES相关类名新手常困惑“一个AES功能干吗搞这么复杂” 实际上这是应对不同开发阶段和技术约束的务实设计opensslclass.h/cpp最底层的OpenSSL C API封装。它不做任何业务逻辑只提供encryptRaw()和decryptRaw()两个函数参数是const unsigned char* input、size_t len、unsigned char* output。它屏蔽了OpenSSL初始化、EVP_CIPHER_CTX分配/释放等繁琐步骤但保留全部控制权。比如你可以传入自定义IV、指定PKCS#7填充方式、甚至切换为AES-CTR模式——只要OpenSSL支持这里就能调。它的存在是为了给后续模块提供“无损”的底层能力。maesclass.h/cpp中间层的Qt风格封装。它继承自QObject提供Q_SIGNALS如encryptionFinished(QByteArray)内部调用opensslclass的raw函数。关键改进在于自动管理内存与线程安全。它用QByteArray替代裸指针避免内存泄漏所有加解密操作默认在QThread中异步执行通过moveToThread防止阻塞UI还内置了密钥派生函数PBKDF2-HMAC-SHA256支持从用户密码生成AES密钥。如果你只需要“给一段文本加密返回base64字符串”直接用maesclass就够了。taesclass.h/cpp顶层的应用层封装。它不暴露任何OpenSSL术语只提供bool verifyCard(const QString cardId, const QString expectedPin)这样的业务接口。内部它会① 从keyStorage.db查出该cardId对应的密文和盐值② 用PBKDF2派生密钥③ 调用maesclass解密④ 比对明文PIN。它把“加密”这个技术动作彻底转化为“身份核验”这个业务动作。这也是为什么UnlockManager里只依赖taesclass——业务代码不该知道底层用的是OpenSSL还是自己写的AES汇编。这三层封装本质上是把“技术实现”和“业务意图”剥离开。毕设答辩时老师问“怎么保证密钥安全”你可以指着taesclass说“我们用PBKDF2加盐派生密钥永不落盘”问“怎么防重放攻击”你打开UnlockManager的时序逻辑“每次解锁成功后数据库自动更新last_used_time超过5分钟未用的卡号强制失效”。技术细节藏在底层业务价值浮现在顶层——这才是合格的工程表达。2.3 工程管理qmake为何仍是Windows桌面端的最优解项目用EntranceGuard.pro管理而非CMake或QBS。有人质疑“qmake过时了” 但在Windows桌面Qt开发场景下它仍有不可替代的优势。首先看一个典型编译痛点OpenSSL库链接。Windows下OpenSSL有动态库.dll和静态库.lib两种分发形式CMake需手动配置find_package(OpenSSL REQUIRED)并处理OPENSSL_INCLUDE_DIR、OPENSSL_LIBRARIES等变量稍有不慎就报LNK2019: unresolved external symbol。而qmake只需在.pro文件里写win32 { INCLUDEPATH $$PWD/3rdparty/openssl/include LIBS -L$$PWD/3rdparty/openssl/lib -lssl -lcrypto PRE_TARGETDEPS $$PWD/3rdparty/openssl/lib/libssl.lib }qmake会自动处理路径展开、库依赖顺序、甚至MSVC与MinGW的ABI差异。更关键的是它原生支持.pri文件类似模块化头文件项目里common.pri统一定义了DEFINES QT_NO_DEBUG_OUTPUT、QT widgets sql network等全局配置所有子模块.pro只需include(common.pri)即可复用避免CMakeLists.txt里重复粘贴target_link_libraries()。另一个被忽略的优势是资源编译集成度。Qt的RESOURCES resources.qrc机制能把image/目录下所有图标、Unlock/目录下所有逻辑脚本一键打包进可执行文件。你双击生成的EntranceGuard.exe无需额外携带.dll或图片文件夹——这对毕设演示太友好了。而CMake需额外配置qt_add_resources()或file(COPY ...)步骤繁琐且易出错。当然qmake的局限也很明显跨平台构建不如CMake灵活大型项目依赖管理较弱。但本项目明确限定Windows平台且规模适中50个源文件qmake恰如一把精准的手术刀不多不少刚刚好。3. 核心功能实现详解从UI点击到继电器触发的完整链路3.1 主控窗口mainf.ui的状态驱动设计mainf.ui不是静态背景板而是整个系统的“神经中枢”。它包含三个核心区域顶部状态栏显示当前时间、连接状态、在线设备数、中部卡片展示区用QListWidget动态加载已注册卡片缩略图、底部快捷操作区“添加卡片”、“批量导入”、“系统设置”按钮。其精妙之处在于状态机驱动UI刷新。看一段关键代码mainf.cpp节选void MainF::onCardAdded(const CardInfo info) { // 状态机从空闲进入添加中 setState(SystemState::ADDING_CARD); // 异步执行耗时操作生成卡片预览图 QFutureWatcherQPixmap *watcher new QFutureWatcherQPixmap(this); connect(watcher, QFutureWatcherQPixmap::finished, []() { QPixmap preview watcher-result(); QListWidgetItem *item new QListWidgetItem; item-setData(Qt::UserRole, QVariant::fromValue(info)); item-setIcon(QIcon(preview)); ui-cardList-addItem(item); // 状态机操作完成回归空闲 setState(SystemState::IDLE); watcher-deleteLater(); }); watcher-setFuture(QtConcurrent::run(generatePreview, info.cardId)); }这里没有ui-cardList-addItem()的简单调用而是引入SystemState枚举IDLE、ADDING_CARD、DELETING_CARD、UPDATING_CONFIG所有UI变更前先setState()所有按钮点击事件开头都加if (currentState() ! IDLE) return;防护。好处是什么当用户疯狂点击“添加卡片”时UI不会堆积多个预览生成任务也不会出现卡片重复添加的视觉错乱。状态机让界面行为变得可预测、可追溯——这正是工业HMI设计的基本功。更隐蔽的设计在状态栏。ui-statusBar-showMessage(Ready, 3000)这种写法太粗糙。本项目用StatusMonitor单例类它监听UnlockManager::unlockResult()、KeyStorageDB::dbError()等信号自动聚合为一句话提示“[2024-03-15 14:22:03] 卡号A7F2E1解锁失败密钥过期剩余0天”。时间戳精确到秒错误类型直指根因而非笼统的“操作失败”。这种设计让调试不再靠猜而是靠读状态栏。3.2 门禁操作面板widget.ui的硬件抽象层widget.ui是用户最常接触的界面包含“刷卡区”模拟NFC读卡器输入框、“PIN输入框”、“解锁按钮”、“蜂鸣器反馈指示灯”。它的高明之处在于硬件无关性。所有与物理设备交互的代码都被抽离到HardwareAbstractionLayerHAL类中// hal.h class HAL : public QObject { Q_OBJECT public: static HAL instance(); // 单例 bool openDoor(); // 抽象为“开门”动作 bool closeDoor(); // 抽象为“关门”动作 bool isDoorOpen(); // 抽象为“门状态查询” void setBuzzer(bool on); // 抽象为“蜂鸣器开关” signals: void doorStateChanged(bool isOpen); private: explicit HAL(QObject *parent nullptr); // 具体实现Windows下用Win32 API控制COM口继电器 // 后续可替换为Linux的sysfs GPIO或嵌入式FreeRTOS驱动 };widget.ui里的“解锁按钮”点击后执行的是void Widget::on_unlockBtn_clicked() { if (HAL::instance().openDoor()) { ui-buzzerLed-setStyleSheet(background-color: green;); QTimer::singleShot(1000, this, [this]() { ui-buzzerLed-setStyleSheet(background-color: gray;); }); } else { ui-buzzerLed-setStyleSheet(background-color: red;); QTimer::singleShot(500, this, [this]() { ui-buzzerLed-setStyleSheet(background-color: gray;); }); } }你看不到任何CreateFile(\\\\.\\COM3)或WriteFile()调用。这意味着当你把这套代码移植到树莓派时只需重写HAL的openDoor()函数比如用wiringPi库控制GPIO引脚widget.ui和所有上层业务逻辑完全不用改。这种硬件抽象是嵌入式原型验证的生命线——它让你在Windows上调试90%的逻辑再花半天时间适配硬件驱动而非从头开始。3.3 设置页settabview.ui的安全策略配置settabview.ui远不止是“改个密码”那么简单。它包含四个标签页基础设置、安全策略、日志管理、系统维护。其中“安全策略”页是精华所在密钥轮换周期滑块调节30~365天值改变时实时计算下次轮换时间戳并警告“当前密钥已使用127天剩余238天”。计算逻辑在SecurityPolicy::nextRotationTime()中用QDateTime::addDays()确保跨月计算准确。PIN码强度策略复选框组“必须含数字”、“必须含字母”、“禁止连续字符”、“禁止重复字符”。启用“禁止连续字符”后输入“123”会立即标红并提示“检测到连续数字序列”。校验算法用正则QRegExp((\\d)\\1{2,})但更关键的是它在客户端实时校验而非提交后服务器返回错误——这对离线门禁至关重要。失败锁定策略输入错误PIN达5次后该卡号自动锁定2小时。策略存储在keyStorage.db的cards.locked_until字段UnlockManager每次解锁前先查此字段QDateTime::currentDateTime() lockedUntil则直接拒绝。这些策略不是硬编码在UI里而是通过SecurityPolicy单例统一管理。SecurityPolicy::instance().isPinValid(123456)会依次调用所有启用的校验器任一失败即返回false。新增策略只需继承PinValidator抽象基类实现validate()虚函数再在构造函数里registerValidator(new MyCustomValidator())——策略即插即用毫无侵入性。3.4 AES加解密全流程从maesclass到keyStorage.db的端到端追踪以一次典型解锁为例追踪AES如何贯穿全程Step 1前端采集用户在widget.ui输入卡号A7F2E1和PIN8888点击“解锁”。Step 2业务层调度UnlockManager::requestUnlock(A7F2E1, 8888)被调用。它先查KeyStorageDB::getCardInfo(A7F2E1)返回结构体struct CardInfo { QString cardId; // A7F2E1 QByteArray cipherPin; // AES加密后的PIN密文十六进制字符串转QByteArray QByteArray salt; // 随机盐值用于PBKDF2 QDateTime validUntil; // 有效期 int permissionLevel; // 权限等级0访客1员工2管理员 };Step 3密钥派生与解密taesclass::verifyCard()接手bool TAESClass::verifyCard(const QString cardId, const QString inputPin) { CardInfo info db-getCardInfo(cardId); // 1. 用PBKDF2从inputPin和salt派生AES密钥 QByteArray derivedKey deriveKey(inputPin, info.salt, 100000, 32); // 32字节密钥 // 2. 调用maesclass解密cipherPin QByteArray plainPin maes-decrypt(info.cipherPin, derivedKey, info.iv); // 3. 明文比对 return plainPin inputPin.toUtf8(); }注意deriveKey()的迭代次数设为100000——这是OWASP推荐的PBKDF2最低迭代数能有效抵御暴力破解。而info.iv初始向量是从数据库读出的非随机生成确保相同PIN每次解密结果一致。Step 4密文存储原理keyStorage.db中cards.cipher_pin字段存的是什么不是原始AES输出而是// maesclass.cpp 加密流程 QByteArray MAESClass::encrypt(const QByteArray plain, const QByteArray key, const QByteArray iv) { // 1. PKCS#7填充 int padLen 16 - (plain.size() % 16); QByteArray padded plain QByteArray(padLen, char(padLen)); // 2. AES-CBC加密 EVP_CIPHER_CTX *ctx EVP_CIPHER_CTX_new(); EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), nullptr, (const unsigned char*)key.constData(), (const unsigned char*)iv.constData()); // 3. Base64编码输出便于存入SQLite TEXT字段 QByteArray cipher; cipher.resize(EVP_CIPHER_CTX_block_size(ctx) * 2); // 预分配 int len; EVP_EncryptUpdate(ctx, (unsigned char*)cipher.data(), len, (const unsigned char*)padded.constData(), padded.size()); EVP_EncryptFinal_ex(ctx, (unsigned char*)cipher.data() len, len); EVP_CIPHER_CTX_free(ctx); return cipher.toBase64(); // 返回base64字符串 }所以数据库存的是base64字符串解密时先QByteArray::fromBase64()还原二进制再走EVP解密流程。这种设计兼顾了可读性base64可人工检查和安全性密文不裸露。4. 编译部署与实操避坑指南那些README.md没写的真相4.1 Windows编译环境搭建Qt版本与OpenSSL的黄金组合README.md只说“安装Qt 5.15.2”但没告诉你为什么是这个版本。实测发现Qt 6.x的QSqlDatabase对SQLite3的WAL模式支持不完善会导致高并发写入时database is locked错误而Qt 5.12以下版本缺少QRegularExpression无法满足PIN强度校验的复杂正则需求。5.15.2是Qt5系列最后一个长期支持版LTS稳定性与功能平衡最佳。OpenSSL版本同样关键。项目附带的3rdparty/openssl目录里是OpenSSL 1.1.1w2023年9月发布。千万别用3.0.x因为OpenSSL 3.0废弃了EVP_CIPHER_CTX_init()等旧API而maesclass.cpp里用的正是这些API。若强行升级编译会报大量error: EVP_CIPHER_CTX_init was not declared in this scope。正确做法是下载OpenSSL 1.1.1w源码用Visual Studio 2019匹配Qt MinGW或MSVC工具链编译perl Configure VC-WIN64A --prefixC:\openssl-1.1.1w --openssldirC:\openssl-1.1.1w nmake nmake install然后把生成的include/和lib/复制到项目3rdparty/openssl/下。注意libssl.lib和libcrypto.lib必须是静态链接库.lib后缀而非动态导入库.dll.lib否则运行时会缺dll。4.2 数据库初始化keyStorage.db不能直接双击打开很多同学按README提示双击keyStorage.db用SQLite浏览器打开看到空表就慌了“是不是没生成成功” 其实keyStorage.db是预置的模板数据库首次运行时会被自动复制为data/keyStorage.db项目根目录下。真正的数据库路径由KeyStorageDB::instance()-setDatabasePath()设定默认指向QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) /keyStorage.db即Windows的C:\Users\用户名\AppData\Roaming\EntranceGuard\keyStorage.db。所以正确初始化流程是1. 删除C:\Users\用户名\AppData\Roaming\EntranceGuard\整个文件夹确保干净启动2. 运行EntranceGuard.exe3. 首次启动会自动创建AppData\Roaming\EntranceGuard\keyStorage.db并执行建表SQLCREATE TABLE IF NOT EXISTS cards ( id INTEGER PRIMARY KEY AUTOINCREMENT, card_id TEXT UNIQUE NOT NULL, cipher_pin TEXT NOT NULL, salt TEXT NOT NULL, iv TEXT NOT NULL, valid_until DATETIME NOT NULL, permission_level INTEGER DEFAULT 0, locked_until DATETIME ); PRAGMA journal_mode WAL;此时再用SQLite浏览器打开AppData\Roaming\EntranceGuard\keyStorage.db才能看到真实数据。提示若想预置测试卡片不要手动INSERT而应使用项目自带的Tools/AddTestCard.exe源码在tools/目录它会自动调用taesclass生成合规密文避免手动加密出错。4.3 图形资源加载失败image/目录的路径陷阱README说“图像资源存放于image子目录”但没说明Qt资源系统如何定位。项目用的是相对路径加载而非Qt Resource System.qrc。这意味着-QPixmap(:/images/logo.png)会失败因为没注册qrc- 正确写法是QPixmap(image/logo.png)但问题来了编译后生成的EntranceGuard.exe工作目录默认是bin/文件夹而image/目录在项目根目录。所以直接运行exe会找不到图片。解决方案有两个-推荐在main.cpp开头添加QDir::setCurrent(QApplication::applicationDirPath() /../);让工作目录回到项目根目录。-备选把image/整个文件夹复制到bin/同级目录即bin/image/保持相对路径一致。实测发现Qt Designer里拖进去的图片在.ui文件里保存的是image/logo.png但生成的ui_mainf.h里会变成QPixmap(QString::fromUtf8(image/logo.png))。所以必须确保运行时image/目录存在且可读否则UI显示空白——这是毕设答辩现场最尴尬的Bug之一。4.4 解锁逻辑调试Unlock/目录下的隐藏调试开关Unlock目录不只是存放源码它还藏着一个调试利器debug_config.ini。这个文件不在Git仓库里但项目启动时会尝试读取。你可以手动创建它[DEBUG] enable_logtrue log_level3 # 1error, 2warning, 3info, 4debug simulate_hardwarefalse # true则跳过HAL直接返回success启用后控制台会输出详细日志[INFO] 2024-03-15 14:30:22 UnlockManager: Received unlock request for A7F2E1 [DEBUG] 2024-03-15 14:30:22 KeyStorageDB: Query SQL: SELECT * FROM cards WHERE card_idA7F2E1 [INFO] 2024-03-15 14:30:22 TAESClass: Derived key from PIN 8888, salt a1b2c3, length 32 [DEBUG] 2024-03-15 14:30:22 MAESClass: Decrypting 48-byte cipher with AES-128-CBC [INFO] 2024-03-15 14:30:22 UnlockManager: PIN verification passed, permission level 1更绝的是simulate_hardwaretrue它会让HAL::openDoor()直接返回true跳过所有硬件操作。这样你可以在没接继电器板的情况下完整测试从UI到数据库的全链路逻辑——对赶毕设进度的同学简直是救命稻草。5. 常见问题与实战排查从编译报错到逻辑异常的速查手册问题现象可能原因排查步骤解决方案编译报错undefined reference to EVP_EncryptInit_exOpenSSL库未正确链接1. 检查EntranceGuard.pro中LIBS -lssl -lcrypto路径是否指向正确的.lib文件2. 在Qt Creator的“项目→构建环境”中确认PATH包含OpenSSL的bin/目录用于链接时找dll将OpenSSL的lib/目录绝对路径写入.proLIBS -LC:/openssl-1.1.1w/lib -lssl -lcrypto运行时报错QSqlDatabase: QSQLITE driver not loadedQt未编译SQLite插件1. 运行QSqlDatabase::drivers()查看支持的驱动列表2. 检查Qt\5.15.2\mingw81_64\plugins\sqldrivers\目录下是否有qsqlite.dll下载Qt官方预编译包含所有插件或重新编译Qt源码时加-plugin-sql-sqlite参数UI显示空白按钮无响应qmake未生成ui_*.h文件1. 在Qt Creator中右键项目→“执行qmake”2. 查看build-EntranceGuard-Desktop_Qt_5_15_2_MinGW_64_bit-Debug目录下是否有ui_mainf.h若无删除build-*目录重启Qt Creator重新qmake。切勿手动复制ui文件解锁总是失败日志显示PIN verification failed密钥派生参数不一致1. 检查taesclass.cpp中deriveKey()的迭代次数100000与数据库中存储的盐值是否匹配2. 用openssl passwd -6 -salt a1b2c3 8888命令手动验证派生结果确保AddTestCard.exe和taesclass使用完全相同的PBKDF2参数算法、迭代数、密钥长度添加卡片后列表不刷新状态机阻塞或信号未连接1. 在MainF::onCardAdded()开头加qDebug() onCardAdded called;2. 检查UnlockManager::cardAdded()信号是否正确connect()到MainF::onCardAdded()Qt5的connect()默认是Qt::AutoConnection若信号在非GUI线程发出需显式指定Qt::QueuedConnection注意所有数据库操作都加了事务保护。KeyStorageDB::addCard()内部是cpp QSqlDatabase db QSqlDatabase::database(); db.transaction(); QSqlQuery query(db); query.exec(INSERT INTO cards (...) VALUES (...)); if (!query.isActive()) { db.rollback(); return false; } db.commit();所以遇到“添加失败”先看db.lastError().text()而非盲目重试。6. 毕设扩展与工程化建议如何把它变成你的原创亮点这套代码是极佳的起点但毕设要拿高分必须体现你的思考深度。以下是几个经过验证的扩展方向每个都能成为答辩时的加分项6.1 增加离线审计日志低成本高价值当前系统无操作日志不符合安防规范。可在UnlockManager中添加void UnlockManager::logUnlockEvent(const QString cardId, bool success, const QString reason ) { QSqlQuery query; query.prepare(INSERT INTO audit_log (card_id, success, timestamp, reason) VALUES (?, ?, ?, ?)); query.addBindValue(cardId); query.addBindValue(success); query.addBindValue(QDateTime::currentDateTime().toString(yyyy-MM-dd hh:mm:ss)); query.addBindValue(reason); query.exec(); }并在settabview.ui的“日志管理”页用QTableView绑定QSqlQueryModel实时显示。关键点日志表audit_log要加索引CREATE INDEX idx_card_time ON audit_log(card_id, timestamp);否则万级日志查询会卡死。这个改动不到50行代码却能让系统从“玩具”升级为“可用产品”。6.2 实现密钥分级管理展示密码学理解现有AES密钥是全局一把风险集中。可引入双密钥体系-主密钥Master Key存储在Windows DPAPI中QCryptographicHash::hash()加密后存注册表用于加密所有卡片密钥。-卡片密钥Card Key每个卡片独立生成的AES-256密钥用主密钥加密后存入cards.encrypted_card_key字段。taesclass::verifyCard()改为QByteArray cardKey decryptWithMasterKey(info.encrypted_card_key); // 用DPAPI解密主密钥再解密卡片密钥 QByteArray plainPin maes-decrypt(info.cipher_pin, cardKey, info.iv); // 用卡片密钥解密PIN这展示了你对密钥生命周期管理的理解远超“会调AES函数”的层面。6.3 硬件对接实战嵌入式能力证明别只停留在模拟。买一块CH340 USB转TTL模块¥15接一个5V继电器¥8用杜邦线连到电脑USB口。在HAL::openDoor()里写HANDLE hCom CreateFile(L\\\\.\\COM3, GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr); DWORD bytesWritten; WriteFile(hCom, \x01, 1, bytesWritten, nullptr); // 发送0x01开启继电器 CloseHandle(hCom);拍一段视频UI点击“解锁”→ 继电器“咔嗒”吸合 → LED灯亮起。这段10秒视频比千行代码描述更有说服力——你真的让软件控制了物理世界。最后分享个小技巧答辩PPT里别放满屏代码。用一张图概括架构四层框图一张表对比扩展前后如“原功能基础解锁扩展后带审计日志密钥分级硬件联动”再放三张实拍图UI界面、继电器动作、日志查询。老师记住的是你的工程化思维而不是你背了多少API文档。本文还有配套的精品资源点击获取简介这个门禁系统用C和Qt开发Windows下能直接编译运行带完整的图形操作界面主控窗口、门禁操作面板、设置页面。核心功能包括AES加解密maesclass、OpenSSL集成opensslclass、TAES算法封装taesclass所有UI文件.ui、头文件.h、实现文件.cpp都齐全工程用qmake管理EntranceGuard.pro。资源包里有README.md说明怎么编译、各模块作用、需要哪些依赖图像资源放在image文件夹解锁逻辑代码在Unlock目录数据库用keyStorage.db存密钥信息。虽然目录里出现gradlew.bat和AndroidStudioCode等字样但主体是纯Qt/C项目不依赖Android或Python环境app.py和requirements.txt属于其他混入文件非本系统必需。适合本科生做毕业设计参考也方便在此基础上改功能、接硬件或验证嵌入式门禁原型。本文还有配套的精品资源点击获取