前言
最近写一个 pc 软件试试,当然一开始就是用户登录界面咯,跟同学软磨硬泡之下,帮我出了个 ui 图,长这样:
还别说,看上去就是专业,和我本人画半天的好看多了。
那么看到这个 ui 图就知道大概有哪些要实现的功能了。本文就只介绍一下我在实现微信登陆的过程中的经历吧。
完整代码 戳这
1. 开发环境
我们常见的微信登陆都是这个样子的
就一个二维码,用手机扫描即可登录。那么怎么有这个二维码呢?
我们这里用的是在自己的窗口部件中嵌入浏览器,也就是说,在自己的窗口部件中显示网页。二微信登陆的二维码也正是一个网页来的,这个网页就一个二维码。
既然要嵌入浏览器,那么这样一来 Qt 的版本就不能超过 5.6 了,因为 Qt 5.7 以后的版本不支持这个了。
所以综上,这个开发环境就是:
Qt 5.6.3
windows 环境下的
2. 用窗口显示网页
那么我们就先开始来做第一步吧,先显示出指定网页。
首先在 .pro 工程文件中加上
QT += webenginewidgets network
在 .h 文件中加上要用到的头文件:
#include <QUrl>
#include <QWebEngineView>
#include <QWebEngineSettings>
#include <QWebEngineUrlRequestInfo>
然后就是正餐了,要实现加载网页就要用到 QWebEngineView 类,所以我们在 .h 文件中先创建一个全局的响应对象指针:
QWebEngineView *LiveView; // 创建 web 浏览器显示对象
这样一来准备工作完成,来试试显示 百度(www.baidu.com) 吧。
在 .cpp 文件中加入以下代码:
this->LiveView = new QWebEngineView(this); // 创建对象
this->LiveView->settings()->setAttribute(QWebEngineSettings::PluginsEnabled, true); // 设置属性为使能插件
this->LiveView->setAttribute(Qt::WA_DeleteOnClose); // 设置属性为关闭时删除
this->LiveView->load(QUrl("http://www.baidu.com")); // 设置加载的网址(http不要忘了哦)
this->LiveView->resize(500, 500); // 现实的窗口大小
this->LiveView->show(); // 显示
运行效果如下:
这样就实现了将网页嵌入到我们自己的窗口之中,是不是很简单?
注意:如果在 .pro 文件加了相应模块后,构建时报出未找到对应的头文件错误的话,请不要慌,点击 构建---->执行qmake 之后再来一次就好啦。
3. 如何判断网页是否加载完成了?
在 QWebEngineView 类中有一个网页加载完成信号
[signal] void QWebEngineView::loadFinished(bool ok);
这样一来我们就可以使用信号和槽来处理加载完成的相关事件。
connect(LiveView, SIGNAL(loadFinished(bool)), this, SLOT(OnLoadFinished()));
关于这个槽函数,我是这样实现的
// 打印一下当前加载完的网址
void OnLoadFinished()
{
QString currentUrl = LiveView->url().toString();
qDebug() << "Url = " << currentUrl << endl;
}
就是这样了,具体要在这里实现什么小功能就看各位的脑洞了。
4. 用窗口显示微信登陆二维码网页
那么我们在上一步中实现了将百度网页内嵌到我们的窗口之中,那我们是不是该将微信登陆的二维码网页安排一下了呢?
没错,但是在此之前,我们先看看微信给出的这个二维码网页链接:
https://open.weixin.qq.com/connect/qrconnect?appid=%1&redirect_uri=%2&response_type=code&state=idc_1qloO7huDcIYf9qpjYZ8WkA&scope=snsapi_login#wechat_redirect
这么长咱也看不懂啊,咱现在注意这一段:
appid=%1&redirect_uri=%2
聪明的小伙伴应该已经想到了,没错,这个%1,%2两个地方要填入的内容就是该二维码网页的核心。那么这个 appid 和我们后面要用的 appsecret 是需要我们去向微信申请的。申请过程中要提交我们所要实现的应用名称等信息,经过审核之后就会拿到这两个内容啦。
我这里虽然后,但实在是不方便公开给大家测试,所以请各位小伙伴自己去微信申请吧~
好的,接下来看 redirect_uri 这个参数,它指的是扫描二维码并授权之后,将会跳转到哪个网址中。这个参数同样需要我们在申请 appid 和 appserect 时指定的,所以各位小伙伴还是先申请之后再往下看吧。
如果实在没有的话,咱可以借用一下 优酷 的微信登陆 url
https://open.weixin.qq.com/connect/qrconnect?appid=wxfec7791666c961ae&redirect_uri=https%3A%2F%2Faccount.youku.com%2Fhavana_callback.htm%3FtargetUrl%3Dhttps%253A%252F%252Fcnpassport.youku.com%252Foauth_sign.htm%253Ftype%253Dweixin&response_type=code&state=idc_1WEVs9T15qk04MxAGzfyeSA&scope=snsapi_login#wechat_redirect
在此之前呢,我们一起把刚刚写的代码封装一下,用于加载指定的网页到我们自己的窗口中。
int loadTheWebPage(QString myUrl)
{
LiveView->settings()->setAttribute(QWebEngineSettings::PluginsEnabled, true);
LiveView->setAttribute(Qt::WA_DeleteOnClose);
LiveView->load(QUrl(myUrl));
LiveView->resize(500, 500);
LiveView->show();
return 0;
}
接下来,咱就来拼接要加载的链接地址,并调用这个函数
// 小伙伴自己去申请这两个值哦,我这个是不能直接用的,*是某个字符来的
QString AppId = "wxf***************7f";
QString AppSecret = "1a6************************46";
QString RedirectUrl = "http://********.com.cn";
this->LiveView = new QWebEngineView(this);
QString myurl = QString("https://open.weixin.qq.com/connect/qrconnect?appid=%1&redirect_uri=%2&response_type=code&state=idc_1qloO7huDcIYf9qpjYZ8WkA&scope=snsapi_login#wechat_redirect")
.arg(AppId).arg(RedirectUrl);
loadTheWebPage(RequestUrl);
尝试过的小伙伴肯定知道了效果,这里就不放截图了。
但大家肯定很疑惑,因为这个效果就仅仅是跳转到我们所指定的网页罢了,仅仅扫了个码,登陆了个寂寞???
5. 登录过程
当然不是了,首先我们先明确一下我们所指的登录究竟是什么?
没错,获取当前扫码用户的预留在微信的个人信息,方便我们辨别用户对吧。那既然我们的最终目标是获取个人信息,那中间过程怎么实现呢?
让我们先来了解这一整套流程:
很显然,我们已经做完了第一步,第二步这 code 在哪儿呢?
6. 获取 code
这里使用的是优酷的那个微信登陆作为例子。
不知各位有没有注意到我们是不是有个槽函数在打印着什么东西,是的,它在兢兢业业的打印着每次加载完的网页网址。我们回头来看看这个打印结果。
刚开始显示二维码未扫码时打印的是这个:
Url = "https://open.weixin.qq.com/connect/qrconnect?appid=wxfec7791666c961ae&redirect_uri=https%3A%2F%2Faccount.youku.com%2Fhavana_callback.htm%3FtargetUrl%3Dhttps%253A%252F%252Fcnpassport.youku.com%252Foauth_sign.htm%253Ftype%253Dweixin&response_type=code&state=idc_1WEVs9T15qk04MxAGzfyeSA&scope=snsapi_login#wechat_redirect"
没错,这就是我们指定的链接地址,没什么问题,接着往下看。
扫描二维码并授权之后,网页跳转,打印的是这个:
Url = "https://cnpassport.youku.com/oauth_sign.htm?type=weixin&code=031nU5000i96lL1LgG100cqBT02nU501&state=idc_1WEVs9T15qk04MxAGzfyeSA"
欸?这里面好像有什么东西???
code=031nU5000i96lL1LgG100cqBT02nU501
哦吼,得来全不费工夫?藏在跳转之后的网页网址中了呀,那这样一来我们就可以很容易的获得它了,只需要将我们的打印网址的槽函数加一个判断再取出来就好了。
void OnLoadFinished()
{
QString currentUrl = LiveView->url().toString();
if(currentUrl.indexOf("code=") != -1) // 如果含有 code= ,就把 code 的值取出来
{
// 截取字符串操作,本人比较笨,硬截,方法多样可改进
QString code = currentUrl.mid( currentUrl.indexOf("code=")+5, (currentUrl.indexOf("&s")-currentUrl.indexOf("code=")-5) );
qDebug() << "code = " << code << endl;
}
}
这样一来,我们就完成了第二步。
后面的步骤没有 appsecret 就做不了啦,所以没有这个的小伙伴先去申请吧,先写到这。
7. 获取 access_token
那我们继续,现在我们手头里已经有了 code ,appid , appsecret,接下来就是用这三者去换取我们的access_token 了,具体怎么操作呢?
查阅微信开发文档,发现是发送一个 get 请求,然后 access_token 就在回码当中。
这个 get 请求地址长这样:
https://api.weixin.qq.com/sns/oauth2/access_token?appid=%1&secret=%2&code=%3&grant_type=authorization_code
然后我们将 code ,appid , appsecret 分别填入到对应位置,就组成了一个完整的请求地址。
那么我们首先来解决如何发送一个 get 请求这个问题。
我们需要用到 QNetworkAccessManager 类来处理网络请求相关的事务,这个类里面有 get 函数用于向目标地址发送一个 get 请求,当然了,它同样也有一个 post 函数,用于发送一个 post 请求。
请求发送完,服务器会相应回码,QNetworkAccessManager 类中有一个响应完成信号:
[signal] void QNetworkAccessManager::finished(QNetworkReply *reply);
再来看看 get 请求函数原型:
QNetworkReply *QNetworkAccessManager::get(const QNetworkRequest &request);
由此可见,我们还要导入这些头文件
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
好的,那我们就来编写一个专门用来发送一个 get 请求的函数:
void sendAGetRequest(const QString &url)
{
qDebug() << "url = " << url;
manager = new QNetworkAccessManager();
manager->get(QNetworkRequest(QUrl(url)));
qDebug() << "Finished send a requset" << endl;
}
那么我们现在就可以直接发送一个 get 请求了:
int getAccessToken(const QString &appId, const QString &appSecret, const QString &code)
{
QString toGetAccessToken = QString("https://api.weixin.qq.com/sns/oauth2/access_token?appid=%1&secret=%2&code=%3&grant_type=authorization_code")
.arg(appId).arg(appSecret).arg(code);
sendAGetRequest(toGetAccessToken); // 调用一下上面的函数,发送 1 个 get 请求
// 连接相应完成槽函数,若有回码,就在这个槽函数中接收
connect(manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(OnReplyFinshed(QNetworkReply*)));
return 0;
}
然后我们在槽函数中处理回文:
void OnReplyFinshed(QNetworkReply *reply)
{
QVariant variant = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
int status = variant.toInt();
qDebug() << "status = " << status; // http 状态码,据此来判断相应状态
QTextCodec *codec = QTextCodec::codecForName("utf8"); //使用utf8编码,这样才可以显示中文
QString all = codec->toUnicode(reply->readAll());
qDebug() << all; // 将回文打印出来
reply->deleteLater(); //最后要释放reply对象
}