获取手机验证码
前言
前几集我们就完成了个人信息窗口的个人昵称和个人个性签名的修改,那么我们还有一个修改内容是我们的绑定手机号的修改,这个内容的前提是我们要完成获取手机验证码的相关功能,所以这一集我们就来完成获取手机验证码的功能。
短信验证码链路
客户端发起请求:
用户在客户端(比如一个手机应用)上发起获取短信验证码的请求。
服务器生成验证码:
服务器接收到请求后,首先生成一个唯一的验证码(verifyCode),这个验证码是一个数字字符串。
同时,服务器会生成一个与验证码相关联的键值对,其中键(key)是verifyCodeId
,值(value)是生成的验证码。
服务器调用第三方短信服务:
服务器调用第三方短信服务提供商的SDK,将短信发送到用户的手机上。这个过程中,第三方服务会使用服务器提供的手机号码和验证码。
短信发送:
第三方短信服务提供商(如阿里云、腾讯云)将短信发送到用户的手机上。
服务器保存验证码:
为了验证用户输入的验证码,服务器需要将生成的验证码和对应的verifyCodeId
保存起来,通常使用Redis等缓存系统来实现快速访问和过期功能。
客户端接收验证码:
用户的手机接收到短信,短信中包含了验证码。
用户输入验证码:
用户在客户端输入收到的验证码。
客户端提交验证码:
客户端将用户输入的验证码和verifyCodeId
一起发送回服务器进行验证。
服务器验证验证码:
服务器接收到客户端提交的验证码和verifyCodeId
后,会检查缓存中是否存在对应的verifyCodeId
和验证码。
如果验证码匹配且在有效期内,验证成功,可以进行后续操作(如修改电话)。
这就是短信验证码的整条链路。
需求分析
我们第一步要点击修改电话的按钮。
之后就会弹出我们短信验证码的获取按钮以及编辑框,我们需要点击获取验证码按钮
获取到验证码后,我们修改电话,输入验证码,点击手机号码提交按钮即可。
我们老样子,先来看看我们的URL和请求响应的定义。
这就是我们定义的URL
这个是我们http的请求和响应!
这就是我们这一集要去构建的内容!
客户端
先从客户端的链路开始,我们之前完成了点击修改手机号码就弹出短信验证码相关组件的功能。那么我们要先给获取验证码按钮绑定一个信号槽!
connect(getVerifyCodeBtn, &QPushButton::clicked, this, &SelfInfoWidget::clickGetVerifyCodeBtn);
当我们点击获取验证码按钮之后,就会触发我们这里的函数。
我们要先获取到输入框的手机号码,之后给我们的服务器发送一个请求,当验证码发送到我们手机之后就会触发一个获取验证码完成的信号,这个信号只需要通知我们的用户验证码已经发送到我们的手机即可。之后我们还要把发送请求的手机号码给保存一下。
我们还有一个问题,不能让用户一直调用这个获取验证码的功能,我们应该做一下限制,弄一个倒计时禁用发送验证码功能。
我们用QTimer进行实现。
void SelfInfoWidget::clickGetVerifyCodeBtn()
{
//获取输入框的手机号码
const QString& phone = phoneEdit->text();
if(phone.isEmpty()){
return;
}
//给服务器发送请求
DataCenter* dataCenter = DataCenter::getInstance();
connect(dataCenter, &DataCenter::getVerifyCodeDone, this, [=](){
//不需要做处理,要提示一下,验证码已经发送!
Toast::showMessage("短信验证码已发送");
});
dataCenter->getVerifyCodeAsync(phone);
//把发送请求的手机号码保存
this->phoneToChange = phone;
//禁用发送验证码按钮,给出倒计时
getVerifyCodeBtn->setEnabled(false);
leftTime = 30;
QTimer* timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, [=](){
if(leftTime < 1){
//倒计时结束
getVerifyCodeBtn->setEnabled(true);
getVerifyCodeBtn->setText("获取验证码");
timer->stop();
timer->deleteLater();
return;
}
--leftTime;
getVerifyCodeBtn->setText(QString::number(leftTime) + "s");
});
timer->start(1000);
}
这里我们要通过网络通信,我们还是需要从DataCenter通向我们的NetClient。但是这里我们不需要传入loginSessionId,因为我们要复用代码实现我们的手机验证码登录功能。
void DataCenter::getVerifyCodeAsync(const QString &phone)
{
netClient.getVerifyCode(phone);//不需要传入loginSessionId,要兼容手机验证码登录,没有loginSessionId
}
之后就到了我们的网络这边,我们还是老样子,代码也是敲了很多遍了。
先构造http请求的body,之后序列化,发送http请求即可,之后就是连接信号槽来处理我们的响应。之后要把我们的验证码的验证id保存到我们的数据中心中,之后发送信号,通知调用。
void NetClient::getVerifyCode(const QString &phone)
{
//构造http请求body
bite_im::PhoneVerifyCodeReq pbReq;
pbReq.setRequestId(makeRequestId());
pbReq.setPhoneNumber(phone);
QByteArray body = pbReq.serialize(&serializer);
LOG() << "[获取手机验证码] 发送请求 requestId=" << pbReq.requestId() << ", phone=" << phone;
//发送http请求
QNetworkReply* resp = this->sendHttpRequest("/service/user/get_phone_verify_code", body);
//处理响应
connect(resp, &QNetworkReply::finished, this, [=](){
bool ok = false;
QString reason;
auto pbResp = this->handleHttpResponse<bite_im::PhoneVerifyCodeRsp>(resp, &ok, &reason);
//判定是否出错
if(!ok){
LOG() << "[获取手机验证码]失败!reason=" << reason;
return;
}
//设置数据到DataCenter
dataCenter->resetVerifyCodeId(pbResp->verifyCodeId());
//发送信号,通知调用
emit dataCenter->getVerifyCodeDone();
LOG() << "[获取手机验证码] 处理响应完毕! requestId=" << pbResp->requestId();
});
}
这就是我们获取验证码的客户端功能。
测试服务端
我们还是老样子,先给我们的获取验证码配置一个路由
httpServer.route("/service/user/get_phone_verify_code", [=](const QHttpServerRequest& req){
return this->getPhoneVerifyCode(req);
});
之后就是完成响应的构造
QHttpServerResponse HttpServer::getPhoneVerifyCode(const QHttpServerRequest &req)
{
//解析请求
bite_im::PhoneVerifyCodeReq pbReq;
pbReq.deserialize(&serializer, req.body());
LOG() << "[REQ 获取手机验证码] requestId=" << pbReq.requestId() << ", phone=" << pbReq.phoneNumber();
//构造响应
bite_im::PhoneVerifyCodeRsp pbResp;
pbResp.setRequestId(pbReq.requestId());
pbResp.setSuccess(true);
pbResp.setErrmsg("");
pbResp.setVerifyCodeId("testVerifyCodeId");
QByteArray body = pbResp.serialize(&serializer);
//构造http响应
QHttpServerResponse resp(body, QHttpServerResponse::StatusCode::Ok);
resp.setHeader("Content-Type", "application/x-protobuf");
return resp;
}
这里注意一下,我们要设置一个验证码id。