尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Qt+MySQL企业级商品库存管理系统开发实战指南

Qt+MySQL企业级商品库存管理系统开发实战指南 如果你正在用 C 开发桌面应用特别是需要连接数据库的企业级软件那么 Qt MySQL 这个组合你一定不陌生。但真正把这两者结合好做出稳定、可维护的商品库存管理系统很多人都会在架构设计、数据库连接、界面交互这些环节踩坑。这篇文章不会只给你一个简单的Hello World示例而是基于一个完整的企业商品库存管理系统拆解 Qt 与 MySQL 实战中的核心问题为什么选择这个技术栈如何设计合理的数据库表结构Qt 的模型/视图架构怎样与数据库高效交互以及最重要的是如何避免那些让项目后期难以维护的典型错误。读完本文你将掌握从环境搭建、数据库设计、Qt 界面开发到数据持久化的完整流程并获得一个可直接用于实际项目的代码框架。1. 为什么 Qt MySQL 适合企业库存管理系统在技术选型时很多开发者会纠结于选择 Qt 自带 SQLite 还是外接 MySQL。SQLite 确实简单易用但对于企业级库存管理MySQL 的优势更加明显并发访问能力库存管理系统通常需要多用户同时操作MySQL 的并发控制机制能有效处理数据竞争问题数据安全性MySQL 提供完善的用户权限管理和数据备份机制网络共享支持远程访问适合分布式办公环境数据容量处理大量商品数据和交易记录时性能更稳定Qt 作为跨平台 GUI 框架其信号槽机制和模型/视图架构与数据库操作天然契合。通过 QtSQL 模块我们可以用统一的方式操作不同数据库大大降低了后期迁移成本。2. 环境准备与工具选择2.1 开发环境配置# 推荐版本组合 Qt: 5.15.2 或 6.2.4LTS版本 MySQL: 8.0.x 编译器: MSVC2019 或 MinGW 8.1.0 IDE: Qt Creator 或 VS Code with Qt插件2.2 项目依赖配置在.pro文件中添加必要的模块# 企业商品库存管理系统.pro QT core gui sql widgets greaterThan(QT_MAJOR_VERSION, 4): QT widgets CONFIG c11 # 启用MySQL驱动 QT sql # 发布配置 CONFIG(release, debug|release) { DESTDIR $$PWD/bin/release OBJECTS_DIR $$PWD/build/release/.obj MOC_DIR $$PWD/build/release/.moc RCC_DIR $$PWD/build/release/.qrc UI_DIR $$PWD/build/release/.ui } CONFIG(debug, debug|release) { DESTDIR $$PWD/bin/debug OBJECTS_DIR $$PWD/build/debug/.obj MOC_DIR $$PWD/build/debug/.moc RCC_DIR $$PWD/build/debug/.qrc UI_DIR $$PWD/build/debug/.ui }2.3 MySQL 驱动检查在代码中检查 MySQL 驱动是否可用// main.cpp 中添加驱动检查 #include QApplication #include QDebug #include QSqlDatabase int main(int argc, char *argv[]) { QApplication a(argc, argv); // 检查MySQL驱动 qDebug() Available drivers:; QStringList drivers QSqlDatabase::drivers(); foreach(QString driver, drivers) { qDebug() driver; } if(!drivers.contains(QMYSQL)) { qDebug() MySQL driver not available!; return -1; } // ... 其余代码 }3. 数据库设计企业库存的核心表结构一个合理的数据库设计是系统稳定性的基础。以下是库存管理系统的核心表结构3.1 商品信息表 (products)CREATE TABLE products ( id INT AUTO_INCREMENT PRIMARY KEY, product_code VARCHAR(50) UNIQUE NOT NULL COMMENT 商品编码, product_name VARCHAR(100) NOT NULL COMMENT 商品名称, category_id INT NOT NULL COMMENT 分类ID, supplier_id INT COMMENT 供应商ID, unit_price DECIMAL(10,2) NOT NULL COMMENT 单价, cost_price DECIMAL(10,2) COMMENT 成本价, stock_quantity INT DEFAULT 0 COMMENT 库存数量, min_stock INT DEFAULT 0 COMMENT 最低库存预警, max_stock INT DEFAULT 1000 COMMENT 最高库存限制, specification TEXT COMMENT 规格说明, created_time DATETIME DEFAULT CURRENT_TIMESTAMP, updated_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_category (category_id), INDEX idx_supplier (supplier_id), INDEX idx_code (product_code) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT商品信息表;3.2 库存流水表 (inventory_records)CREATE TABLE inventory_records ( id INT AUTO_INCREMENT PRIMARY KEY, product_id INT NOT NULL COMMENT 商品ID, record_type ENUM(in, out, adjust) NOT NULL COMMENT 操作类型:入库/出库/调整, quantity INT NOT NULL COMMENT 数量, before_quantity INT NOT NULL COMMENT 操作前库存, after_quantity INT NOT NULL COMMENT 操作后库存, operator_id INT NOT NULL COMMENT 操作员ID, related_order VARCHAR(100) COMMENT 相关订单号, remark TEXT COMMENT 备注, created_time DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_product (product_id), INDEX idx_time (created_time), FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE RESTRICT ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT库存流水表;3.3 分类和供应商表-- 商品分类表 CREATE TABLE categories ( id INT AUTO_INCREMENT PRIMARY KEY, category_name VARCHAR(50) NOT NULL COMMENT 分类名称, parent_id INT DEFAULT 0 COMMENT 父分类ID, sort_order INT DEFAULT 0 COMMENT 排序, created_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT商品分类表; -- 供应商表 CREATE TABLE suppliers ( id INT AUTO_INCREMENT PRIMARY KEY, supplier_name VARCHAR(100) NOT NULL COMMENT 供应商名称, contact_person VARCHAR(50) COMMENT 联系人, contact_phone VARCHAR(20) COMMENT 联系电话, address TEXT COMMENT 地址, created_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT供应商表;4. Qt 数据库连接层封装直接在每个界面中写 SQL 代码会导致维护困难我们需要封装一个统一的数据库操作类。4.1 数据库连接管理类// dbmanager.h #ifndef DBMANAGER_H #define DBMANAGER_H #include QObject #include QSqlDatabase #include QSqlError #include QSqlQuery #include QVariant class DBManager : public QObject { Q_OBJECT public: static DBManager instance(); bool connectToDatabase(const QString host, const QString dbName, const QString user, const QString password); bool isConnected() const; QSqlError lastError() const; // 基础操作 bool executeQuery(const QString query, const QVariantList params QVariantList()); QSqlQuery executeSelect(const QString query, const QVariantList params QVariantList()); // 事务支持 bool beginTransaction(); bool commitTransaction(); bool rollbackTransaction(); private: explicit DBManager(QObject *parent nullptr); QSqlDatabase m_database; }; #endif // DBMANAGER_H4.2 数据库连接实现// dbmanager.cpp #include dbmanager.h DBManager::DBManager(QObject *parent) : QObject(parent) { } DBManager DBManager::instance() { static DBManager instance; return instance; } bool DBManager::connectToDatabase(const QString host, const QString dbName, const QString user, const QString password) { m_database QSqlDatabase::addDatabase(QMYSQL); m_database.setHostName(host); m_database.setDatabaseName(dbName); m_database.setUserName(user); m_database.setPassword(password); m_database.setConnectOptions(MYSQL_OPT_RECONNECT1); if (!m_database.open()) { qDebug() Database connection failed: m_database.lastError(); return false; } qDebug() Database connected successfully; return true; } bool DBManager::executeQuery(const QString query, const QVariantList params) { QSqlQuery sqlQuery; sqlQuery.prepare(query); for (int i 0; i params.size(); i) { sqlQuery.bindValue(i, params.at(i)); } if (!sqlQuery.exec()) { qDebug() Query failed: sqlQuery.lastError(); return false; } return true; } // 事务操作方法 bool DBManager::beginTransaction() { return m_database.transaction(); } bool DBManager::commitTransaction() { return m_database.commit(); } bool DBManager::rollbackTransaction() { return m_database.rollback(); }5. 商品管理模块实现5.1 商品数据模型类使用 Qt 的模型/视图架构可以高效管理表格数据// productmodel.h #ifndef PRODUCTMODEL_H #define PRODUCTMODEL_H #include QAbstractTableModel #include QSqlQueryModel class ProductModel : public QSqlQueryModel { Q_OBJECT public: explicit ProductModel(QObject *parent nullptr); // 重写父类方法 QVariant data(const QModelIndex index, int role Qt::DisplayRole) const override; QVariant headerData(int section, Qt::Orientation orientation, int role) const override; // 自定义方法 void refresh(); bool addProduct(const QVariantMap productData); bool updateProduct(int productId, const QVariantMap productData); bool deleteProduct(int productId); QVariantMap getProductById(int productId) const; private: void initModel(); }; #endif // PRODUCTMODEL_H5.2 商品模型实现// productmodel.cpp #include productmodel.h #include dbmanager.h ProductModel::ProductModel(QObject *parent) : QSqlQueryModel(parent) { initModel(); } void ProductModel::initModel() { QString query SELECT p.id, p.product_code, p.product_name, c.category_name, s.supplier_name, p.unit_price, p.stock_quantity, p.min_stock, p.max_stock FROM products p LEFT JOIN categories c ON p.category_id c.id LEFT JOIN suppliers s ON p.supplier_id s.id ORDER BY p.id DESC; this-setQuery(query); } QVariant ProductModel::data(const QModelIndex index, int role) const { if (!index.isValid()) return QVariant(); if (role Qt::TextAlignmentRole) { // 数字列右对齐 int column index.column(); if (column 5 || column 6 || column 7 || column 8) { // 价格和库存列 return Qt::AlignRight Qt::AlignVCenter; } return Qt::AlignLeft Qt::AlignVCenter; } if (role Qt::DisplayRole || role Qt::EditRole) { int column index.column(); QVariant value QSqlQueryModel::data(index, role); // 价格格式显示 if (column 5 value.isValid()) { return QString(¥%1).arg(value.toDouble(), 0, f, 2); } return value; } return QSqlQueryModel::data(index, role); } QVariant ProductModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation Qt::Horizontal role Qt::DisplayRole) { QStringList headers; headers ID 商品编码 商品名称 分类 供应商 单价 库存 最低库存 最高库存; if (section headers.size()) { return headers.at(section); } } return QSqlQueryModel::headerData(section, orientation, role); } bool ProductModel::addProduct(const QVariantMap productData) { DBManager db DBManager::instance(); QString query INSERT INTO products (product_code, product_name, category_id, supplier_id, unit_price, cost_price, stock_quantity, min_stock, max_stock, specification) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?); QVariantList params; params productData[product_code] productData[product_name] productData[category_id] productData[supplier_id] productData[unit_price] productData[cost_price] productData[stock_quantity] productData[min_stock] productData[max_stock] productData[specification]; if (db.executeQuery(query, params)) { refresh(); return true; } return false; }6. 主界面设计与布局6.1 主窗口类设计// mainwindow.h #ifndef MAINWINDOW_H #define MAINWINDOW_H #include QMainWindow #include QTabWidget #include QTableView #include QStatusBar #include productmodel.h class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent nullptr); ~MainWindow(); private slots: void onProductSelectionChanged(const QModelIndex current, const QModelIndex previous); void onAddProduct(); void onEditProduct(); void onDeleteProduct(); void onInventoryIn(); void onInventoryOut(); private: void setupUI(); void setupConnections(); void setupMenuBar(); void setupToolBar(); void setupStatusBar(); QTabWidget *m_tabWidget; QTableView *m_productTableView; ProductModel *m_productModel; }; #endif // MAINWINDOW_H6.2 主界面实现// mainwindow.cpp #include mainwindow.h #include QMenuBar #include QToolBar #include QAction #include QVBoxLayout #include QHBoxLayout #include QWidget #include QMessageBox #include QHeaderView MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , m_tabWidget(new QTabWidget(this)) , m_productTableView(new QTableView(this)) , m_productModel(new ProductModel(this)) { setupUI(); setupConnections(); setupMenuBar(); setupToolBar(); setupStatusBar(); setWindowTitle(企业商品库存管理系统); setMinimumSize(1024, 768); } void MainWindow::setupUI() { // 设置中心部件 QWidget *centralWidget new QWidget(this); QVBoxLayout *mainLayout new QVBoxLayout(centralWidget); // 商品表格设置 m_productTableView-setModel(m_productModel); m_productTableView-setSelectionMode(QAbstractItemView::SingleSelection); m_productTableView-setSelectionBehavior(QAbstractItemView::SelectRows); m_productTableView-horizontalHeader()-setStretchLastSection(true); m_productTableView-setAlternatingRowColors(true); m_productTableView-setSortingEnabled(true); // 设置列宽 m_productTableView-setColumnWidth(0, 60); // ID m_productTableView-setColumnWidth(1, 120); // 商品编码 m_productTableView-setColumnWidth(2, 200); // 商品名称 m_productTableView-setColumnWidth(3, 100); // 分类 m_productTableView-setColumnWidth(4, 150); // 供应商 m_productTableView-setColumnWidth(5, 80); // 单价 m_productTableView-setColumnWidth(6, 60); // 库存 m_productTableView-setColumnWidth(7, 80); // 最低库存 m_productTableView-setColumnWidth(8, 80); // 最高库存 mainLayout-addWidget(m_productTableView); centralWidget-setLayout(mainLayout); setCentralWidget(centralWidget); } void MainWindow::setupConnections() { connect(m_productTableView-selectionModel(), QItemSelectionModel::currentRowChanged, this, MainWindow::onProductSelectionChanged); } void MainWindow::onProductSelectionChanged(const QModelIndex current, const QModelIndex previous) { Q_UNUSED(previous) if (current.isValid()) { int row current.row(); QModelIndex index m_productModel-index(row, 6); // 库存列 int stock m_productModel-data(index).toString().toInt(); // 库存预警提示 QModelIndex minStockIndex m_productModel-index(row, 7); int minStock m_productModel-data(minStockIndex).toString().toInt(); if (stock minStock) { statusBar()-showMessage(警告该商品库存低于最低库存限制, 3000); } } }7. 库存操作的核心业务逻辑7.1 库存入库操作// inventorymanager.h #ifndef INVENTORYMANAGER_H #define INVENTORYMANAGER_H #include QObject #include QVariantMap class InventoryManager : public QObject { Q_OBJECT public: explicit InventoryManager(QObject *parent nullptr); bool stockIn(int productId, int quantity, int operatorId, const QString orderNo , const QString remark ); bool stockOut(int productId, int quantity, int operatorId, const QString orderNo , const QString remark ); bool stockAdjust(int productId, int newQuantity, int operatorId, const QString remark ); private: bool updateStock(int productId, int quantity, const QString operationType, int operatorId, const QString relatedOrder, const QString remark); int getCurrentStock(int productId); }; #endif // INVENTORYMANAGER_H7.2 库存操作实现// inventorymanager.cpp #include inventorymanager.h #include dbmanager.h #include QSqlQuery #include QSqlError InventoryManager::InventoryManager(QObject *parent) : QObject(parent) { } bool InventoryManager::stockIn(int productId, int quantity, int operatorId, const QString orderNo, const QString remark) { return updateStock(productId, quantity, in, operatorId, orderNo, remark); } bool InventoryManager::stockOut(int productId, int quantity, int operatorId, const QString orderNo, const QString remark) { return updateStock(productId, -quantity, out, operatorId, orderNo, remark); } bool InventoryManager::updateStock(int productId, int quantity, const QString operationType, int operatorId, const QString relatedOrder, const QString remark) { DBManager db DBManager::instance(); if (!db.beginTransaction()) { qDebug() Begin transaction failed; return false; } try { // 1. 获取当前库存 int currentStock getCurrentStock(productId); if (currentStock -1) { db.rollbackTransaction(); return false; } // 2. 计算新库存 int newStock currentStock quantity; if (newStock 0) { qDebug() Insufficient stock; db.rollbackTransaction(); return false; } // 3. 更新商品库存 QSqlQuery updateQuery; updateQuery.prepare(UPDATE products SET stock_quantity ? WHERE id ?); updateQuery.addBindValue(newStock); updateQuery.addBindValue(productId); if (!updateQuery.exec()) { qDebug() Update stock failed: updateQuery.lastError(); db.rollbackTransaction(); return false; } // 4. 记录库存流水 QSqlQuery recordQuery; recordQuery.prepare(INSERT INTO inventory_records (product_id, record_type, quantity, before_quantity, after_quantity, operator_id, related_order, remark) VALUES (?, ?, ?, ?, ?, ?, ?, ?)); recordQuery.addBindValue(productId); recordQuery.addBindValue(operationType); recordQuery.addBindValue(qAbs(quantity)); recordQuery.addBindValue(currentStock); recordQuery.addBindValue(newStock); recordQuery.addBindValue(operatorId); recordQuery.addBindValue(relatedOrder); recordQuery.addBindValue(remark); if (!recordQuery.exec()) { qDebug() Insert record failed: recordQuery.lastError(); db.rollbackTransaction(); return false; } // 5. 提交事务 if (!db.commitTransaction()) { qDebug() Commit transaction failed; db.rollbackTransaction(); return false; } return true; } catch (...) { db.rollbackTransaction(); return false; } }8. 数据验证与异常处理8.1 输入数据验证// inputvalidator.h #ifndef INPUTVALIDATOR_H #define INPUTVALIDATOR_H #include QObject #include QString #include QRegularExpression class InputValidator : public QObject { Q_OBJECT public: explicit InputValidator(QObject *parent nullptr); static bool validateProductCode(const QString code); static bool validateProductName(const QString name); static bool validatePrice(const QString price); static bool validateQuantity(const QString quantity); static bool validatePhoneNumber(const QString phone); static QString formatPrice(double price); static QString getValidationError(); private: static QString m_lastError; }; #endif // INPUTVALIDATOR_H8.2 验证规则实现// inputvalidator.cpp #include inputvalidator.h QString InputValidator::m_lastError ; InputValidator::InputValidator(QObject *parent) : QObject(parent) { } bool InputValidator::validateProductCode(const QString code) { if (code.isEmpty()) { m_lastError 商品编码不能为空; return false; } if (code.length() 50) { m_lastError 商品编码长度不能超过50个字符; return false; } QRegularExpression regex(^[A-Za-z0-9_-]$); if (!regex.match(code).hasMatch()) { m_lastError 商品编码只能包含字母、数字、下划线和减号; return false; } m_lastError ; return true; } bool InputValidator::validatePrice(const QString price) { if (price.isEmpty()) { m_lastError 价格不能为空; return false; } bool ok; double value price.toDouble(ok); if (!ok || value 0) { m_lastError 价格必须是有效的非负数; return false; } if (value 9999999.99) { m_lastError 价格数值过大; return false; } m_lastError ; return true; }9. 常见问题与解决方案9.1 MySQL 连接问题排查问题现象可能原因解决方案QMYSQL driver not loadedMySQL驱动未安装安装对应版本的MySQL Connector/CCant connect to MySQL server服务器地址、端口错误检查host、port配置确认MySQL服务运行Access denied for user用户名密码错误检查数据库用户权限Unknown database数据库不存在先创建数据库或检查数据库名拼写9.2 Qt 数据库操作常见错误问题1查询结果不更新// 错误做法直接setQuery不会刷新视图 model.setQuery(SELECT * FROM products); // 正确做法先清空再查询 model.clear(); model.setQuery(SELECT * FROM products);问题2内存泄漏// 错误做法每次创建新query void someFunction() { QSqlQuery query; query.exec(SELECT ...); } // 正确做法复用query或使用RAII class QueryExecutor { public: QueryExecutor() : m_query(DBManager::instance().database()) {} private: QSqlQuery m_query; };9.3 性能优化建议数据库连接池频繁创建连接影响性能建议使用连接池索引优化为经常查询的字段添加索引分批加载大量数据时使用分页查询缓存机制静态数据适当缓存减少数据库访问10. 项目部署与发布10.1 打包依赖文件创建部署脚本deploy.batWindowsecho off echo 正在打包企业商品库存管理系统... :: 创建发布目录 if not exist dist mkdir dist :: 复制可执行文件 copy bin\release\InventoryManagementSystem.exe dist\ :: 使用windeployqt收集依赖 windeployqt dist\InventoryManagementSystem.exe :: 复制MySQL驱动 copy %QTDIR%\plugins\sqldrivers\qsqlmysql.dll dist\sqldrivers\ echo 打包完成请检查dist目录 pause10.2 数据库初始化脚本创建init_database.sql-- 初始化企业库存管理系统数据库 CREATE DATABASE IF NOT EXISTS inventory_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE inventory_db; -- 创建表结构省略见前面章节 -- ... -- 插入初始数据 INSERT INTO categories (category_name, parent_id, sort_order) VALUES (电子产品, 0, 1), (办公用品, 0, 2), (家用电器, 0, 3), (手机, 1, 1), (电脑, 1, 2); INSERT INTO suppliers (supplier_name, contact_person, contact_phone) VALUES (某某科技有限公司, 张经理, 13800138000), (办公用品供应商, 李主任, 13900139000); -- 创建系统用户 CREATE USER inventory_user% IDENTIFIED BY secure_password; GRANT SELECT, INSERT, UPDATE, DELETE ON inventory_db.* TO inventory_user%; FLUSH PRIVILEGES;11. 扩展功能建议在实际项目中你还可以考虑添加以下功能来完善系统用户权限管理基于角色的访问控制RBAC数据备份恢复定期自动备份数据库报表统计销售统计、库存周转率分析条码打印支持商品条码生成和打印数据导入导出Excel导入导出功能网络版支持多客户端同时访问这个基于 C Qt 和 MySQL 的企业商品库存管理系统提供了一个完整的开发框架涵盖了从数据库设计到界面实现的各个环节。重点在于理解 Qt 的模型/视图架构如何与数据库交互以及如何设计合理的业务逻辑来处理库存操作。在实际开发中建议先跑通基础功能再根据具体业务需求逐步扩展。记得定期备份数据库特别是在进行大规模数据操作时。
返回列表