在现代化的后端开发中,良好的项目结构是保证代码可维护性和团队协作效率的关键。对于使用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