公司动态

QT C++ 短信验证码发送模块:基于MVC模式的可复用业务逻辑模型设计与实现

📅 2026/7/17 5:33:18
QT C++ 短信验证码发送模块:基于MVC模式的可复用业务逻辑模型设计与实现
1. 项目概述与核心价值最近在做一个需要用户注册登录的桌面客户端项目用QT框架和C开发。项目做到一半产品经理跑过来说“咱们得加个手机短信验证码登录现在都流行这个安全。” 我当时心里就咯噔一下心想这玩意儿后端接口好说前端弹个框输个手机号也简单但验证码的发送逻辑、倒计时管理、防重复点击、错误处理这些“脏活累活”要是从头手写又得是一堆重复且容易出错的代码。更别提还得考虑不同平台Windows、macOS、Linux下网络请求的兼容性、UI线程的阻塞问题。就在我琢磨着怎么优雅地“造轮子”时突然想到为什么不用开源项目呢一个设计良好的、专注于“发送验证码”这个单一职责的模块Mode不仅能直接拿来用还能作为项目的基础设施为后续所有需要验证码的场景注册、登录、修改密码、支付确认提供服务。这就是我动手封装这个“QT平台C语言编写的手机发送验证码Mode基础代码”的初衷。它不是一个完整的、带UI的发送验证码对话框而是一个纯粹的、可复用的业务逻辑模型Model。你可以把它理解为一个“验证码发送引擎”只管三件事向指定手机号请求验证码、管理倒计时状态、处理发送结果。UI层View只需要调用它的接口并监听它的状态信号来更新界面即可。这个Mode的价值在于它将验证码发送这个高频且琐碎的交互逻辑从具体的UI界面中彻底解耦出来。无论你的登录框是QWidget还是QML写的无论你用的是HTTP还是自定义的TCP协议与后端通信这个Mode的核心状态机空闲、发送中、倒计时、发送失败和业务规则都是稳定不变的。直接使用成熟的开源网络库如QNetworkAccessManager作为基础再结合QT强大的信号槽机制进行封装你就能得到一个健壮、可测试、易集成的核心组件。这不仅能极大提升当前项目的开发效率更能作为一项技术资产沉淀到你的代码工具箱里未来在任何QT桌面或嵌入式项目中需要手机验证码功能时都能快速复用。2. 核心设计思路与架构拆解2.1 为什么选择“Mode”而非“Widget”在QT生态里大家可能更熟悉各种按钮、输入框这样的Widget。但针对“发送验证码”这个业务Widget并不是最佳载体。Widget的核心是“显示”和“交互”它强依赖于具体的界面布局和样式。如果我们把发送、倒计时、重试的逻辑全部写在一个SendSmsButton自定义按钮里那么这个按钮就会变得异常臃肿难以测试你需要模拟点击、模拟网络响应更无法复用到其他样式的按钮或非按钮的触发场景中比如按回车键触发发送。因此我采用了经典的MVCModel-View-Controller或其变种如MVVM设计思想。这里的Mode模型就是SmsVerificationCodeModel类。它不关心自己是被一个按钮点击触发还是被一个菜单项触发。它的职责非常清晰状态管理维护一个内部状态枚举Idle,Sending,Countdown,Failed并对外发射状态变化的信号。业务逻辑暴露requestVerificationCode(const QString phoneNumber)方法。当外部调用此方法时模型负责校验手机号格式、发起网络请求、启动倒计时计时器。数据封装它可能包含一些配置数据如倒计时总时长默认60秒、后端API地址、应用密钥等。而View视图可能是一个QPushButton也可能是一个QML中的Text元素。它只做两件事一是调用Model的请求方法二是监听Model的状态信号并相应地更新自己的文本“发送验证码”、“发送中...”、“59秒后重试”、“发送失败点击重试”、启用/禁用状态。这样UI和逻辑就完全分离开了。修改UI样式不影响发送逻辑调整发送逻辑比如更换API也不影响UI。2.2 关键组件与依赖分析要构建这样一个Mode我们需要依赖QT核心模块中的几个关键类QNetworkAccessManager (QNAM)这是QT进行HTTP/HTTPS网络请求的核心类。我们的Mode内部将持有一个QNAM实例或共享应用全局的实例用于向后端服务器发送POST请求。选择QNAM是因为它跨平台、支持SSL、异步非阻塞完美契合桌面应用的需求。QTimer用于实现精确的倒计时功能。当验证码发送成功后启动一个单次触发的定时器singleShot在倒计时结束后将模型状态从Countdown切换回Idle。同时为了在倒计时期间每秒更新剩余时间并通知UI还需要一个每秒触发的定时器。QSignalMapper 或 Lambda (C11及以上)用于优雅地处理网络请求的异步回调。QNetworkReply会在请求完成时发射finished()信号。我们可以使用Lambda表达式将回复处理逻辑直接绑定到该信号上使代码更紧凑。QJsonDocument / QJsonObject用于解析服务器返回的JSON格式响应。后端API通常会返回一个如{“code”: 0, “msg”: “success”, “data”: {}}的结构我们需要解析code字段来判断成功与否。整个Mode的架构是事件驱动和状态驱动的。外部调用requestVerificationCode是一个命令模型内部据此改变状态、发起网络请求。网络请求的完成是一个事件驱动模型解析结果、更新状态、发射信号。定时器的超时是另一个事件驱动倒计时状态的更新。所有UI的更新都是通过监听这些状态变化信号来被动响应的避免了主动轮询或紧耦合的调用。3. 核心代码实现与逐行解析下面我将呈现这个SmsVerificationCodeModel的核心实现代码并附上详细的注释说明每一部分的设计意图和注意事项。3.1 头文件定义 (smsverificationcodemodel.h)头文件定义了类的接口、信号和槽以及内部状态。#ifndef SMSVERIFICATIONCODEMODEL_H #define SMSVERIFICATIONCODEMODEL_H #include QObject #include QTimer #include QNetworkAccessManager #include QNetworkReply class SmsVerificationCodeModel : public QObject { Q_OBJECT // 将状态和剩余时间暴露给QML或属性系统可选 Q_PROPERTY(State state READ state NOTIFY stateChanged) Q_PROPERTY(int countdownSeconds READ countdownSeconds NOTIFY countdownSecondsChanged) public: explicit SmsVerificationCodeModel(QObject *parent nullptr); ~SmsVerificationCodeModel(); // 内部状态枚举 enum State { StateIdle, // 空闲可发送 StateSending, // 发送请求中 StateCountdown, // 发送成功倒计时中 StateFailed // 发送失败 }; Q_ENUM(State) // 获取当前状态 State state() const { return m_currentState; } // 获取倒计时剩余秒数 int countdownSeconds() const { return m_countdownSeconds; } // 配置API可在构造函数中传入或通过set方法 void setApiEndpoint(const QString url) { m_apiUrl url; } void setAppKey(const QString key) { m_appKey key; } public slots: // 核心方法请求向指定手机号发送验证码 void requestVerificationCode(const QString phoneNumber); signals: // 状态改变信号UI层应监听此信号 void stateChanged(SmsVerificationCodeModel::State newState); // 倒计时秒数变化信号用于实时更新按钮文本 void countdownSecondsChanged(int remainingSeconds); // 请求开始信号可选用于UI显示加载 void requestStarted(); // 请求完成信号携带成功与否及消息 void requestFinished(bool success, const QString message); private slots: // 处理网络请求回复 void onReplyFinished(QNetworkReply *reply); // 倒计时定时器每秒触发 void onCountdownTimerTimeout(); private: // 内部状态切换方法 void setState(State newState); // 校验手机号格式简单版本 bool validatePhoneNumber(const QString phoneNumber) const; // 构建请求的JSON数据 QByteArray buildRequestJson(const QString phoneNumber) const; State m_currentState StateIdle; int m_countdownSeconds 0; int m_totalCountdownDuration 60; // 默认倒计时60秒 QTimer *m_countdownTimer; // 用于每秒更新剩余时间 QNetworkAccessManager *m_networkManager; QString m_apiUrl “https://your-api-server.com/sms/send”; // 默认API地址 QString m_appKey “”; // 用于签名的应用密钥 }; #endif // SMSVERIFICATIONCODEMODEL_H关键设计点使用Q_PROPERTY虽然我们的Mode主要供C的Widget使用但声明为属性可以使其无缝接入QML界面极大增强了组件的复用范围。状态枚举State明确定义四种状态这是整个模型驱动UI的核心。所有UI变化都源于状态变迁。信号设计stateChanged是主信号。requestFinished是一个业务结果信号方便UI弹出成功或失败提示。countdownSecondsChanged用于实现精细的倒计时UI更新。3.2 源文件实现 (smsverificationcodemodel.cpp)这是逻辑实现的核心包含了所有的业务细节。#include “smsverificationcodemodel.h” #include QNetworkRequest #include QJsonDocument #include QJsonObject #include QDebug #include QRegularExpression SmsVerificationCodeModel::SmsVerificationCodeModel(QObject *parent) : QObject(parent) , m_currentState(StateIdle) , m_countdownSeconds(0) , m_countdownTimer(new QTimer(this)) , m_networkManager(new QNetworkAccessManager(this)) { // 配置倒计时定时器每秒触发一次用于更新剩余时间显示 m_countdownTimer-setInterval(1000); // 1秒 connect(m_countdownTimer, QTimer::timeout, this, SmsVerificationCodeModel::onCountdownTimerTimeout); // 网络管理器默认已经可用无需额外配置 } SmsVerificationCodeModel::~SmsVerificationCodeModel() { // QT的对象树机制会自动管理子对象此处无需手动删除m_countdownTimer和m_networkManager } void SmsVerificationCodeModel::requestVerificationCode(const QString phoneNumber) { // 1. 状态守卫如果不在空闲状态则拒绝新的请求 if (m_currentState ! StateIdle m_currentState ! StateFailed) { qDebug() “请求被忽略当前状态为” m_currentState; emit requestFinished(false, “操作过于频繁请稍后再试”); return; } // 2. 验证手机号格式 if (!validatePhoneNumber(phoneNumber)) { emit requestFinished(false, “手机号格式不正确”); return; } // 3. 切换到“发送中”状态 setState(StateSending); emit requestStarted(); // 通知UI开始加载 // 4. 构建网络请求 QNetworkRequest request; request.setUrl(QUrl(m_apiUrl)); request.setHeader(QNetworkRequest::ContentTypeHeader, “application/json”); // 如有必要添加认证头例如使用App Key if (!m_appKey.isEmpty()) { request.setRawHeader(“X-App-Key”, m_appKey.toUtf8()); } QByteArray postData buildRequestJson(phoneNumber); qDebug() “准备发送请求到” m_apiUrl “数据” postData; // 5. 发起异步POST请求 QNetworkReply *reply m_networkManager-post(request, postData); // 使用Lambda连接finished信号确保reply对象在槽函数中被正确识别和处理 connect(reply, QNetworkReply::finished, this, [this, reply]() { onReplyFinished(reply); }); // 错误处理可选也可以在finished中统一处理 connect(reply, QNetworkReply::errorOccurred, this, [this, reply](QNetworkReply::NetworkError error) { qDebug() “网络请求发生错误” error reply-errorString(); // 注意errorOccurred信号触发后finished信号仍会触发所以错误处理最好在onReplyFinished中统一进行。 }); } void SmsVerificationCodeModel::onReplyFinished(QNetworkReply *reply) { // 确保reply对象被自动删除 reply-deleteLater(); bool success false; QString message; // 检查网络错误 if (reply-error() ! QNetworkReply::NoError) { message QString(“网络错误: %1”).arg(reply-errorString()); qWarning() “SMS request failed:” message; setState(StateFailed); emit requestFinished(success, message); return; } // 读取并解析服务器响应 QByteArray responseData reply-readAll(); QJsonParseError parseError; QJsonDocument jsonDoc QJsonDocument::fromJson(responseData, parseError); if (parseError.error ! QJsonParseError::NoError) { message QString(“服务器响应格式错误: %1”).arg(parseError.errorString()); qWarning() “Failed to parse JSON:” message “Raw data:” responseData; setState(StateFailed); emit requestFinished(success, message); return; } QJsonObject jsonObj jsonDoc.object(); int code jsonObj.value(“code”).toInt(-1); // 假设成功码为0 QString msg jsonObj.value(“msg”).toString(“Unknown error”); if (code 0) { // 请求成功 success true; message “验证码发送成功”; qDebug() “SMS request succeeded:” msg; // 启动倒计时 m_countdownSeconds m_totalCountdownDuration; setState(StateCountdown); m_countdownTimer-start(); // 启动每秒更新的定时器 // 同时设置一个单次定时器在倒计时结束后自动切换回空闲状态 QTimer::singleShot(m_totalCountdownDuration * 1000, this, [this]() { if (m_currentState StateCountdown) { setState(StateIdle); m_countdownTimer-stop(); } }); } else { // 请求失败业务逻辑失败如手机号已注册、频率超限等 message QString(“发送失败(%1): %2”).arg(code).arg(msg); qDebug() “SMS request business failed:” message; setState(StateFailed); } // 发射请求完成信号UI可根据success决定是显示成功提示还是错误信息 emit requestFinished(success, message); } void SmsVerificationCodeModel::onCountdownTimerTimeout() { if (m_currentState ! StateCountdown || m_countdownSeconds 0) { m_countdownTimer-stop(); return; } m_countdownSeconds--; emit countdownSecondsChanged(m_countdownSeconds); // 通知UI更新剩余秒数显示 // qDebug() “Countdown:” m_countdownSeconds; } void SmsVerificationCodeModel::setState(State newState) { if (m_currentState ! newState) { m_currentState newState; emit stateChanged(newState); // 状态变化是驱动UI的主信号 qDebug() “State changed to:” newState; } } bool SmsVerificationCodeModel::validatePhoneNumber(const QString phoneNumber) const { // 简单的中国大陆手机号校验正则可根据实际需求调整 QRegularExpression regex(“^1[3-9]\\d{9}$”); return regex.match(phoneNumber).hasMatch(); } QByteArray SmsVerificationCodeModel::buildRequestJson(const QString phoneNumber) const { QJsonObject json; json[“phone”] phoneNumber; json[“timestamp”] QDateTime::currentSecsSinceEpoch(); // 时间戳防重放 // 这里可以添加签名逻辑例如sign md5(phonetimestampappSecret) // json[“sign”] calculateSign(phoneNumber, timestamp, m_appSecret); QJsonDocument doc(json); return doc.toJson(QJsonDocument::Compact); }代码解析与避坑指南状态守卫在requestVerificationCode开头进行状态检查至关重要。它能有效防止用户在“发送中”或“倒计时”状态下疯狂点击按钮导致重复请求既浪费资源又可能触发后端的风控策略。网络请求的生命周期管理QNetworkReply *reply对象必须在请求完成后被删除。我们使用reply-deleteLater()将其交给QT的事件循环自动删除这是最安全的方式。绝对不要在槽函数外或使用delete直接删除否则可能导致程序崩溃。Lambda表达式的使用在连接finished信号时使用了Lambda并捕获了this和reply。这确保了在槽函数执行时我们能访问到正确的模型实例和对应的回复对象。注意Lambda中捕获的reply就是当前这次请求的回复完美解决了多个并发请求时信号混淆的问题虽然我们这个模型通常串行处理。错误处理的层次onReplyFinished中先处理网络层错误reply-error()再处理应用层错误解析JSON和业务状态码code。这样能给出更精准的错误提示。双定时器策略我们使用了一个每秒触发的m_countdownTimer来更新UI显示又用了一个QTimer::singleShot在倒计时总时长后重置状态。为什么不用一个定时器循环m_totalCountdownDuration次因为singleShot更符合“倒计时结束”这个一次性事件的语义逻辑更清晰。而秒级定时器专注于UI更新。JSON解析的安全性使用QJsonParseError来检查JSON格式是否正确避免因服务器返回非法数据导致解析崩溃。4. 在项目中的集成与使用示例4.1 在C Widget项目中的集成假设我们有一个传统的QWidget登录对话框LoginDialog里面有一个发送验证码的按钮m_btnSendSms。// logindialog.h #include “smsverificationcodemodel.h” class LoginDialog : public QDialog { Q_OBJECT public: LoginDialog(QWidget *parent nullptr); private slots: void onSendButtonClicked(); void onSmsStateChanged(SmsVerificationCodeModel::State state); void onCountdownChanged(int seconds); void onSmsRequestFinished(bool success, const QString msg); private: Ui::LoginDialog *ui; SmsVerificationCodeModel *m_smsModel; }; // logindialog.cpp LoginDialog::LoginDialog(QWidget *parent) : QDialog(parent) , ui(new Ui::LoginDialog) , m_smsModel(new SmsVerificationCodeModel(this)) { ui-setupUi(this); // 连接按钮点击信号 connect(ui-m_btnSendSms, QPushButton::clicked, this, LoginDialog::onSendButtonClicked); // 连接Model的信号 connect(m_smsModel, SmsVerificationCodeModel::stateChanged, this, LoginDialog::onSmsStateChanged); connect(m_smsModel, SmsVerificationCodeModel::countdownSecondsChanged, this, LoginDialog::onCountdownChanged); connect(m_smsModel, SmsVerificationCodeModel::requestFinished, this, LoginDialog::onSmsRequestFinished); // 配置Model m_smsModel-setApiEndpoint(“https://api.yourdomain.com/v1/sms/send”); m_smsModel-setAppKey(“your_app_key_here”); } void LoginDialog::onSendButtonClicked() { QString phone ui-lineEditPhone-text().trimmed(); if (phone.isEmpty()) { QMessageBox::warning(this, “提示”, “请输入手机号”); return; } m_smsModel-requestVerificationCode(phone); } void LoginDialog::onSmsStateChanged(SmsVerificationCodeModel::State state) { QPushButton *btn ui-m_btnSendSms; btn-setEnabled(state SmsVerificationCodeModel::StateIdle || state SmsVerificationCodeModel::StateFailed); // 可以根据状态改变按钮样式例如发送中显示加载动画 } void LoginDialog::onCountdownChanged(int seconds) { ui-m_btnSendSms-setText(QString(“%1秒后重试”).arg(seconds)); } void LoginDialog::onSmsRequestFinished(bool success, const QString msg) { // 可以在这里用QMessageBox或一个QLabel来显示发送结果提示 if (success) { // 可能不需要提示因为倒计时已经开始 // statusBar()-showMessage(“验证码已发送”, 3000); } else { QMessageBox::warning(this, “发送失败”, msg); // 发送失败后按钮文本可以重置为“重新发送” if (m_smsModel-state() SmsVerificationCodeModel::StateFailed) { ui-m_btnSendSms-setText(“重新发送”); } } }集成要点Model作为成员变量将SmsVerificationCodeModel实例作为对话框的成员其生命周期由父对象LoginDialog管理。信号连接将Model的stateChanged、countdownSecondsChanged、requestFinished信号连接到对话框的槽函数实现UI的自动更新。UI状态同步在onSmsStateChanged中根据模型状态设置按钮的可用性setEnabled。这是实现“防重复点击”的关键。业务逻辑分离对话框只负责收集用户输入手机号和展示结果所有关于“何时能发”、“发的结果如何”、“倒计时怎么走”的逻辑全部由Model控制。代码清晰职责分明。4.2 在QML项目中的集成得益于我们在头文件中使用了Q_PROPERTY和Q_ENUM这个Model可以非常方便地在QML中使用。// LoginPage.qml import QtQuick 2.15 import QtQuick.Controls 2.15 import com.yourcompany.sms 1.0 // 假设Model被注册到了这个模块 Page { id: root // 在C端将Model实例设置为QML上下文属性或通过其他方式注入 property alias smsModel: _smsModel SmsVerificationCodeModel { id: _smsModel // 属性绑定也可以在QML中设置 // apiEndpoint: “https://...” } Column { anchors.centerIn: parent spacing: 20 TextField { id: phoneField placeholderText: “请输入手机号” validator: RegularExpressionValidator { regularExpression: /^1[3-9]\d{9}$/ } } Button { id: sendSmsButton text: { switch (_smsModel.state) { case SmsVerificationCodeModel.StateIdle: return “获取验证码” case SmsVerificationCodeModel.StateSending: return “发送中...” case SmsVerificationCodeModel.StateCountdown: return _smsModel.countdownSeconds “秒后重试” case SmsVerificationCodeModel.StateFailed: return “发送失败点击重试” } } enabled: _smsModel.state SmsVerificationCodeModel.StateIdle || _smsModel.state SmsVerificationCodeModel.StateFailed onClicked: { if (!phoneField.acceptableInput) { showToast(“手机号格式错误”) return } _smsModel.requestVerificationCode(phoneField.text) } } } // 连接Model的信号 Connections { target: _smsModel function onRequestFinished(success, message) { if (!success) { showToast(message) // 假设showToast是一个显示提示的函数 } } } }QML集成优势声明式绑定按钮的text和enabled属性直接绑定到Model的state和countdownSeconds属性上。当Model状态变化时QML引擎会自动更新UI无需手动编写槽函数来设置文本和状态。这是MVVM模式的精髓。代码极其简洁整个UI逻辑清晰直观业务逻辑完全由后端的Model驱动。5. 进阶优化与生产环境考量上面展示的是一个基础可用的版本。但在实际生产项目中我们还需要考虑更多细节。5.1 网络请求的健壮性增强超时处理QNetworkRequest可以设置超时时间。QNetworkRequest request; request.setUrl(QUrl(m_apiUrl)); request.setTransferTimeout(10000); // 设置10秒超时SSL/TLS配置如果API使用HTTPS可能需要配置SSL证书。对于自签名证书需要忽略错误仅限测试环境。QSslConfiguration sslConfig QSslConfiguration::defaultConfiguration(); sslConfig.setPeerVerifyMode(QSslSocket::VerifyNone); // 生产环境应改为VerifyPeer request.setSslConfiguration(sslConfig);请求重试机制对于网络波动导致的失败可以加入简单的重试逻辑。注意重试前需判断状态并避免无限重试。void SmsVerificationCodeModel::requestVerificationCode(const QString phoneNumber) { // ... 校验和状态检查 ... m_currentPhoneNumber phoneNumber; // 保存手机号用于重试 m_retryCount 0; doRequest(); } void SmsVerificationCodeModel::doRequest() { // 实际发起请求的逻辑 QNetworkReply *reply m_networkManager-post(...); connect(reply, QNetworkReply::finished, this, [this, reply]() { if (reply-error() QNetworkReply::TimeoutError m_retryCount MAX_RETRY) { m_retryCount; QTimer::singleShot(2000, this, [this]() { doRequest(); }); // 2秒后重试 } else { onReplyFinished(reply); } reply-deleteLater(); }); }5.2 数据安全与防篡改参数签名在buildRequestJson函数中提到的签名逻辑非常重要可以防止请求被篡改。通常使用MD5或HMAC-SHA256对“手机号时间戳密钥”进行签名并将签名值一同发送。服务器端用同样的算法验证确保请求来源合法。手机号脱敏处理在日志中打印手机号时应进行脱敏例如只显示前3后4位。QString maskedPhone phoneNumber.left(3) “****” phoneNumber.right(4); qDebug() “Requesting SMS for:” maskedPhone;5.3 配置化与可测试性将配置移至外部API地址、App Key、倒计时时长等不应硬编码在代码中。可以通过QSettings读取配置文件或在构造函数中传入一个配置对象。依赖注入便于测试可以将QNetworkAccessManager作为参数传入构造函数。这样在单元测试中我们可以传入一个模拟的Mock网络管理器从而在不依赖真实网络的情况下测试Model的业务逻辑。class SmsVerificationCodeModel : public QObject { public: explicit SmsVerificationCodeModel(QNetworkAccessManager *networkManager nullptr, QObject *parent nullptr); private: QNetworkAccessManager *m_networkManager; bool m_networkManagerOwned; // 标记是否拥有该管理器 };5.4 与开源项目的结合回顾我们参考的开源验证码项目tianai-captcha它是一个后端行为验证码库。我们的SmsVerificationCodeModel是前端客户端的短信验证码发送模型。二者可以完美结合构成一个完整的验证体系用户进入登录页首先需要完成一个滑块或点选验证使用tianai-captcha的Web SDK或类似前端库以证明不是机器人。验证通过后前端获得一个token。用户输入手机号点击发送。此时SmsVerificationCodeModel在构建请求JSON时需要将这个token作为参数之一例如captchaToken发送给后端。后端在发送短信前先校验这个token的有效性通过后才执行真实的短信发送逻辑。这样就从源头发送请求前增加了恶意刷短信的成本是当前业界的标准实践。我们的Mode可以很容易地扩展一个setCaptchaToken的方法来支持这种流程。封装这个SmsVerificationCodeModel的过程本质上是一次对“关注点分离”和“模型驱动开发”的实践。它让验证码发送这个功能点从一堆散落在各个UI文件里的碎片化代码变成了一个独立、健壮、可测试、可复用的核心组件。当你下次在QT项目中再遇到“发送验证码”的需求时你只需要把这个模型类复制过去花几分钟连接一下信号槽功能就完成了。这种通过抽象和封装来提升开发效率、保证代码质量的方法其价值远超过实现功能本身。