最近接到一个特殊需求,需要开发一套骨灰撒散预约管理系统。作为一款需要长期稳定运行的民生服务系统,我选择了Qt C++作为开发框架,搭配MySQL数据库。这套系统需要满足以下几个核心需求:
选择Qt框架主要基于以下考虑:首先,Qt的跨平台特性可以让系统轻松部署在不同环境;其次,Qt Quick提供的现代化UI开发方式能够快速构建美观的界面;最后,Qt对数据库的良好支持简化了后端开发工作。
系统采用经典的三层架构设计:
code复制[表示层] Qt Quick界面(QML)
↑↓
[业务逻辑层] C++核心代码
↑↓
[数据访问层] MySQL数据库
这种分层设计使得各模块职责明确,便于后期维护和扩展。表示层负责用户交互,业务逻辑层处理核心业务流程,数据访问层负责数据持久化。
Qt版本选择:建议使用Qt 5.15 LTS或Qt 6.x版本。LTS版本提供长期支持,稳定性更有保障。考虑到部分企业环境可能还未升级,本教程以Qt 5.15为例。
数据库选择:MySQL 8.0相比5.7版本在性能和安全性上都有显著提升,特别是对JSON格式的支持更好,为未来可能的扩展需求预留空间。
注意:安装路径不要包含中文或空格,避免后续编译问题。
sql复制CREATE USER 'ashes_admin'@'localhost' IDENTIFIED BY 'StrongPassword123!';
GRANT ALL PRIVILEGES ON ashes_scatter.* TO 'ashes_admin'@'localhost';
在Qt Creator中新建项目时:
qmake复制QT += quick sql
sql复制CREATE DATABASE IF NOT EXISTS ashes_scatter
CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE ashes_scatter;
CREATE TABLE reservation (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
phone VARCHAR(20) NOT NULL,
id_card VARCHAR(18) NOT NULL,
deceased_name VARCHAR(50) NOT NULL,
scatter_date DATE NOT NULL,
scatter_time TIME NOT NULL,
location VARCHAR(100) NOT NULL,
remarks TEXT,
status TINYINT DEFAULT 0 COMMENT '0-待确认 1-已确认 2-已取消',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY (phone, scatter_date, scatter_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
创建DatabaseManager类处理数据库连接:
cpp复制// DatabaseManager.h
#include <QSqlDatabase>
class DatabaseManager {
public:
static DatabaseManager& instance();
bool connect();
QSqlDatabase database() const;
private:
DatabaseManager();
QSqlDatabase db;
};
cpp复制// DatabaseManager.cpp
#include "DatabaseManager.h"
DatabaseManager& DatabaseManager::instance() {
static DatabaseManager singleton;
return singleton;
}
bool DatabaseManager::connect() {
db = QSqlDatabase::addDatabase("QMYSQL");
db.setHostName("localhost");
db.setDatabaseName("ashes_scatter");
db.setUserName("ashes_admin");
db.setPassword("StrongPassword123!");
if (!db.open()) {
qWarning() << "数据库连接失败:" << db.lastError().text();
return false;
}
return true;
}
使用QML创建预约界面:
qml复制// Main.qml
import QtQuick 2.15
import QtQuick.Controls 2.15
ApplicationWindow {
visible: true
width: 800
height: 600
title: "骨灰撒散预约系统"
ScrollView {
anchors.fill: parent
padding: 20
Column {
spacing: 15
width: parent.width
Label {
text: "骨灰撒散预约"
font.pixelSize: 24
anchors.horizontalCenter: parent.horizontalCenter
}
TextField {
id: nameField
placeholderText: "申请人姓名"
width: parent.width
}
TextField {
id: phoneField
placeholderText: "联系电话"
inputMethodHints: Qt.ImhDialableCharactersOnly
validator: RegExpValidator { regExp: /^[0-9]{11}$/ }
width: parent.width
}
// 其他表单字段...
Button {
text: "提交预约"
width: parent.width
onClicked: reservationController.submitReservation()
}
}
}
}
在C++端实现数据验证:
cpp复制bool ReservationController::validateInput(const QVariantMap &data) {
if (data["name"].toString().trimmed().isEmpty()) {
emit errorOccurred("请输入申请人姓名");
return false;
}
QRegularExpression phoneRegex("^1[3-9]\\d{9}$");
if (!phoneRegex.match(data["phone"].toString()).hasMatch()) {
emit errorOccurred("请输入正确的手机号码");
return false;
}
// 其他验证逻辑...
return true;
}
cpp复制bool ReservationController::submitReservation() {
QVariantMap reservationData = {
{"name", m_name},
{"phone", m_phone},
// 其他字段...
};
if (!validateInput(reservationData)) {
return false;
}
QSqlQuery query;
query.prepare("INSERT INTO reservation (name, phone, id_card, deceased_name, "
"scatter_date, scatter_time, location, remarks) "
"VALUES (:name, :phone, :id_card, :deceased_name, "
":scatter_date, :scatter_time, :location, :remarks)");
query.bindValue(":name", reservationData["name"]);
query.bindValue(":phone", reservationData["phone"]);
// 绑定其他值...
if (!query.exec()) {
qWarning() << "添加预约失败:" << query.lastError().text();
emit errorOccurred("预约提交失败,请稍后再试");
return false;
}
emit reservationSubmitted();
return true;
}
cpp复制QList<QVariantMap> ReservationController::getReservations(const QDate &date) {
QList<QVariantMap> result;
QSqlQuery query;
query.prepare("SELECT * FROM reservation WHERE scatter_date = :date ORDER BY scatter_time");
query.bindValue(":date", date);
if (!query.exec()) {
qWarning() << "查询预约失败:" << query.lastError().text();
return result;
}
while (query.next()) {
QVariantMap item;
item["id"] = query.value("id");
item["name"] = query.value("name");
// 其他字段...
result.append(item);
}
return result;
}
问题现象:连接MySQL时报错"QMYSQL driver not loaded"
解决方案:
qmake复制LIBS += -L"path/to/mysql/lib" -lmysql
问题:QML的DatePicker与MySQL日期格式不匹配
解决方法:在C++层进行格式转换
cpp复制QString formatDateForMySQL(const QDate &date) {
return date.toString("yyyy-MM-dd");
}
QDate parseDateFromMySQL(const QString &dateStr) {
return QDate::fromString(dateStr, "yyyy-MM-dd");
}
问题:多人同时预约同一时间段
解决方案:使用数据库事务和乐观锁
cpp复制bool reserveTimeSlot(int reservationId) {
QSqlDatabase::database().transaction();
QSqlQuery query;
query.prepare("SELECT status FROM reservation WHERE id = :id FOR UPDATE");
query.bindValue(":id", reservationId);
if (!query.exec() || !query.next()) {
QSqlDatabase::database().rollback();
return false;
}
int status = query.value(0).toInt();
if (status != 0) {
QSqlDatabase::database().rollback();
return false;
}
query.prepare("UPDATE reservation SET status = 1 WHERE id = :id");
query.bindValue(":id", reservationId);
if (!query.exec()) {
QSqlDatabase::database().rollback();
return false;
}
QSqlDatabase::database().commit();
return true;
}
bash复制mysqldump -u ashes_admin -p ashes_scatter > backup_$(date +%Y%m%d).sql
sql复制CREATE INDEX idx_scatter_datetime ON reservation(scatter_date, scatter_time);
cpp复制void sendReminder() {
QDate tomorrow = QDate::currentDate().addDays(1);
auto reservations = getReservations(tomorrow);
for (const auto &res : reservations) {
QString phone = res["phone"].toString();
QString msg = QString("尊敬的%1,您预约的骨灰撒散服务将于明天%2进行,请准时到场。")
.arg(res["name"].toString())
.arg(res["scatter_time"].toString());
sendSMS(phone, msg); // 实现短信发送接口
}
}
使用Qt Charts实现数据可视化:
qml复制import QtCharts 2.15
ChartView {
title: "预约量统计"
anchors.fill: parent
antialiasing: true
BarSeries {
axisX: BarCategoryAxis { categories: ["1月", "2月", ...] }
BarSet { label: "2023"; values: [25, 32, ...] }
}
}
在实际开发过程中,有几个关键点值得特别注意:
时间处理:骨灰撒散预约对时间准确性要求很高,必须确保系统时间与数据库时间一致。建议在服务器端统一获取时间,而不是依赖客户端时间。
数据验证:除了前端验证,后端必须做完整的二次验证。特别是对于身份证号、手机号等敏感信息,要使用正则表达式严格校验格式。
异常处理:数据库操作必须做好异常捕获和日志记录。我们在生产环境就遇到过因为网络波动导致的连接超时问题,良好的错误处理机制可以快速定位问题。
性能考量:预约高峰期系统负载会显著增加,建议对数据库查询做优化,必要时可以考虑添加缓存层。
一个实用的技巧是:在开发阶段就建立完整的日志系统,记录所有关键操作和异常情况。这不仅能帮助调试,也为后续的运维工作提供了宝贵的数据支持。