QT发送HTTP请求的最小Demo

最近在筹划一个开源项目,为了尽可能压榨服务器性能,所以部分代码使用C++编写。程序涉及到对外的HTTP请求,正好发现QT自带这方面的库,那就直接引用吧!

下面给出一个最小的Demo,不做额外介绍,每个类的具体用法请查阅官方文档。

注意

跑Demo前需要在.pro文件中加上如下代码,以链接网络库。

QT += network

Demo:

#include <QCoreApplication>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QNetworkAccessManager>

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    // 构建及发送请求
    QNetworkAccessManager *manager = new QNetworkAccessManager();
    QString url = "https://www.ibadboy.net";
    QNetworkRequest request;
    request.setUrl(QUrl(url));
    QNetworkReply *pReply = manager->get(request);

    // 开启一个局部的事件循环,等待页面响应结束
    QEventLoop eventLoop;
    QObject::connect(manager, &QNetworkAccessManager::finished, &eventLoop, &QEventLoop::quit);
    eventLoop.exec();

    // 获取网页Body中的内容
    QByteArray bytes = pReply->readAll();
    qDebug() << bytes;

    return a.exec();
}

QT快速学习笔记——介绍窗体

QT中的窗体

在上一节中,我允诺会在本节介绍一个空白组件来作为窗体的“衬底”,也就是所有其他组件的父组件。

这个空白的组件的名字是QWidget,我不知道上一节中我贴出来的QT中类的结构简图你有没有仔细看。假如仔细看过应该会记得QPushButton以及其他所有的可视的窗体组件都是继承自QWidget的。

这应该很好理解,QWidget是所有可视组件的基类,拥有一个组件的基本属性——可以设置尺寸、设置标题、设置是否允许最大化及最小化等。

是不是被我说晕了?潜意识里认定明明只有窗体才拥有标题以及最大最小化这些按钮,为什么组件也可以?

这是因为在QT中其实并没有严格意义上的“窗体”,前面两节中咱们运行的程序展现出的窗体实际上都是组件本身——当一个不存在父组件的组件被调用show方法时,他就会显示出窗体的外边框。

下图是上一节中编写的程序,他的窗体外边框(标红部分)来自不存在父组件的button1对象,而button2对象的父组件是button1,所以button2的窗体外边框被隐藏了。

简而言之,QWidget拥有一个组件的基本能力,同时他默认不显示任何内容也不提供任何功能。

虽然QT中没有专门作为窗体的东西,但我们完全可以把QWidget这个组件当成纯粹的窗体来使用。

改进上一节的程序

非常简单,我们只需要把button1对象换成一个来自QWidget类的对象就好啦!

#include <QApplication>
#include <QPushButton>
#include <QWidget> // 引入QWidget组件

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    // 实例化一个QWidget对象window
    QWidget *window = new QWidget();
    // 注意这里将button2的父组件修改为window
    QPushButton *button2 = new QPushButton("Hi", window);

    // 这里修改要显示的组件为window
    window->show();

    return a.exec();
}

运行程序

还是很简陋,但是比上一节中那个两个按钮叠罗汉的程序可是好多了哈~

下一节中我将介绍QT组件的方法和属性,那时我们就可以利用新学的知识再度美化一下界面了。

QT快速学习笔记——在组件之上显示另一个组件

Parenting system介绍

这是我要介绍的QT程序的第一个重要的概念——Parenting system,原谅我不知道怎么翻译=_=。

这个机制的实现由QObject类提供,该类是QT中绝大部分组件的基类。QT中类的关系简图如下:

可以看到,我们在上一节中所使用的QPushButton组件也继承自QObject类,所以该组件也支持Parenting system机制。

代码演示

接下来我将通过一段代码来演示Parenting system机制的作用:

#include <QApplication>
#include <QPushButton>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    QPushButton *button1 = new QPushButton("Hello World");
    // 实例化button2,在构造函数的第二个参数中填入button1的地址
    // 此时button2就是button1的子组件
    QPushButton *button2 = new QPushButton("Hi", button1);

    // 只需要显示父组件,其下的子组件就会一同显示
    // 另外子组件也不需要单独调用delete(),他会随着父组件一起被释放
    button1->show();

    return a.exec();
}

运行后显示是这样的:

我们将窗体拉大,变成如下:

解读

可以看到,button2是显示在了button1的上面。这就很实际了,因为我们常见的Windows窗体都是在一个组件上叠加其他组件来显示的,例如:

每一个颜色的框框代表一个组件(没有把所有组件都标记出来,只摘取部分说明问题)。可以发现这样一个很复杂的窗体,就是一个面积很大的组件上面无限层次的叠加小组件来最终实现的。

而维护这种组件之间的父子关系的方法就是我们本篇文章所介绍的——Parenting system机制。

但是我们刚刚的程序还存在一个问题,就是父组件button1是一个按钮,试想一下如果把button1换成一个显示空白的组件是不是会更好一些?相关内容将在下一篇文章中详述。

QT快速学习笔记——编写第一个QT程序

刚新建完的QT项目包含五个文件,分别是:

  • main.cpp
  • mainwindow.cpp
  • mainwindow.h
  • mainwindow.ui
  • untitled.pro

尝试运行一下这个默认项目,运行的方法如下所示。

运行起来的程序:

是一个标题为MainWindow的空窗体,里面什么也没有。即便如此,这个窗体的代码对于初学者而言依然过于复杂,接下来我们删除掉窗体显示相关代码,只保留QT程序最基本的框架,随后再一点一点向里面填充内容。

删掉除了main.cpp和untitled.pro之外的其他文件。最后整个项目变成这样:

我们看main.cpp,这个文件的初始内容如下:

#include "mainwindow.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

下面简单修改一下这个文件,删除掉对mainwindow.h文件的引用和对MainWindow类的实例化相关代码。我们在之前看到的那个标题为MainWindow的窗体实际上就是这个类生成的。删除后的代码如下所示:

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    
    return a.exec();
}

现在看到的就是一个QT程序的最基本框架了。下面我们填充一个按钮到这个QT程序里,让按钮显示Hello World。

#include <QApplication>
#include <QPushButton> // QPushButton这个类提供按钮组件

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);

    // 实例化一个按钮,这里传给构造函数的值会被设置为按钮显示的文字
    // 除此之外还可以通过按钮类提供的setText方法来设置,后面会讲
    QPushButton *button = new QPushButton("Hello World"); 
    button->show(); // 调用show方法,显示组件。

    return a.exec();
}

之后运行程序就可以看到第一个QT程序跑起来了~他可能很简陋,但战略意义远大于战术意义。

QT快速学习笔记——开发环境部署及项目创建

QT开发环境安装

在下面的地址中找到适合你的操作系统的最新的安装包,下载之。

http://download.qt.io/archive/qt/

安装步骤就是一路点击“下一步”,直到出现如下界面的时候需要手工勾选一下,只勾选我标记出来的选项即可。

随后继续下一步,直到安装完成。

创建一个新项目

QT自带的开发工具叫QT Creator,是一个C++ IDE,你也可以使用微软的VS来开发QT,这里只介绍QT Creator。

QT安装好后QT Creator的快捷方式并不会创建在系统桌面上,需要你去开始菜单里找。

QT Creator启动后长这个样子:

创建项目的流程是:文件->新建文件或项目,然后调出如下界面,按照我选的选一下,之后一路无脑下一步。选的过程中可以简单看看界面的文字介绍了解一下这一步大概做了哪些工作,实在搞不懂的也别去纠结,该懂的以后自然会懂~

这样一个QT项目就创建好了,如下所示:

QT快速学习笔记——导语

前言

为什么称之为“快速学习笔记”?是因为我本人学东西就是一个“莽”字,啥也不会的时候就横冲直撞观其大略习其思想,瞅的差不多了直接融入到项目里应用,再在开发过程中对细节问题随时查缺补漏。当然这种学习方法并不适合所有人,有的自律性强耐得住寂寞的人就比较喜欢稳扎稳打,从基础开始一点点学。

哦,对了——你可能想问学习QT是不是要精通C++才行?我必须告诉你并不是,因为我本人就是个C++白痴(约等于什么也不会的那种),按照我的学习思路,C++的语法是到用到的时候才会去查手册的,并不会在学习QT时提前去学。当然,这样做的前提是,你必须拥有其他至少一门OOP编程语言的使用经验,否则还是先简单学习下C++再来看这个系列的文章吧。

QT简介

QT是一个C++的开发库,里面包括多线程、网络、GUI等等的模块。很多人把QT当成是一个GUI库其实是片面了,因为QT更像一个大杂烩,GUI只是其功能的一部分。

QT学习资料

除了本系列文章之外你还可以参考官方的学习手册。其实我是很推荐你看看官方手册的,因为我所写的是我按照我的理解方式总结出来的,也许你自己翻一遍官方手册会有不一样的感悟呢。英文不好没关系,我也是借着谷歌翻译看的=_=

QT官方入门手册(推荐初学者阅读)

https://wiki.qt.io/Qt_for_Beginners

QT官方完全开发手册(推荐做项目的时候随用随查)

https://doc.qt.io/qt-5/reference-overview.html

QT的应用案例

列举四个比较有名的项目

  • 国产Deepin操作系统的DDE桌面环境
  • KDE桌面环境
  • WPS办公软件
  • HBuilderX前端开发者工具

如何从YouTube上扒等效柱状投影的3D全景视频

导语

这段时间接了个项目,是给WordPress编写全景视频播放插件,客户的视频来源是YouTube。

但是我发现了一个很有意思的问题:

做过全景视频的同学应该一眼就看出来这种不是常见的视频格式。

事实上,这个是谷歌最新研发的 “等角度立方体贴图 (EAC) ”,这种格式目前只有YouTube可以正常解析。

当然,YouTube也提供了常见的“等效柱状投影”的全景视频,下面看获取方法。

YouTube上等效柱状投影全景视频的获取方法

核心思想就是改变浏览器的User Agent,让YouTube识别不出你所用的浏览器,这样他就不能确定你的浏览器是否支持解析“等角度立方体贴图”格式的视频,所以会返回最普通的“等效柱状投影”的视频。这样即便是直接展示视频的原始样式而不做全景播放处理也依然可以勉强观看。

效果如下:

修改User Agent可以使用各个浏览器的插件实现,比如FireFox可以使用User-Agent Switcher插件,将UserAgent修改为curl或者为空都可以。之后就可以使用常用的扒视频工具从这个网页上提取链接下载了。

as86提示as: error reading input的解决方法

这个问题大概率是所用的编辑器的锅,具体原理还没搞清楚,但是只要在Linux下用VIM重新打开保存一次就会好的。

Laravel-admin的Form中获取模型的方法

使用以下方法从URL中检索出模型的ID然后手工执行查询

request()->route()->parameters()['路由名'];

网站在Edge中乱码的解决方法

把网站的页面压缩算法从Brotli改为Gzip就好了……

记录一个Laravel orm访问器不起作用的问题

前言

今天码砖的时候发现laravel orm的访问器在用户前台死活都用不了,但是管理员后台却显示正常,一时间感到很迷乱……

问题原因

经过仔细观察,原来后台是先查询然后再通过手工调用对象属性的方式显示,前台则是查询后直接将结果返回给用户。

翻了下官方手册,orm的访问器并不是在数据库查询的时候起的作用,而是在调用相应的orm对象属性的时候才会生效。

所以,想用访问器的话就不要把查询到的orm对象直接返回给客户了,中间加一层处理就好了。

附加

根据访问器的工作原理可知,假如数据库查询时限制了输出的字段,那么在访问器中被限制输出的字段是没办法用$this->field或$this->attributes[‘field’]来索引的。

JB家的IDE在Linux下无法保存FTP/数据库的密码的解决方案

前言

事情的缘由是这样的——某天我开开心心的打开computer想要码一会砖,然后万恶的Phpstorm一直在询问我sftp的登录用户名和密码。即便我输入的时候选中了记住密码,在程序重启之后这货又会再次询问。之后发现Datagrip也犯了一样的毛病,一直询问我数据库登录信息……

这里必须要吐槽一下JB家IDE的脑残设计——明知道是缺少依赖软件包,为啥就不能直接在前端抛一个错误呢?搞得我还得一点一点的翻程序日志,我太难了=_=…

错误原因

错误原因很简单,就是前面说的:缺少依赖包。具体缺少的是一个叫做gnome-keyring的包,这个包是用来保存各种密码的。

解决方案

安装之。以下列出常见的三个系列发行版下的安装方式,其他发行版的同学使用各自的包管理器直接安装就行,这个包通常都是集成在官方源里的。

Ubuntu/Debian

sudo apt install gnome-keyring

Fedora/CentOS

sudo yum install gnome-keyring

Arch/Manjaro

sudo pacman -S gnome-keyring

整理下我的VIM配置文件

以下是我个人使用的VIM配置文件内容,记录在这里,方便日后查阅。

"打开语法高亮
syntax on
set t_Co=256
"打开文件类型检测功能
filetype on
"不同文件类型采用不同缩进
filetype indent on
"允许使用插件
filetype plugin on
filetype plugin indent on
"关闭vi模式
set nocp
"与windows共享剪贴板
set clipboard+=unnamed
"取消VI兼容,VI键盘模式不易用
set nocompatible
"显示行号, 或set number
set nu
"历史命令保存行数
set history=100
"当文件被外部改变时自动读取
set autoread
"取消自动备份及产生swp文件
set nobackup
set nowb
set noswapfile
"允许使用鼠标点击定位
set mouse=a
"允许区域选择
set selection=exclusive
set selectmode=mouse,key
"高亮光标所在行
set cursorline
"取消光标闪烁
set novisualbell
"总是显示状态行
set laststatus=2
"状态栏显示当前执行的命令
set showcmd
"标尺功能,显示当前光标所在行列号
set ruler
"粘贴时保持格式
set paste
"高亮显示匹配的括号
set showmatch
"在搜索的时候忽略大小写
set ignorecase
"高亮被搜索的句子
set hlsearch
"在搜索时,输入的词句的逐字符高亮(类似firefox的搜索)
set incsearch
"继承前一行的缩进方式,特别适用于多行注释
set autoindent
"为C程序提供自动缩进
set smartindent
"使用C样式的缩进
set cindent
"制表符为4
set tabstop=4
set expandtab
"%retab!
"统一缩进为4
set softtabstop=4
set shiftwidth=4
"允许使用退格键,或set backspace=2
set backspace=eol,start,indent
set whichwrap+=<,>,h,l
"取消换行
set nowrap
"启动的时候不显示那个援助索马里儿童的提示
set shortmess=atI
"在被分割的窗口间显示空白,便于阅读
set fillchars=vert:\ ,stl:\ ,stlnc:\
"光标移动到buffer的顶部和底部时保持3行距离, 或set so=3
set scrolloff=3
"设定默认解码
set fenc=utf-8
set fencs=utf-8,usc-bom,euc-jp,gb18030,gbk,gb2312,cp936
"设定字体
set guifont=Courier_New:h11:cANSI
set guifontwide=新宋体:h11:cGB2312
"设定编码
set enc=utf-8
set fileencodings=ucs-bom,utf-8,chinese
set langmenu=zh_CN.UTF-8
language message zh_CN.UTF-8
source $VIMRUNTIME/delmenu.vim
source $VIMRUNTIME/menu.vim
"自动补全
filetype plugin indent on
set completeopt=longest,menu
"自动补全命令时候使用菜单式匹配列表
set wildmenu
autocmd FileType ruby,eruby set omnifunc=rubycomplete#Complete
autocmd FileType python set omnifunc=pythoncomplete#Complete
autocmd FileType javascript set omnifunc=javascriptcomplete#CompleteJS
autocmd FileType html set omnifunc=htmlcomplete#CompleteTags
autocmd FileType css set omnifunc=csscomplete#CompleteCSS
autocmd FileType xml set omnifunc=xmlcomplete#CompleteTags
autocmd FileType java set omnifunc=javacomplete#Complet

35行C++代码实现时间戳转时间算法

程序逻辑概述

根据平年闰年计算规则,利用递归先求出年份。之后再根据固定的大小月规则和平年闰年下2月天数的不同求出月数,之后就剩下最简单的天数、小时、分钟和秒了。

这个程序我承认为了尽可能缩小行数,有的地方写的不是很规范~正可谓是为了减少行数而不择手段~

程序代码

注:去掉空行的话的确只有35行的,我并不是标题党!

#include <iostream>

int time_stamp = 28800, year = 1969, month = 0, day = 0, hour = -1, minute = -1, tmp;

auto isLeapYear = [] { return (year % 4 == 0 && year % 100 != 0) || (year % 100 == 0 && year % 400 == 0) || (year % 3200 == 0 && year % 172800 == 0); };

int getYear(int *ptr_time_stamp) {
	++year;
	if (*ptr_time_stamp >= (isLeapYear() ? 31622400: 31536000))
		getYear(&(*ptr_time_stamp -= (isLeapYear() ? 31622400 : 31536000)));

	return year;
}

int getMonth(int *ptr_time_stamp) {
	++month;
	if (month == 2 && (*ptr_time_stamp >= (isLeapYear() ? 2505600 : 2419200)))
		getMonth(&(*ptr_time_stamp -= (isLeapYear() ? 2505600 : 2419200)));
	else if ((month == 1 || month == 3 || month == 5 || month == 7 || month == 8 || month == 10 || month == 12) && *ptr_time_stamp >= 2592000)
		getMonth(&(*ptr_time_stamp -= 2678400));
	else if ((month == 4 || month == 6 || month == 9 || month == 11) && *ptr_time_stamp >= 2678400)
		getMonth(&(*ptr_time_stamp -= 2592000));

	return month;
}

int getTime(int *ptr_time_stamp, int *type, int second) {
	++*type;
	if (*ptr_time_stamp >= second) 
		getTime(&(*ptr_time_stamp -= second), type, second);

	return *type;
}

int main(void) {
	std::cin >> tmp;
	time_stamp += tmp;

	std::cout << getYear(&time_stamp) << "年";
	std::cout << getMonth(&time_stamp) << "月";
	std::cout << getTime(&time_stamp, &day, 86400) << "日 ";
	std::cout << getTime(&time_stamp, &hour, 3600) << "时";
	std::cout << getTime(&time_stamp, &minute, 60) << "分" << time_stamp << "秒";

	return 0;
}

Guzzle请求URL时附带Cookie

就不长篇大论了,直接贴示例代码吧

$client = new \GuzzleHttp\Client();

$jar = new \GuzzleHttp\Cookie\CookieJar();
$domain = 'www.baidu.com';
$cookies = [
    'sessionid' => '123456',
]; 
$cookieJar = $jar->fromArray($cookies, $domain);

return $client->request('GET', 'www.baidu.com', ['cookies' => $cookieJar]);