Day2_客户端http管理类
一、设置错误提示功能
设置一个错误提示信息,邮箱格式错误或者是网络错误都会提示出来,并且是红色字体。
如果正确则是绿色。
- 在qss文件里添加err_tip样式,根据不同的状态做字体显示。
//如果stata=normal就是绿色
#err_tip[state='normal']
{
color : green;
}
//如果stae=err就是红色
#err_tip[state='err']
{
color : red;
}
- 在global头文件中生命repolish函数,并在global.cpp中实现它
功能:把旧的属性去掉,重新添加一个新的属性
就是说,当出现错误是,会根据状态来重新渲染字体颜色
如:输入邮箱格式错误,点击获取按钮会直接把字体变为红色,并显示错误信息
std::function<void(QWidget *)> repolish = [](QWidget * w){
w->style()->unpolish(w); //把原来的样式卸掉
w->style()->polish(w); //再重新刷一下
};
2、实现获取验证码
验证邮箱格式是否正确,如果正确,就发送验证码,如果不正确就将错误提示变为红色
void RegisterDialog::on_get_code_clicked()
{
//验证邮箱的地址正则表达式
auto email = ui->email_edit->text();
// 邮箱地址的正则表达式
QRegularExpression regex(R"((\w+)(\.|_)?(\w*)@(\w+)(\.(\w+))+)");
bool match = regex.match(email).hasMatch(); // 执行正则表达式匹配
if(match){
//发送http请求获取验证码
}else{
//提示邮箱不正确
showTip(tr("邮箱地址不正确"));
}
}
- 添加showTip函数
参数:(将要修改的错误提示,邮箱格式是否正确)
void RegisterDialog::showTip(QString str, bool b_ok)
{
if(b_ok)
{
ui->err_tip->setProperty("state","normal"); //如果正确,就设置为nomal,绿色
}
else
{
ui->err_tip->setProperty("state","err"); //设置错误状态,err就是红色
}
ui->err_tip->setText(str);
repolish(ui->err_tip); //重新刷新一下
}
二、实现判断邮箱正确后发送http请求
1、 网络请求类要做成一个单例类,这样方便在任何需要发送http请求的时候调用,我们先实现单例类,添加singleton.h实现如下
#ifndef SINGLETON_H
#define SINGLETON_H
#include "global.h"
template <typename T>
//单例类
class Singleton
{
protected:
//构造函数,不允许被外部访问,但希望子类可以构造它,保证子类的构造没有问题
Singleton() = default;
//不允许拷贝构造和拷贝赋值
Singleton(const Singleton<T>&) = delete; //删除拷贝构造
Singleton& operator = (const Singleton<T>& st) = delete; //删除拷贝赋值
//单例类会返回一个实例,我们不希望手动去回收这个实例,就需要智能指针来实现回收
static std::shared_ptr<T> _instance;
//为什么要用静态成员变量?,我们想在使用的时候,所有的单例类都返回这一个实例
public:
//实现一个公有对外的函数,它也是static
//因为我们期望,不管这个单例类在哪调用,都需要要给作用域然后一个单例类里的静态函数直接返回一个静态的成员变量。
//达到单例的效果
static std::shared_ptr<T> GetInstance()
{
//第一次调用的时候s_flag会初始化,但是多次调用的时候s_flag只会初始化一次
static std::once_flag s_flag;
std::call_once (s_flag, [&](){
_instance = std::shared_ptr<T>(new T); //思考:为什么不用makeshare来生成智能指针?
});
return _instance;
}
//打印地址
void PrintAddress()
{
std::cout<< _instance.get() <<std::endl;
}
~Singleton()
{
std::cout<<"this is singleton destruct" <<std::endl;
}
};
//如果直接在外部直接调用Singleton中的GetInstance函数,编译的时候能过,但是链接会出问题
//它会告诉你不识别。问题出在_instance,它是一个静态变量
//一个类的静态变量,一定会被初始化,如果它不是一个模板类,那么我们要把它放到cpp里
//如果它是一个模板类,就需要放在.h文件中。
template <typename T>
//_instance是属于Singleton<T>模板类下面的成员变量,将它初始化为空指针
//_instance是std::shared_ptr<T>类型
std::shared_ptr<T> Singleton<T>::_instance = nullptr;
#endif // SINGLETON_H
2、 http管理类(HttpMgr)
- 实现Post请求函数PostHttpReq
功能:实现发发送http请求。
发送请求用到请求的url,请求的数据(json或者protobuf序列化),请求的id,以及哪个模块发出的请求mod。
PostHttpReq函数声明如下:
void PostHttpReq(QUrl url, QJsonObject json, ReqId req_id, Modules mod);
- 在global.h定义ReqId枚举类型
//注册模块
enum ReqId
{
ID_GET_VARIFY_CODE = 1001, //获取验证码
ID_REG_USER = 1002, //注册用户
};
//错误的返回码
enum ErrorCodes
{
SUCCESS = 0, //成功
ERR_JSON = 1, //json解析失败
ERR_NETWORK = 2, //网络错误
};
//模块
enum Modules
{
REGISTERMOD = 0,
};
- 修改要发送的信号
http发送完了之后,会发送一个finish的信号,通知给其他模块,让其他模块做界面上的显示
结果QString res是成功或失败的内容,它是一个json的字符串,可以扔到不同的模块去解析
// 参数:(请求的id, 结果res(成功|失败), 具体的错误, 属于哪个模块)
void sig_http_finish(ReqId id, QString res, ErrorCodes err, Modules mod);
- 实现PostHttpReq函数
功能:发送http的post请求
具体步骤:
1、创建http post请求,设置请求头和请求体;
2、创建要发送的数据data
3、设置请求头信息:请求的数据类型、请求数据长度
4、发送post请求,并处理响应,这里采用伪闭包技术实现
处理过程:如果收到响应报文,会有finish信号,然后执行lambda表达式。
lambda表达式:如果有错误就处理错误,如果没有错误就读出请求回应内容,然后发送sig_http_finish信号通知完成
sig_http_finish信号跟槽函数slot_http_finish连接,如果有信号过来就会执行slot_http_finish函数。
void HttpMgr::PostHttpReq(QUrl url, QJsonObject json, ReqId req_id, Modules mod)
{
//创建一个HTTP POST请求,并设置请求头和请求体
//要发送http请求,首先需要创建发送的数据,
//需要把这样一个json对象序列化成字节数组
QByteArray data = QJsonDocument(json).toJson(); //将C++中的数据结构转换为JSON格式的字符串
//http是基于tcp实现的,但是http发送完请,收到对方的恢复之后,链接自动断开,并不会维持一个长链接的状态
//http不用切包,不存在粘包的问题,原因是:我们在头部设置了一些对端需要获取的信息
//比如,我们回告诉对方:我们发送的数据类型是json还是text文本类型还是matter这样的复杂类型,
//还会告诉对方数据的总长度是多少。 对方会在底层自动获取这么长的数据
QNetworkRequest request(url); //通过url构造请求
//设置头部信息,
request.setHeader(QNetworkRequest::ContentTypeHeader,"application/json"); //1、请求的数据类型为json
request.setHeader(QNetworkRequest::ContentLengthHeader,QByteArray::number(data.length())); //2、请求数据长度
//发送请求,并处理响应,获取自己的智能指针,构造伪闭包并增加智能指针引用计数
//使用闭包技术,C++中是没有闭包的,想要使用,就要继承enable_shared_from_this类
auto self = shared_from_this();
//因为我们发送完请求之后会受到一个回应,所以用reply类型,_manager.post(请求,数据) 就是要发送post请求和数据
QNetworkReply * reply = _manager.post(request, data); //reply是指针,需要我们自己去回收
//问题:发送了之后,我们怎么知道它完成没完成?
//设置信号和槽 等待发送完成 收到reply finish 回应信号之后,就执行lambda表达式
//其中,lambda表达式捕获reply,self,req+id,mod
QObject::connect(reply, &QNetworkReply::finished, [reply, self, req_id, mod](){
//处理错误的情况
if(reply->error() != QNetworkReply::NoError) //如果error有错误
{
qDebug() << reply->errorString(); //打印错误类型
//发送信号通知完成,出错了也要告诉相关界面出错了,
//发送内容:出错误的id,数据(这里是空字符串),错误的类型(网络错误),模块号
emit self->sig_http_finish(req_id, "", ErrorCodes::ERR_NETWORK, mod);
//采用deleteLater()方式回收,因为reply又可能还会被用于底层的事件循环,deleteLater()就是告诉qt
//等reply不用的时候,你帮我回收一下
reply->deleteLater(); //回收reply
return;
}
//如果没有错误,就读出所有字节,读出请求回应报文
//readAll()函数实际上是QByteArry类型,我们可以直接把QByteArry转换为QString类型
QString res = reply->readAll();
//发送信号通知完成
//发送内容:(id,返回的结果(成功),成功,模块号)
emit self->sig_http_finish(req_id, res, ErrorCodes::SUCCESS, mod);
reply->deleteLater(); //回收reply
return;
});
}
将信号sig_http_finish和槽函数slot_http_finish链接起来
如果信号sig_http_finish有返回就执行槽函数slot_http_finish
connect(this, &HttpMgr::sig_http_finish, this, &HttpMgr::slot_http_finish);
槽函数,用来接收sig_http_finish信号
//参数:(id, 返回的结果(成功/失败),具体的错误,模块号)
void HttpMgr::slot_http_finish(ReqId id, QString res, ErrorCodes err, Modules mod)
{
if(mod == Modules::REGISTERMOD) //如果是注册模块
{
//发送信号通知指定模块http的响应结束了
emit sig_reg_mod_finish(id, res, err);
//我们把sig_reg_mod_finish这个信号定义在httpmgr里,让注册信号去连接它,从而达到想要的效果
}
}
总结:
qt的http请求是一个异步的发送,发送完之后并不能马上收到对方的回应,qt回给我们生成一个reply实例。
这个reply我们要手动的回收,我们什么时候收到回包不确定,是通过lambda表达式告诉我们。

浙公网安备 33010602011771号