在现代化的后端开发中,良好的项目结构是保证代码可维护性和团队协作效率的关键。对于使用Qt框架的服务端应用而言,如何优雅地组织多模块项目结构,实现模块间的清晰依赖和高效构建,是每个开发者都需要面对的挑战。本文将深入探讨如何利用CMake构建系统,实现Qt项目的模块化融合,打造清晰、可扩展的后端架构。
模块化架构的价值与CMake的优势
随着业务复杂度的增加,单一庞大的代码库会迅速变得难以维护。模块化架构通过将功能分解为独立的单元,带来了诸多好处:
- 代码复用性提升:通用模块可以在不同项目中共享
- 编译效率优化:仅重新编译变更的模块,减少构建时间
- 团队协作简化:不同团队可以并行开发独立模块
- 依赖管理清晰:明确的模块边界减少隐式依赖
CMake作为跨平台的构建系统生成器,为Qt项目提供了强大的模块管理能力。相比传统的qmake,CMake在大型项目管理、外部依赖集成和构建配置方面表现更加出色,特别适合构建复杂的微服务架构。

单个模块的简单结构示意图

多模块融合后的复杂项目结构
构建全局模块:实现跨模块共享功能
全局模块通常包含项目中多个其他模块都需要使用的通用功能,如配置管理、日志系统、工具函数等。创建全局模块是构建模块化架构的第一步。
首先,在项目根目录下创建文件夹,这个命名清晰地表明了模块的用途。common

全局模块目录结构
在文件夹中,我们需要创建标准的头文件和源文件。以common为例,这是一个简单的工具类实现:global_variant.h 、global_variant.cpp
头文件定义:
#ifndef GLOBAL_VARIANT_H
#define GLOBAL_VARIANT_H
#include <QString>
extern QString g_testGlobalVariant;
//数据库服务器ip
extern QString g_databaseServerIp;
//数据库密码
extern QString g_databaseServerPassword;
//数据库端口
extern int g_databaseServerPort;
#endif // GLOBAL_VARIANT_H
源文件实现:
#include "global_variant.h"
// 定义全局变量
QString g_testGlobalVariant = "test";
//数据库服务器ip
QString g_databaseServerIp = "127.0.0.1";
//数据库密码
QString g_databaseServerPassword = "admin";
//数据库端口
int g_databaseServerPort = 3306;
接下来,创建模块的CMake配置文件。在文件中,我们定义模块的构建规则:CMakeLists.txt
# 设置cmake的最低版本
cmake_minimum_required(VERSION 3.16)
# 定义项目名称、项目版本、变成语言
project(Common VERSION 0.1 LANGUAGES CXX)
# 自动处理信号与槽
# set(CMAKE_AUTOMOC ON)
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets)
set(PROJECT_NAME "Common")
# 设置源文件
set(SOURCES
global_variant.cpp
)
# 设置头文件
set(HEADERS
global_variant.h
)
# 创建库
add_library(${PROJECT_NAME} STATIC
${SOURCES}
${HEADERS}
)
# 把库链接到目标执行程序上
target_link_libraries(${PROJECT_NAME} PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)
# 设置库的包含目录
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
关键点:使用add_library创建静态库,通过target_link_libraries指定Qt依赖,最后用target_include_directories设置头文件包含路径。
在根目录的中,我们需要添加对全局模块的引用:CMakeLists.txt
# 将子项目作为独立模块管理
add_subdirectory(Common)
编译成功后,项目目录将显示新增的全局模块:

包含全局模块的项目结构
实现模块间依赖:数据库模块的集成
在实际的后端开发中,数据库访问是核心功能之一。我们将创建一个独立的数据库模块,展示如何实现模块间的依赖关系。
首先创建文件夹,并在其中添加数据库相关的头文件dbm:create_database_connection.h
#ifndef CREATE_DATABASE_CONNECTION_H
#define CREATE_DATABASE_CONNECTION_H
#include<QCoreApplication>
#include<QFile>
#include<QSysInfo>
#include<QtGlobal>
#include<QDebug>
#include<QMessageBox>
#include<QObject>
#include <QSqlError>
#include <QSqlQuery>
#include "global_variant.h"
static bool CreateDatabaseConnection(){
g_databaseServerIp = "192.168.1.100";
g_databaseServerPassword = "admin";
g_databaseServerPort = 3308;
//设置数据库驱动
QSqlDatabase mysqlDB = QSqlDatabase::addDatabase("QMYSQL", "mysql_info_connection");
mysqlDB.setHostName(g_databaseServerIp);
mysqlDB.setUserName("root");
mysqlDB.setPassword(g_databaseServerPassword);
mysqlDB.setPort(g_databaseServerPort);
mysqlDB.setDatabaseName("test_db");
//trainTotalInfoDbName
//根据系统环境设计数据库路径
// Q_OS_LINUX:Q_OS_WIN: Q_OS_MAC Q_OS_WIN32
//如果远程mysql数据库没有打开
if(!mysqlDB.open()){
QSqlError error = mysqlDB.lastError();
//error.text()
// qDebug << "连接失败原因:" << error.text; // 打印完整错误信息
// qDebug << "数据库报错:" << error.databaseText; // 数据库返回的原始错误
// qDebug << "驱动报错:" << error.driverText; // 驱动层错误描述
// qDebug << "错误代码:" << error.nativeErrorCode; // 数据库原生错误码(如MySQL的1045)
//("不能建立总表数据库连接!")
// QMessageBox* databaseInformationBox = new QMessageBox(QMessageBox::Critical, ("信息提示"), ("不能建立总表数据库连接!"), QMessageBox::Yes);
// auto button = databaseInformationBox->exec();
// if(button == QMessageBox::Yes){
// databaseInformationBox->deleteLater();
// }
return false;
}else{
#ifdef Q_OS_WIN
mysqlDB.exec("SET NAMES 'GBK'");
#endif
#ifdef Q_OS_MAC
#endif
}
//QSqlDatabase sqliteDB = QSqlDatabase::addDatabase("QSQLITE", "sqlite_connection1");
// #ifdef Q_OS_WIN //Q_OS_WIN32
// qDebug()<<"QCoreApplication::applicationDirPath():"<<QCoreApplication::applicationDirPath();
// sqliteDB.setDatabaseName(QCoreApplication::applicationDirPath() + QString("/database/LocalSystemDatabse.db"));
// #endif
//如果本地sqlite数据库没有打开
// if(!sqliteDB.open()){
// QMessageBox* databaseInformationBox = new QMessageBox(QMessageBox::Critical, ("信息提示"), ("不能建立本地数据库连接!"), QMessageBox::Yes);
// auto button = databaseInformationBox->exec();
// if(button == QMessageBox::Yes){
// databaseInformationBox->deleteLater();
// }
// return false;
// }
return true;
}
#endif // CREATE_DATABASE_CONNECTION_H
这个数据库管理器类封装了常见的数据库操作,提供了统一的API接口供其他模块调用。
数据库模块的CMake配置需要特别注意依赖管理。在中:CMakeLists.txt
# 设置cmake的最低版本
cmake_minimum_required(VERSION 3.16)
# 定义项目名称、项目版本、变成语言
project(Dbm VERSION 0.1 LANGUAGES CXX)
# 自动处理界面文件
set(CMAKE_AUTOUIC ON)
# 自动处理信号与槽
set(CMAKE_AUTOMOC ON)
# 自动处理资源文件
set(CMAKE_AUTORCC ON)
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core Sql Widgets)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core Sql Widgets)
set(PROJECT_NAME "Dbm")
# 设置源文件
set(SOURCES
)
# 设置头文件
set(HEADERS
create_database_connection.h
)
# 设置UI文件
set(FORMS
)
# 创建库
add_library(${PROJECT_NAME} STATIC
${SOURCES}
${HEADERS}
${FORMS}
)
# set (FORM_INCLUDE_DIRS "dbm/")
# 链接Qt库
target_link_libraries(${PROJECT_NAME} PRIVATE
Qt${QT_VERSION_MAJOR}::Sql
Qt${QT_VERSION_MAJOR}::Widgets
Qt${QT_VERSION_MAJOR}::Core
)
target_link_libraries(${PROJECT_NAME} PRIVATE Common)
# 设置库的包含目录
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_SOURCE_DIR}/common)
⚠️ 注意事项:数据库模块依赖于SQL模块,必须通过find_package正确配置。
在根目录的CMakeLists中,我们需要同时添加数据库模块和必要的Qt SQL依赖。由于模块使用了数据库功能,根配置中也需要相应调整:Dbm
# 寻找Qt版本,并精确加载具体的 Qt 版本配置。
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets Sql)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Sql)
# 将子项目作为独立模块管理
add_subdirectory(Dbm)
# 把库链接到目标执行程序上(链接阶段使用,告诉链接器需要链接哪些库文件)
target_link_libraries(${PROJECT_NAME} PRIVATE
FORM
Dbm
)
编译后的项目结构将包含完整的模块层次:

完整的多模块项目结构
现在,我们可以在主模块中使用数据库功能。在main函数中引入数据库模块:
#include "create_database_connection.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
while(!CreateDatabaseConnection()){
// QMessageBox* infoBox = new QMessageBox(QMessageBox::Critical, ("信息提示"), ("不能建立总表数据库连接!"));
// QTimer::singleShot(2000, infoBox, &QMessageBox::accept);
// infoBox->show();
// 创建提问框
QMessageBox msgBox;
msgBox.setWindowTitle("确认退出");
msgBox.setText("不能建立数据库连接,是否要关闭软件吗?");
msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
msgBox.setDefaultButton(QMessageBox::Cancel);
msgBox.setButtonText(QMessageBox::Ok, "关闭");
msgBox.setButtonText(QMessageBox::Cancel, "取消");
// 设置3秒后自动关闭
QTimer::singleShot(5000, &msgBox, &QMessageBox::close);
// 显示模态对话框并获取结果
int result = msgBox.exec();
// 处理用户选择
if (result == QMessageBox::Ok) {
// 点击确定,关闭软件
return 0;
} else {
// 点击其他按钮,不执行关闭操作
// 这里可以添加其他处理逻辑
continue; // 继续运行程序
}
}
MainWindow w;
w.show();
return a.exec();
}
[AFFILIATE_SLOT_1]
资源管理与应用配置
在完整的后端应用中,除了代码模块,资源文件和应用配置也是重要组成部分。Qt提供了完善的资源管理系统,可以与CMake良好集成。
添加图片资源
对于需要嵌入到应用中的图片资源,我们首先在文件中创建资源定义文件form:Image.qrc
<RCC>
<qresource prefix="/">
<file>Image/docusaurus.png</file>
</qresource>
</RCC>
在根目录的CMake配置中,添加资源文件的构建规则:
set(PROJECT_SOURCES
main.cpp
form/Image.qrc
${APP_RC_FILE}
)
在模块的Form中,我们可以使用这些资源:mainwindow.cpp
void MainWindow::initializeStyleSheet()
{
QPixmap icoPix;
// 根据 Image.qrc 中的 prefix="/",资源路径为 :/Image/docusaurus.png
icoPix.load(":/Image/docusaurus.png");
// 检查图片是否加载成功
if (icoPix.isNull()) {
qDebug() << "Failed to load image: :/Image/docusaurus.png";
return;
}
// 正确使用scaled方法的返回值
QPixmap scaledPix = icoPix.scaled(ui->label->size(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
ui->label->setScaledContents(true);
ui->label->setPixmap(scaledPix);
}
注意(为什么不在子模块的中引入 ? ):
因为Qt 资源系统有一个特性: 静态库中的资源不会被自动链接到最终的可执行文件中 。即使 FORM 静态库正确编译了 Image.qrc ,当链接到主程序时,由于资源没有直接被引用,链接器可能会优化掉这些资源。
故需要在 CMakeLists.txt 主项目中添加了 ,这样资源会被编译到主可执行文件中,确保资源在运行时可访问。
设置应用程序图标
专业的应用需要有自己的图标。在Windows平台上,我们可以通过CMake配置自动生成图标文件。在根目录的中添加:CMakeLists
if(WIN32)
# 检查图标文件是否存在
if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/common/appicon.ico")
# 将.rc文件添加到可执行文件
set(APP_RC_FILE "${CMAKE_CURRENT_SOURCE_DIR}/common/appicon.rc")
message(WARNING "Found icon file: appicon.ico")
else()
message(WARNING "Icon file not found: appicon.ico")
endif()
else()
message(STATUS ${CMAKE_CURRENT_SOURCE_DIR}/common/appicon.ico)
endif()
set(PROJECT_SOURCES
main.cpp
${APP_RC_FILE}
)
运行程序后,应用程序将显示自定义图标:

应用图标效果展示
高级扩展与最佳实践
掌握了基本的模块化技巧后,我们可以进一步优化项目架构。以下是一些高级扩展建议:
| 名称 | 链接 |
|---|---|
| 模块化管理代码 | 链接 |
| 模块化管理代码 | 链接 |
| 模块化管理代码 | 链接 |
| 多模块融合 | 链接 |
微服务架构集成:对于大型后端系统,可以考虑将每个模块部署为独立的微服务。Qt的网络模块和序列化功能可以很好地支持微服务间的通信。
持续集成配置:在CMake基础上配置CI/CD流水线,实现自动化测试和部署。可以考虑使用GitHub Actions或GitLab CI等工具。
[AFFILIATE_SLOT_2]
总结
通过CMake实现Qt项目的模块化组织,不仅提升了代码的可维护性和复用性,也为项目的长期演进奠定了坚实基础。本文详细介绍了从基础模块创建到复杂依赖管理的完整流程,包括:
- 全局模块的设计与实现
- 数据库模块的集成与依赖配置
- 资源文件的统一管理
- 应用图标等配置项的设置
在实际的后端开发中,良好的架构设计往往比编码技巧更为重要。模块化思维不仅适用于Qt项目,对于任何复杂的服务端系统都具有指导意义。建议读者在实践中不断优化模块划分,找到最适合自己项目的架构平衡点。
完整的项目代码示例可以在附录中找到,建议下载后实际运行和修改,以加深理解。
CmakeLists.txtImage.qrcform/Image.qrcpri.cmakeCMakeLists.txtCMakeLists.txt
浙公网安备 33010602011771号