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自定义按钮里,那么这个按钮就会变得异常臃肿,难以测试(你需要模拟点击、模拟网络响应),更无法复用到其他样式的按钮或非按钮的触发场景中(比如按回车键触发发送)。

因此,我采用了经典的MVC(Model-View-Controller)或其变种(如MVVM)设计思想。这里的Mode(模型)就是SmsVerificationCodeModel类。它不关心自己是被一个按钮点击触发,还是被一个菜单项触发。它的职责非常清晰:

  1. 状态管理:维护一个内部状态枚举(Idle,Sending,Countdown,Failed),并对外发射状态变化的信号。
  2. 业务逻辑:暴露requestVerificationCode(const QString& phoneNumber)方法。当外部调用此方法时,模型负责校验手机号格式、发起网络请求、启动倒计时计时器。
  3. 数据封装:它可能包含一些配置数据,如倒计时总时长(默认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 (C++11及以上):用于优雅地处理网络请求的异步回调。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

关键设计点

  1. 使用Q_PROPERTY:虽然我们的Mode主要供C++的Widget使用,但声明为属性可以使其无缝接入QML界面,极大增强了组件的复用范围。
  2. 状态枚举State:明确定义四种状态,这是整个模型驱动UI的核心。所有UI变化都源于状态变迁。
  3. 信号设计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(phone+timestamp+appSecret) // json[“sign”] = calculateSign(phoneNumber, timestamp, m_appSecret); QJsonDocument doc(json); return doc.toJson(QJsonDocument::Compact); }

代码解析与避坑指南

  1. 状态守卫:在requestVerificationCode开头进行状态检查至关重要。它能有效防止用户在“发送中”或“倒计时”状态下疯狂点击按钮,导致重复请求,既浪费资源又可能触发后端的风控策略。
  2. 网络请求的生命周期管理QNetworkReply *reply对象必须在请求完成后被删除。我们使用reply->deleteLater()将其交给QT的事件循环自动删除,这是最安全的方式。绝对不要在槽函数外或使用delete直接删除,否则可能导致程序崩溃。
  3. Lambda表达式的使用:在连接finished信号时使用了Lambda,并捕获了thisreply。这确保了在槽函数执行时,我们能访问到正确的模型实例和对应的回复对象。注意Lambda中捕获的reply就是当前这次请求的回复,完美解决了多个并发请求时信号混淆的问题(虽然我们这个模型通常串行处理)。
  4. 错误处理的层次onReplyFinished中先处理网络层错误(reply->error()),再处理应用层错误(解析JSON和业务状态码code)。这样能给出更精准的错误提示。
  5. 双定时器策略:我们使用了一个每秒触发的m_countdownTimer来更新UI显示,又用了一个QTimer::singleShot在倒计时总时长后重置状态。为什么不用一个定时器循环m_totalCountdownDuration次?因为singleShot更符合“倒计时结束”这个一次性事件的语义,逻辑更清晰。而秒级定时器专注于UI更新。
  6. 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(“重新发送”); } } }

集成要点

  1. Model作为成员变量:将SmsVerificationCodeModel实例作为对话框的成员,其生命周期由父对象(LoginDialog)管理。
  2. 信号连接:将Model的stateChangedcountdownSecondsChangedrequestFinished信号连接到对话框的槽函数,实现UI的自动更新。
  3. UI状态同步:在onSmsStateChanged中,根据模型状态设置按钮的可用性(setEnabled)。这是实现“防重复点击”的关键。
  4. 业务逻辑分离:对话框只负责收集用户输入(手机号)和展示结果,所有关于“何时能发”、“发的结果如何”、“倒计时怎么走”的逻辑,全部由Model控制。代码清晰,职责分明。

4.2 在QML项目中的集成

得益于我们在头文件中使用了Q_PROPERTYQ_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集成优势

  1. 声明式绑定:按钮的textenabled属性直接绑定到Model的statecountdownSeconds属性上。当Model状态变化时,QML引擎会自动更新UI,无需手动编写槽函数来设置文本和状态。这是MVVM模式的精髓。
  2. 代码极其简洁:整个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前端(客户端)的短信验证码发送模型。二者可以完美结合,构成一个完整的验证体系:

  1. 用户进入登录页,首先需要完成一个滑块或点选验证(使用tianai-captcha的Web SDK或类似前端库),以证明不是机器人。
  2. 验证通过后,前端获得一个token
  3. 用户输入手机号,点击发送。此时,SmsVerificationCodeModel在构建请求JSON时,需要将这个token作为参数之一(例如captchaToken)发送给后端。
  4. 后端在发送短信前,先校验这个token的有效性,通过后才执行真实的短信发送逻辑。

这样,就从源头(发送请求前)增加了恶意刷短信的成本,是当前业界的标准实践。我们的Mode可以很容易地扩展一个setCaptchaToken的方法来支持这种流程。

封装这个SmsVerificationCodeModel的过程,本质上是一次对“关注点分离”和“模型驱动开发”的实践。它让验证码发送这个功能点从一堆散落在各个UI文件里的碎片化代码,变成了一个独立、健壮、可测试、可复用的核心组件。当你下次在QT项目中再遇到“发送验证码”的需求时,你只需要把这个模型类复制过去,花几分钟连接一下信号槽,功能就完成了。这种通过抽象和封装来提升开发效率、保证代码质量的方法,其价值远超过实现功能本身。