网站怎么做展现量,哈尔滨关键词搜索排名,网站建设 爱诚科技,wordpress怎么设置用户登陆基于脚手架微服务的视频点播系统-客户端业务逻辑处理第一部分一.RESTful API二.启动页-临时用户登录接口三.首页-获取全部视频列表四.首页-获取分类#xff0c;标签#xff0c;搜索视频列表#xff0c;顺带实现置顶与刷新以及获取下一页视频的方法五.videoBox页-下载图片接口…
基于脚手架微服务的视频点播系统-客户端业务逻辑处理第一部分一.RESTful API二.启动页-临时用户登录接口三.首页-获取全部视频列表四.首页-获取分类标签搜索视频列表顺带实现置顶与刷新以及获取下一页视频的方法五.videoBox页-下载图片接口六.videoBox页-更新视频封面七.videoBox页-更新上传视频的用户头像前面的文章我们已经实现完毕客户端的界面播放器以及数据中心模块与通信模块的预备工作。那么接下来我们便对客户端的业务逻辑进行实现。一.RESTful API
本项目采用RESTful API是⼀种遵循REST架构风格基于HTTP协议的应用程序接口设计规范它提供了⼀种通过标准化的操作和资源访问模式进行客户端与服务器通信的⽅式。 REST是Representational State Transfer的缩写翻译过来就是表现层状态转化。 资源(Resource) RESTful API 中的每⼀个对象、实体或数据都被抽象为⼀个资源。例如用户、文章等都可以作为资源。每个资源都通过⼀个唯⼀的 URI 统⼀资源标识符标识。 URI(统⼀资源标识) URI是用于标识资源的地址。 RESTful API 中通常使用 URL 统⼀资源定位符作为 URI 。例如 /users/123 表示 id 为 123 的用户资源 /posts/456 表示 id 为 456 的⽂章资源 HTTP动作(HTTP Methods) RESTful API 依赖于 HTTP 协议的常见方法来对资源进⾏操作每个 HTTP 方法对应不同的操作 GET 获取服务器上的资源。 POST 在服务器上创建新的资源。 PUT 更新服务器的上的资源。 DELETE 删除服务器上的资源。 无状态 RESTful API 是无状态的。每个请求都应该是独立的服务器不会在请求之间保存客户端的状态。 表现层状态转移(Representational State Transfer) 资源的表现形式可以是 JSON 、 XML 、 HTML 等格式通常 RESTful API 使用 JSON 作为数据交换格式因为它轻量且易于解析。 RESTful API的优点可以总结如下: 1.简单与统一接口使用标准的 HTTP 方法GET, POST, PUT, DELETE 等来执行操作设计直观学习和使用成本低。 2.可伸缩性无状态特性每次请求都包含所有必要信息使得服务器无需保存客户端状态更容易通过增加服务器来扩展系统性能。 3.松耦合与独立性客户端和服务器是分离的只要接口不变可以独立地发展和更换技术栈。 4.通用性与互操作性基于 HTTP 协议可以被任何支持 HTTP 的客户端浏览器、移动应用、IoT 设备等轻松调用语言无关。 我们之前开发的测试接口如hello和ping采用的就是RESTful API设计这种标准化接口几乎无需额外学习就能快速上手使用。所以我们使用它来实现客户端背后的业务逻辑。
二.启动页-临时用户登录接口
在进行项目实现之前我们就已经规定好了请求的URL以及请求的参数字段: 请求URL:
POST /HttpService/tempLogin 请求参数(客户端):
字段名称字段类型字段说明requestIdstring请求ID
示例:
{ requestId: string
}返回响应:200 OK
字段名称字段类型字段说明requestIdstring请求IDerrorCodeinteger错误码0-成功errorMsgstring错误信息resultstring响应结果sessionIdstring客户端会话ID
示例:
{requestId: string,errorCode: 0,errorMsg: ,result: {sessionId: string}
}那么首先我们在dataCenter.h之前我们给hello与ping测试接口定义异步请求方法以及完成信号的相同位置定好临时登录请求的异步方法与信号:
/////////////datacenter.h
public://临时登录请求void tempLoginAsync();
signals://临时登录成功之后发射的信号void tempLoginDone();
/////////////datacenter.cpp
void DataCenter::tempLoginAsync()
{netClient.tempLogin();
}接下来我们去netclient类中根据我们之前定好的请求接口与URL实现tempLogin方法:
//netclient.h
namespace model{
class DataCenter;
}namespace netclient{
class NetClient : public QObject
{Q_OBJECT
public:void tempLogin();//临时登录请求
};
}//end netclient
//netclient.cpp
void NetClient::tempLogin()
{//请求报文的bodyQJsonObject reqbody;reqbody[requestId] makeRequestId();QNetworkReply* reply sendReqToHttpServer(/HttpService/tempLogin,reqbody);//异步处理服务端的responseconnect(reply,QNetworkReply::finished,this,[](){bool ok true;QString reason;QJsonObject respBody handleRespFromHttpServer(reply,ok,reason);if(!ok){//说明reply有问题打印错误信息并返回LOG() reason;reply-deleteLater();return;}reply-deleteLater();//解析成功处理响应信息QJsonObject result respBody[result].toObject();//设置sessionIddataCenter-setSessionId(result[sessionId].toString());LOG() sessionId设置成功- result[sessionId].toString();emit dataCenter-tempLoginDone();});
}对于session与cookie不了解的读者可以移步至我之前写过的一篇文章: HTTP协议解析Session/Cookie机制与HTTPS加密体系的技术演进(二) 这里我们就不再深入介绍了。当服务端收到临时登录请求后会向客户端发送一个sessionId。由于HTTP协议本身是无状态的为了保持后续通信的有状态特性客户端需要保存这个sessionId并在之后与服务端的所有交互中都携带该sessionId。我们在dataCenter中添加一个成员变量记录该sessionId并添加setSessionId方法。 客户端请求处理完毕接下来我们在mockServer模拟实现一个简单的响应逻辑: 首先新增枚举常量UserType标识本次通信对象的身份-默认为临时登录用户:
enum UserType{SuperAdmin 1, // 超级管理员Admin 2, // 管理员User 3, // 普通⽤⼾TempUser 4 // 临时⽤⼾
};接下来定义实现临时登录请求的响应接口同时根据我们之前规定好的URL设置路由:
//mockserver.h
class MockServer : public QWidget
{Q_OBJECTpublic:QHttpServerResponse tempLogin(const QHttpServerRequest request);//临时登录请求接口
private:UserType userType TempUser;//默认为临时用户
};
//mockserver.cpp
bool MockServer::init()
{//监听指定端口quint16 port 8080;quint16 ret httpServer.listen(QHostAddress::Any,port);//设置路由httpServer.route(/HttpService/tempLogin,[](const QHttpServerRequest request){return tempLogin(request);});return ret 8080;
}
QHttpServerResponse MockServer::tempLogin(const QHttpServerRequest request)
{//构建body信息QJsonObject reqData QJsonDocument::fromJson(request.body()).object();LOG() [tempLogin] 收到 tempLogin 请求, requestId reqData[requestId].toString();userType TempUser;//设置当前用户为临时用户QJsonObject respData;respData[requestId] reqData[requestId].toString();respData[errorCode] 0;respData[errorMsg] ;QJsonObject result;result[sessionId] QUuid::createUuid().toString().sliced(25,12);//构建sessionIdrespData[result] result;//构建http响应报文QHttpServerResponse response(QJsonDocument(respData).toJson(),QHttpServerResponse::StatusCode::Ok);response.setHeader(Content-Type,application/json;charsetutf-8);return response;
}当客户端收到服务端的响应时会发射一个名为tempLoginDone的信号。我们这里的处理方法是在启动页调用其异步处理方法并处理该信号-当响应处理完毕后再显示首页界面:
//startpage.h
class StartPage : public QDialog
{Q_OBJECT
public:void tempLoginDone();
private:bool isLogin false;//默认登录状态为false
};
//startpage.cpp
StartPage::StartPage(QWidget *parent): QDialog(parent)
{//绑定临时登录的信号槽auto dataCenter model::DataCenter::getInstance();connect(dataCenter,model::DataCenter::tempLoginDone,this,StartPage::tempLoginDone);
}void StartPage::startUp()
{QTimer* timer new QTimer(this);auto dataCenter model::DataCenter::getInstance();dataCenter-tempLoginAsync();//发送登录请求timer-setSingleShot(true);//允许重复触发定时器connect(timer,QTimer::timeout,this,[](){if(isLogin){timer-stop();close();timer-deleteLater();}//当登录成功之后再关闭启动页面});timer-start(2000);
}void StartPage::tempLoginDone()
{//设置确认登录状态isLogin true;
}接下来我们启动客户端与服务端就能看到如下效果:
三.首页-获取全部视频列表
刚进⼊首页时在用户还没有进行任何选择情况下默认先获取所有视频列表给用户⼀个默认的视频信息页面默认情况下⼀次性获取20个视频这些视频按照播放量和点赞量之后降序排列。 请求URL POST /HttpService/allVideoList 请求参数(客户端):
字段名称字段类型字段说明requestIdstring请求IDsessionIdstring客⼾端会话IDpageIndexinteger⻚码pageCountinteger每⻚条⽬数量
示例:
{requestId: string,sessionId: string,pageIndex: 0,pageCount: 0
}针对单个分类下视频数量庞大的情况我们采用分页加载机制来优化性能。系统默认每页加载20个视频数据当用户滚动至页面底部时自动触发下一页的加载。其中pageCount参数控制每页加载的视频数量pageIndex参数则指定当前加载的页码。针对单个分类下视频数量庞大的情况我们采用分页加载机制来优化性能。系统默认每页加载20个视频数据当用户滚动至页面底部时自动触发下一页的加载。其中pageCount参数控制每页加载的视频数量pageIndex参数则指定当前加载的页码。
返回响应:200 OK
字段名称字段类型字段说明requestIdstring请求IDerrorCodeinteger错误码0-成功errorMsgstring错误信息resultobject响应结果totalCountinteger总数量videoListarray视频列表videoIdstring视频IDuserIdstring所属用户IDuserAvatarIdstring用户头像IDnicknamestring所属用户名称videoFiledstring视频文件IDphotoFiledstring封面文件IDlikeCountinteger点赞数量playCountinteger播放数量videoSizeinteger视频大小videoDescstring视频简介videoTitlestring视频标题videoDurationinteger视频时长videoUpTimestring视频上架时间
示例:
{requestId: string,errorCode: 0,errorMsg: ,result: {totalCount:0videoList: [{videoId: string,userId: string,userAvatarId:string,nickname: string,videoFileId: string,photoFileId: string,likeCount: 0,playCount: 0,videoSize: 0,videoDesc: string,videoTitle: string,videoDuration: 0,videoUpTime: string}]}
}**注意videoList是⼀个数组内部存放返回给客⼾端的单个视频JSON对象。**根据响应中单个视频中包含的信息除此之外客⼾端在解析响应结果时需要将所有视频信息组织起来所以定义描述视频信息的结构以及管理这些视频信息的视频列表实现如下
//data.h
////////////////////////////
/////////单条视频的信息结构
////////////////////////////
class VideoInfo{
public:QString videoId;QString userId;QString userAvatarId;QString nickname;QString videoFileId;QString photoFileId;int64_t likeCount;int64_t playCount;int64_t videoSize;QString videoDesc;QString videoTitle;int64_t videoDuration;QString videoUpTime;//加载json对象到单个VideoInfo中void loadJsonResultToVideoInfo(const QJsonObject result);
};////////////////////////////
/////////视频列表的信息结构
////////////////////////////
class VideoList{
public:VideoList();// 设置或获取下⼀次要获取视频⻚⻚号void setPageIndex(int64_t pageIndex);int64_t getPageIndex()const;// 获取视频列表中实际视频个数int64_t getVideoCount()const;// 设置或获取特定条件下(⽐如分类)总视频个数视频审核⻚⾯⽤来计算分⻚器上总⻚数void setVideoTotalCount(int64_t videoTotalCount);int64_t getVideoTotalCount()const;// 往视频列表中添加视频void addVideo(const VideoInfo videoInfo);// 获取排序后的视频列表-按照点赞量播放量及观看量的总和进行排序const QListVideoInfo getVideoList()const;// 将列表中的所有视频清空void clearVideoList();const static int PAGE_COUNT 20;QListVideoInfo videoInfos; // ⽬前从服务器获取下来的视频数据int64_t pageIndex; // ⻚⾯索引int64_t videototalCount;// ⽤视频总数和PAGE_COUNT能计算出该分类下总共有多少⻚视频
};//data.cpp
////////////////////////////
/////////单条视频的信息结构
////////////////////////////
void VideoInfo::loadJsonResultToVideoInfo(const QJsonObject result)
{videoId result[videoId].toString();userId result[userId].toString();userAvatarId result[userAvatarId].toString();nickname result[nickname].toString();videoFileId result[videoFileId].toString();photoFileId result[photoFileId].toString();likeCount result[likeCount].toInteger();playCount result[playCount].toInteger();videoSize result[videoSize].toInteger();videoDesc result[videoDesc].toString();videoTitle result[videoTitle].toString();videoDuration result[videoDuration].toInteger();videoUpTime result[videoUpTime].toString();
}////////////////////////////
/////////视频列表的信息结构
////////////////////////////
VideoList::VideoList():pageIndex(1),videototalCount(0)
{}void VideoList::setPageIndex(int64_t pageIndex)
{this-pageIndex pageIndex;
}int64_t VideoList::getPageIndex() const
{return pageIndex;
}int64_t VideoList::getVideoCount() const
{return videoInfos.size();
}void VideoList::setVideoTotalCount(int64_t videoTotalCount)
{this-videototalCount videoTotalCount;
}int64_t VideoList::getVideoTotalCount() const
{return videototalCount;
}void VideoList::addVideo(const VideoInfo videoInfo)
{videoInfos.append(videoInfo);
}const QListVideoInfo VideoList::getVideoList() const
{return videoInfos;
}void VideoList::clearVideoList()
{videoInfos.clear();pageIndex 1;videototalCount 0;
}因为之后单条视频的信息是要设置到videobox中的所以我们需要在videobox中处理videoInfo提供的信息:
//videobox.h
namespace Ui {
class VideoBox;
}class VideoBox : public QWidget
{Q_OBJECTpublic:explicit VideoBox(model::VideoInfo videoInfo,QWidget *parent nullptr);//根据videoInfo更新视频块信息void updateVideoUi();//设置点赞数播放数为标准的显示格式QString setCount(int64_t count);//设置时间为标准的显示格式QString setTime(int64_t time);
private:Ui::VideoBox *ui;PlayerPage* videoPlayer;//当前视频的播放信息model::VideoInfo videoInfo;
};//videobox.cpp
#include videobox.h
#include ui_videobox.h
#include util.h
#include QDir
#include QStringVideoBox::VideoBox(model::VideoInfo videoInfo,QWidget *parent): QWidget(parent), ui(new Ui::VideoBox), videoInfo(videoInfo)
{//根据videoInfo更新界面updateVideoUi();
}void VideoBox::updateVideoUi()
{//设置ui上需要展示的所有视频信息ui-userNikeName-setText(videoInfo.nickname);ui-likeNum-setText(setCount(videoInfo.likeCount));ui-playNum-setText(setCount(videoInfo.playCount));ui-videoTitle-setText(videoInfo.videoTitle);ui-videoDuration-setText(setTime(videoInfo.videoDuration));ui-lodeupTime-setText(videoInfo.videoUpTime);
}QString VideoBox::setCount(int64_t count)
{if(count 10000){return QString(%1万).arg(QString::number(count / 10000.0));}return QString::number(count);
}QString VideoBox::setTime(int64_t time)
{QString res;if(time/60/60){res QString(%1:).arg(time/60/60,2,10,QChar(0));}res QString(%1:%2).arg(time/60%60,2,10,QChar(0)).arg(time%60,2,10,QChar(0));return res;
}接下来定义实现异步接口与完成信号-同时当服务端返回响应报文时将报文中的视频信息添加到DataCenter类中管理起来:
//datacenter.h
class DataCenter : public QObject
{Q_OBJECT
public:void deleteDataCenter();const QString getSessionId();//获取本次登录服务端返回的sessionIdvoid setSessionId(const QString sessionId);//设置本次登录的sessionIdvoid setHomeVideoList(const QJsonObject result);//通过网络获取的json对象更新当前首页的视频VideoList* getHomeVideoListPtr();//获取首页视频列表指针//根据jsonresult设置视频列表信息void setVideoList(const QJsonObject videoListJsonObj);
private://本次登录的sessionIdQString sessionId;//管理首页视频的视频列表VideoList* homeVideoList nullptr;public://获取当前全部视频列表void getAllVideoListAsync();
signals:void getAllVideoListDone();
};
//datacenter.cpp
#include datacenter.hnamespace model{const QString DataCenter::getSessionId()
{return sessionId;
}void DataCenter::setSessionId(const QString sessionId)
{this-sessionId sessionId;
}VideoList *DataCenter::getHomeVideoListPtr()
{if(homeVideoList nullptr){//初始化首页视频列表homeVideoList new VideoList();}return homeVideoList;
}void DataCenter::setVideoList(const QJsonObject result)
{// 保证videoList对象先构造了getHomeVideoListPtr();model::VideoList* homeVideoList getHomeVideoListPtr();homeVideoList-setVideoTotalCount(result[totalCount].toInteger());QJsonArray videoList result[videoList].toArray();for(int i 0;i videoList.size();i){QJsonObject videoInfoObj videoList[i].toObject();model::VideoInfo videoInfo;videoInfo.loadJsonResultToVideoInfo(videoInfoObj);homeVideoList-addVideo(videoInfo);}
}DataCenter::~DataCenter()
{if(kindsAndTags)delete kindsAndTags;kindsAndTags nullptr;if(homeVideoList)delete homeVideoList;homeVideoList nullptr;
}void DataCenter::getAllVideoListAsync()
{netClient.getAllVideoList();
}
}在netclient类中实现getAllVideoList客户端请求方法:
//netclient.h
namespace netclient{
class NetClient : public QObject
{Q_OBJECT
public:void getAllVideoList();//全部视频列表获取请求
};
//netclient.cpp
void NetClient::getAllVideoList()
{/** requestId: string,* sessionId: string,* pageIndex: 0,* pageCount: 0*///请求报文的bodyQJsonObject reqbody;reqbody[requestId] makeRequestId();reqbody[sessionId] dataCenter-getSessionId();reqbody[pageIndex] dataCenter-getHomeVideoListPtr()-getPageIndex();reqbody[pageCount] model::VideoList::PAGE_COUNT;QNetworkReply* reply sendReqToHttpServer(/HttpService/allVideoList,reqbody);//异步处理服务端的responseconnect(reply,QNetworkReply::finished,this,[](){bool ok true;QString reason;QJsonObject respBody handleRespFromHttpServer(reply,ok,reason);if(!ok){//说明reply有问题打印错误信息并返回LOG() reason;reply-deleteLater();return;}reply-deleteLater();//解析成功处理响应信息QJsonObject result respBody[result].toObject();dataCenter-setVideoList(result);emit dataCenter-getAllVideoListDone();});
}客户端处理完毕接下来我们在mockServer中构造一批假数据作为响应返回给客户端:
//mockserver.h
public:QHttpServerResponse getAllVideoList(const QHttpServerRequest request);//获取全部视频列表接口
//mockserver.cpp
/** result: {totalCount:0videoList: [{videoId: string,userId: string,userAvatarId:string,nickname: string,videoFileId: string,photoFileId: string,likeCount: 0,playCount: 0,videoSize: 0,videoDesc: string,videoTitle: string,videoDuration: 0,videoUpTime: string}]
}*/QHttpServerResponse MockServer::getAllVideoList(const QHttpServerRequest request)
{//构建body信息QJsonObject reqData QJsonDocument::fromJson(request.body()).object();LOG() [getAllVideoList] 收到 getAllVideoList 请求, requestId reqData[requestId].toString() sessionId reqData[sessionId].toString();//获取页号与获取视频数目int pageCount reqData[pageCount].toInt();int64_t pageIndex reqData[pageIndex].toInteger();QJsonObject respData;respData[requestId] reqData[requestId].toString();respData[errorCode] 0;respData[errorMsg] ;QJsonObject result;//总视频个数设置为100result[totalCount] 100;QJsonArray videoList;//构造假数据int videoId 10000;int userId 10000;int resourceId 1000;int fileId 10000;int maxVideoNum qMin(100,pageCount * pageIndex);for(int i pageCount * (pageIndex - 1); i maxVideoNum; i){QJsonObject videoJsonObj;videoJsonObj[videoId] QString::number(videoId);videoJsonObj[userId] QString::number(userId);videoJsonObj[nickname] 咻114514;videoJsonObj[userAvatarId] QString::number(resourceId);videoJsonObj[photoFileId] QString::number(resourceId);videoJsonObj[videoFileId] QString::number(fileId);videoJsonObj[likeCount] 9867;videoJsonObj[playCount] 2105000;videoJsonObj[videoSize] 10240;videoJsonObj[videoDesc] 『Bad Apple!! feat.SEKAI』\n25時、ナイトコードで。 × 初音ミク\n作詞Haruka-作曲ZUN上海アリス幻樂団-編曲ビートまりお×まろん、まらしぃ、Masayoshi Minoshima;videoJsonObj[videoTitle] 【25時、ナイトコードで。 × 初音ミク】Bad Apple!!【2DMV世界计划 × 东方project 联动收录曲】;videoJsonObj[videoDuration] 231;videoJsonObj[videoUpTime] 2024-05-04 17:11:11;videoList.append(videoJsonObj);}result[videoList] videoList;respData[result] result;//构建http响应报文QHttpServerResponse response(QJsonDocument(respData).toJson(),QHttpServerResponse::StatusCode::Ok);response.setHeader(Content-Type,application/json;charsetutf-8);return response;
}收到视频信息之后我们需要在首页进行视频的更新那么就应该在首页类中处理getAllVideoListDone信号然后将datacenter中记录的所有videoInfo更新到首页中去:
//homepagewidget.h
#ifndef HOMEPAGEWIDGET_H
#define HOMEPAGEWIDGET_H#include QWidget
#include QPushButtonnamespace Ui {
class HomePageWidget;
}//记录当前获取视频的方式
enum VideoListStyle{AllStyle, // 所有视频列表KindStyle, // 分类视频列表TagStyle, // 标签视频列表SearchStyle // 搜索视频列表
};class HomePageWidget : public QWidget
{Q_OBJECTpublic://链接网络部分的所有信号槽函数void connectSingalsAndSlotsNet();//更新首页当前的视频void updateShowVideos();
};#endif // HOMEPAGEWIDGET_H
//homepagewidget.cpp
#include homepagewidget.h
#include ui_homepagewidget.h
#include videobox.h
#include util.h
#include ./model/datacenter.h
#include QScrollBarHomePageWidget::HomePageWidget(QWidget *parent): QWidget(parent), ui(new Ui::HomePageWidget)
{ui-setupUi(this);connectSingalsAndSlotsNet();initVideos();
}void HomePageWidget::initVideos()
{//设置视频块对齐规则ui-videoGLayout-setAlignment(Qt::AlignLeft | Qt::AlignTop);auto dataCenter model::DataCenter::getInstance();dataCenter-getAllVideoListAsync();
}void HomePageWidget::connectSingalsAndSlotsNet()
{auto dataCenter model::DataCenter::getInstance();//处理视频信息更新好的消息connect(dataCenter,model::DataCenter::getAllVideoListDone,this,HomePageWidget::updateShowVideos);
}void HomePageWidget::updateShowVideos()
{//更新视频到首页auto dataCenter model::DataCenter::getInstance();auto VideoList dataCenter-getHomeVideoListPtr()-getVideoList();int count ui-videoGLayout-count();for(int i count;i VideoList.size();i){VideoBox* video new VideoBox(VideoList[i]);ui-videoGLayout-addWidget(video,i/4,i%4);}LOG() 视频更新完毕首页中此时一共有 : ui-videoGLayout-count() 个视频;//更新视频页数dataCenter-getHomeVideoListPtr()-pageIndex;
}四.首页-获取分类标签搜索视频列表顺带实现置顶与刷新以及获取下一页视频的方法
由于点击分类标签之后需要更新dataCenter中的视频列表所以需要先将原有视频列表清空这里我们在首页类中新增清空视频列表的方法:
//homepagewidget.cpp
void HomePageWidget::clearShowVideos()
{//重置滑动条位置ui-videoScroll-verticalScrollBar()-setValue(0);//清空dataCenter中的videoListauto dataCenter model::DataCenter::getInstance();dataCenter-getHomeVideoListPtr()-clearVideoList();//清空界面中的所有视频QLayoutItem* VideoItem;while ((VideoItem ui-videoGLayout-takeAt(0)) ! nullptr) {// 先删除item中的widget如果有的话if (QWidget* widget VideoItem-widget()) {delete widget;}// 然后删除item本身delete VideoItem;}
}由于获取全部视频列表与分类视频列表标签视频列表以及搜索视频列表的实现机制几乎类似同时他们的响应字段均相同请求字段后三者仅比前者多了一处不同所以接下来我们实现过程不再赘述对于这三者直接给出实现的方法。 获取分类视频列表的接口描述如下 请求URL: 对于分类视频列表: POST /HttpService/allVideoList 对于标签视频列表: POST /HttpService/tagVideoList 对于搜索视频列表: POST /HttpService/keyVideoList 请求参数(客户端):
字段名称字段类型字段说明requestIdstring请求IDsessionIdstring客⼾端会话IDvideoTypeIdinteger视频分类类型pageIndexinteger⻚码pageCountinteger每⻚条⽬数量
对于标签视频列表第三个字段为: videoTag integer 视频标签类型 对于搜索视频列表第三个字段为: searchKey string 搜索关键字
示例:
{requestId: string,sessionId: string,videoTypeId: 0,pageIndex: 0,pageCount: 0
}对于标签视频列表第三个字段为: “videoTag” : 0 对于搜索视频列表第三个字段为: “searchKey” : “string”
返回响应:200 OK
字段名称字段类型字段说明requestIdstring请求IDerrorCodeinteger错误码0-成功errorMsgstring错误信息resultobject响应结果totalCountinteger总数量videoListarray视频列表videoIdstring视频IDuserIdstring所属用户IDuserAvatarIdstring用户头像IDnicknamestring所属用户名称videoFiledstring视频文件IDphotoFiledstring封面文件IDlikeCountinteger点赞数量playCountinteger播放数量videoSizeinteger视频大小videoDescstring视频简介videoTitlestring视频标题videoDurationinteger视频时长videoUpTimestring视频上架时间
示例:
{requestId: string,errorCode: 0,errorMsg: ,result: {totalCount:0videoList: [{videoId: string,userId: string,userAvatarId:string,nickname: string,videoFileId: string,photoFileId: string,likeCount: 0,playCount: 0,videoSize: 0,videoDesc: string,videoTitle: string,videoDuration: 0,videoUpTime: string}]}
}datacenter中新增这三者的完成信号与异步请求方法:
//datacenter.h
namespace model{class DataCenter : public QObject
{Q_OBJECT
public://获取当前分类视频列表void getAlltypeVideoListAsync(int classifyId);//获取当前标签视频列表void getAlltagVideoListAsync(int tagId);//获取搜索视频列表void getAllkeyVideoListAsync(const QString searchKey);
signals:void getAlltypeVideoListDone();//获取标签下的所有视频成功void getAlltagVideoListDone();//获取对应搜索key下的所有视频成功void getAllkeyVideoListDone();
};}
//datacenter.cpp
#include datacenter.hnamespace model{void DataCenter::getAlltypeVideoListAsync(int classifyId)
{netClient.getAlltypeVideoList(classifyId);
}void DataCenter::getAlltagVideoListAsync(int tagId)
{netClient.getAlltagVideoList(tagId);
}void DataCenter::getAllkeyVideoListAsync(const QString searchKey)
{netClient.getAllkeyVideoList(searchKey);
}
}在netclient中实现对应方法:
//netclient.h
#ifndef NETCLIENT_H
#define NETCLIENT_H#include QObject
#include QNetworkAccessManagernamespace model{
class DataCenter;
}namespace netclient{
class NetClient : public QObject
{Q_OBJECT
public:void getAlltypeVideoList(int classifyId);//当前分类下视频列表的获取请求void getAlltagVideoList(int tagId);//当前标签下视频列表的获取请求void getAllkeyVideoList(const QString searchKey);//当前搜索key下视频列表的获取请求
};
}//end netclient#endif // NETCLIENT_H//netclient.cpp
void NetClient::getAlltypeVideoList(int classifyId)
{/** requestId: string,* sessionId: string,* videoTypeId: 0,* pageIndex: 0,* pageCount: 0*///请求报文的bodyQJsonObject reqbody;reqbody[requestId] makeRequestId();reqbody[sessionId] dataCenter-getSessionId();reqbody[videoTypeId] classifyId;reqbody[pageIndex] dataCenter-getHomeVideoListPtr()-getPageIndex();reqbody[pageCount] model::VideoList::PAGE_COUNT;QNetworkReply* reply sendReqToHttpServer(/HttpService/typeVideoList,reqbody);//异步处理服务端的responseconnect(reply,QNetworkReply::finished,this,[](){bool ok true;QString reason;QJsonObject respBody handleRespFromHttpServer(reply,ok,reason);if(!ok){//说明reply有问题打印错误信息并返回LOG() reason;reply-deleteLater();return;}reply-deleteLater();//解析成功处理响应信息QJsonObject result respBody[result].toObject();dataCenter-setVideoList(result);emit dataCenter-getAlltypeVideoListDone();});
}void NetClient::getAlltagVideoList(int tagId)
{/** requestId: string,* sessionId: string,* videoTag: 0,* pageIndex: 0,* pageCount: 0*///请求报文的bodyQJsonObject reqbody;reqbody[requestId] makeRequestId();reqbody[sessionId] dataCenter-getSessionId();reqbody[videoTag] tagId;reqbody[pageIndex] dataCenter-getHomeVideoListPtr()-getPageIndex();reqbody[pageCount] model::VideoList::PAGE_COUNT;QNetworkReply* reply sendReqToHttpServer(/HttpService/tagVideoList,reqbody);//异步处理服务端的responseconnect(reply,QNetworkReply::finished,this,[](){bool ok true;QString reason;QJsonObject respBody handleRespFromHttpServer(reply,ok,reason);if(!ok){//说明reply有问题打印错误信息并返回LOG() reason;reply-deleteLater();return;}reply-deleteLater();//解析成功处理响应信息QJsonObject result respBody[result].toObject();dataCenter-setVideoList(result);emit dataCenter-getAlltagVideoListDone();});
}void NetClient::getAllkeyVideoList(const QString searchKey)
{/** requestId: string,* sessionId: string,* searchKey: string,* pageIndex: 0,* pageCount: 0*///请求报文的bodyQJsonObject reqbody;reqbody[requestId] makeRequestId();reqbody[sessionId] dataCenter-getSessionId();reqbody[searchKey] searchKey;reqbody[pageIndex] dataCenter-getHomeVideoListPtr()-getPageIndex();reqbody[pageCount] model::VideoList::PAGE_COUNT;QNetworkReply* reply sendReqToHttpServer(/HttpService/keyVideoList,reqbody);//异步处理服务端的responseconnect(reply,QNetworkReply::finished,this,[](){bool ok true;QString reason;QJsonObject respBody handleRespFromHttpServer(reply,ok,reason);if(!ok){//说明reply有问题打印错误信息并返回LOG() reason;reply-deleteLater();return;}reply-deleteLater();//解析成功处理响应信息QJsonObject result respBody[result].toObject();dataCenter-setVideoList(result);emit dataCenter-getAllkeyVideoListDone();});
}在mockServer构造假数据响应给客户端:
//mockserver.h
#ifndef MOCKSERVER_H
#define MOCKSERVER_H#include QWidget
#include QHttpServerQT_BEGIN_NAMESPACE
namespace Ui {
class MockServer;
}
QT_END_NAMESPACEenum UserType{SuperAdmin 1, // 超级管理员Admin 2, // 管理员User 3, // 普通⽤⼾TempUser 4 // 临时⽤⼾
};class MockServer : public QWidget
{Q_OBJECTpublic:QHttpServerResponse getAlltypeVideoList(const QHttpServerRequest request);//获取当前分类下所有视频的接口QHttpServerResponse getAlltagVideoList(const QHttpServerRequest request);//获取当前标签下所有视频的接口QHttpServerResponse getAllkeyVideoList(const QHttpServerRequest request);//获取当前搜索文本对应的所有视频的接口
};
#endif // MOCKSERVER_H//mockserver.cpp
QHttpServerResponse MockServer::getAlltypeVideoList(const QHttpServerRequest request)
{//构建body信息QJsonObject reqData QJsonDocument::fromJson(request.body()).object();LOG() [getAlltypeVideoList] 收到 getAlltypeVideoList 请求, requestId reqData[requestId].toString() sessionId reqData[sessionId].toString();LOG() videoTypeId reqData[videoTypeId].toInt();//获取页号与获取视频数目int pageCount reqData[pageCount].toInt();int64_t pageIndex reqData[pageIndex].toInteger();QJsonObject respData;respData[requestId] reqData[requestId].toString();respData[errorCode] 0;respData[errorMsg] ;QJsonObject result;//总视频个数设置为100result[totalCount] 100;QJsonArray videoList;//构造假数据int videoId 20000;int userId 20000;int resourceId 2000;int fileId 20000;int maxVideoNum qMin(100,pageCount * pageIndex);for(int i pageCount * (pageIndex - 1); i maxVideoNum; i){QJsonObject videoJsonObj;videoJsonObj[videoId] QString::number(videoId);videoJsonObj[userId] QString::number(userId);videoJsonObj[nickname] 咻114514;videoJsonObj[userAvatarId] QString::number(resourceId);videoJsonObj[photoFileId] QString::number(resourceId);videoJsonObj[videoFileId] QString::number(fileId);videoJsonObj[likeCount] 9867;videoJsonObj[playCount] 2105000;videoJsonObj[videoSize] 10240;videoJsonObj[videoDesc] 『Bad Apple!! feat.SEKAI』\n25時、ナイトコードで。 × 初音ミク\n作詞Haruka-作曲ZUN上海アリス幻樂団-編曲ビートまりお×まろん、まらしぃ、Masayoshi Minoshima;videoJsonObj[videoTitle] 【25時、ナイトコードで。 × 初音ミク】Bad Apple!!【2DMV世界计划 × 东方project 联动收录曲】;videoJsonObj[videoDuration] 231;videoJsonObj[videoUpTime] 2024-05-04 17:11:11;videoList.append(videoJsonObj);}result[videoList] videoList;respData[result] result;//构建http响应报文QHttpServerResponse response(QJsonDocument(respData).toJson(),QHttpServerResponse::StatusCode::Ok);response.setHeader(Content-Type,application/json;charsetutf-8);return response;
}QHttpServerResponse MockServer::getAlltagVideoList(const QHttpServerRequest request)
{//构建body信息QJsonObject reqData QJsonDocument::fromJson(request.body()).object();LOG() [getAlltagVideoList] 收到 getAlltagVideoList 请求, requestId reqData[requestId].toString() sessionId reqData[sessionId].toString();LOG() videoTag reqData[videoTag].toInt();//获取页号与获取视频数目int pageCount reqData[pageCount].toInt();int64_t pageIndex reqData[pageIndex].toInteger();QJsonObject respData;respData[requestId] reqData[requestId].toString();respData[errorCode] 0;respData[errorMsg] ;QJsonObject result;//总视频个数设置为100result[totalCount] 100;QJsonArray videoList;//构造假数据int videoId 30000;int userId 30000;int resourceId 3000;int fileId 30000;int maxVideoNum qMin(100,pageCount * pageIndex);for(int i pageCount * (pageIndex - 1); i maxVideoNum; i){QJsonObject videoJsonObj;videoJsonObj[videoId] QString::number(videoId);videoJsonObj[userId] QString::number(userId);videoJsonObj[nickname] 咻114514;videoJsonObj[userAvatarId] QString::number(resourceId);videoJsonObj[photoFileId] QString::number(resourceId);videoJsonObj[videoFileId] QString::number(fileId);videoJsonObj[likeCount] 9867;videoJsonObj[playCount] 2105000;videoJsonObj[videoSize] 10240;videoJsonObj[videoDesc] 『Bad Apple!! feat.SEKAI』\n25時、ナイトコードで。 × 初音ミク\n作詞Haruka-作曲ZUN上海アリス幻樂団-編曲ビートまりお×まろん、まらしぃ、Masayoshi Minoshima;videoJsonObj[videoTitle] 【25時、ナイトコードで。 × 初音ミク】Bad Apple!!【2DMV世界计划 × 东方project 联动收录曲】;videoJsonObj[videoDuration] 231;videoJsonObj[videoUpTime] 2024-05-04 17:11:11;videoList.append(videoJsonObj);}result[videoList] videoList;respData[result] result;//构建http响应报文QHttpServerResponse response(QJsonDocument(respData).toJson(),QHttpServerResponse::StatusCode::Ok);response.setHeader(Content-Type,application/json;charsetutf-8);return response;
}QHttpServerResponse MockServer::getAllkeyVideoList(const QHttpServerRequest request)
{//构建body信息QJsonObject reqData QJsonDocument::fromJson(request.body()).object();LOG() [getAllkeyVideoList] 收到 getAllkeyVideoList 请求, requestId reqData[requestId].toString() sessionId reqData[sessionId].toString();LOG() searchKey reqData[searchKey].toString();//获取页号与获取视频数目int pageCount reqData[pageCount].toInt();int64_t pageIndex reqData[pageIndex].toInteger();QJsonObject respData;respData[requestId] reqData[requestId].toString();respData[errorCode] 0;respData[errorMsg] ;QJsonObject result;//总视频个数设置为100result[totalCount] 100;QJsonArray videoList;//构造假数据int videoId 40000;int userId 40000;int resourceId 4000;int fileId 40000;int maxVideoNum qMin(100,pageCount * pageIndex);for(int i pageCount * (pageIndex - 1); i maxVideoNum; i){QJsonObject videoJsonObj;videoJsonObj[videoId] QString::number(videoId);videoJsonObj[userId] QString::number(userId);videoJsonObj[nickname] 咻114514;videoJsonObj[userAvatarId] QString::number(resourceId);videoJsonObj[photoFileId] QString::number(resourceId);videoJsonObj[videoFileId] QString::number(fileId);videoJsonObj[likeCount] 9867;videoJsonObj[playCount] 2105000;videoJsonObj[videoSize] 10240;videoJsonObj[videoDesc] 『Bad Apple!! feat.SEKAI』\n25時、ナイトコードで。 × 初音ミク\n作詞Haruka-作曲ZUN上海アリス幻樂団-編曲ビートまりお×まろん、まらしぃ、Masayoshi Minoshima;videoJsonObj[videoTitle] 【25時、ナイトコードで。 × 初音ミク】Bad Apple!!【2DMV世界计划 × 东方project 联动收录曲】;videoJsonObj[videoDuration] 231;videoJsonObj[videoUpTime] 2024-05-04 17:11:11;videoList.append(videoJsonObj);}result[videoList] videoList;respData[result] result;//构建http响应报文QHttpServerResponse response(QJsonDocument(respData).toJson(),QHttpServerResponse::StatusCode::Ok);response.setHeader(Content-Type,application/json;charsetutf-8);return response;
}记得不要忘记在init函数中路由我们规定好的URI。 接下来我们在首页调用datacenter中的异步方法并处理完成信号同时实现置顶与刷新方法以及获取下一页视频的方法因为比较多这里我就不像之前那样只给新增的部分了而是把对应模块的cpp与h文件全部给出以便读者理解:
//homepagewidget.h
#ifndef HOMEPAGEWIDGET_H
#define HOMEPAGEWIDGET_H#include QWidget
#include QPushButtonclass HomePageWidget : public QWidget
{Q_OBJECTpublic:explicit HomePageWidget(QWidget *parent nullptr);//初始化分类栏与标签栏void initKindsAndTags();//初始化刷新和置顶按钮void initRefreshAndTop();//初始化首页视频列表void initVideos();//构建一个按钮对象QPushButton* buildSelectBtn(QWidget* parent,const QString color,const QString text);//重新设置标签栏void resetTags(const QListQString kindToTags);//链接网络部分的所有信号槽函数void connectSingalsAndSlotsNet();//更新首页当前的视频void updateShowVideos();//清空当前页面所有视频void clearShowVideos();//发起搜索视频请求void searchRequest(const QString searchKey);//检测滑动条位置决定更新策略void onSrcollAreaValueChanged(int value);~HomePageWidget();private slots://设置分类与标签按钮被点击时的响应函数void onKindBtnClicked(QPushButton* kindBtn);void onTagBtnClicked(QPushButton* tagBtn);//设置置顶和刷新按钮被点击时的响应函数void onRefreshBtnClicked();void onTopBtnClicked();private:Ui::HomePageWidget *ui;QHashQString,QListQString tags;QString currentKind;//辅助curTag找到对应IdQString currentTag;int curKind 0;int curTag 0;QString currentSearch;VideoListStyle videoListStyle AllStyle;//当前获取视频的方式-默认为全部视频
};#endif // HOMEPAGEWIDGET_H//homepagewidget.cpp
#include homepagewidget.h
#include ui_homepagewidget.h
#include videobox.h
#include util.h
#include ./model/datacenter.h
#include QScrollBarHomePageWidget::HomePageWidget(QWidget *parent): QWidget(parent), ui(new Ui::HomePageWidget)
{ui-setupUi(this);initKindsAndTags();initRefreshAndTop();connectSingalsAndSlotsNet();initVideos();
}void HomePageWidget::initKindsAndTags()
{//添加首行分类标签QPushButton* classify buildSelectBtn(ui-classify,#3ECEFF,分类);classify-setDisabled(true);ui-classifyHLayout-addWidget(classify);auto dataCenter model::DataCenter::getInstance();auto kindsAndTags dataCenter-getKindsAndTags();//添加分类按钮顺便初始化tagsconst QListQString kinds kindsAndTags-getAllKinds();for(auto kind : kinds){QPushButton* kindBtn buildSelectBtn(ui-classify,#222222,kind);ui-classifyHLayout-addWidget(kindBtn);//连接点击事件信号槽connect(kindBtn,QPushButton::clicked,this,[](){onKindBtnClicked(kindBtn);});//初始化tagstags.insert(kind,kindsAndTags-getAllTagsFromKind(kind));}//分类栏按钮设置一定间距ui-classifyHLayout-setSpacing(8);//标签栏按钮设置一定间距ui-labelHLayout-setSpacing(4);resetTags(tags[kinds[0]]);
}void HomePageWidget::initRefreshAndTop()
{QWidget* widget new QWidget(this);widget-setFixedSize(42, 94);widget-setStyleSheet(QPushButton:hover{background-color:#666666}QPushButton{background-color : #DDDDDD;border-radius : 21px;border : none;});QPushButton* refresh new QPushButton();refresh-setFixedSize(42, 42);refresh-setStyleSheet(border-image :url(:/images/homePage/shuaxin.png););QPushButton* top new QPushButton();top-setFixedSize(42, 42);top-setStyleSheet(border-image : url(:/images/homePage/zhiding.png));QVBoxLayout* layout new QVBoxLayout(widget);layout-addWidget(refresh);layout-addWidget(top);layout-setContentsMargins(0,0,0,0);layout-setSpacing(10);widget-move(1285, 630);widget-show();//为两个按钮分别绑定槽函数connect(refresh,QPushButton::clicked,this,HomePageWidget::onRefreshBtnClicked);connect(top,QPushButton::clicked,this,HomePageWidget::onTopBtnClicked);
}void HomePageWidget::initVideos()
{//设置视频块对齐规则ui-videoGLayout-setAlignment(Qt::AlignLeft | Qt::AlignTop);auto dataCenter model::DataCenter::getInstance();dataCenter-getAllVideoListAsync();
}QPushButton* HomePageWidget::buildSelectBtn(QWidget *parent, const QString color, const QString text)
{QPushButton* pushButton new QPushButton(text,parent);pushButton-setMinimumHeight(26);pushButton-setFixedWidth(text.size()*161818);//设置按钮宽度按照字体大小及个数动态变化pushButton-setStyleSheet(color : color ;);return pushButton;
}void HomePageWidget::onKindBtnClicked(QPushButton *kindBtn)
{auto dataCenter model::DataCenter::getInstance();//重复点击不响应int kind dataCenter-getKindsAndTags()-getKindId(kindBtn-text());if(curKind kind){return;}//更新curKind以及重置curTagcurrentKind kindBtn-text();curKind kind;curTag 0;//设置当前按钮的点击颜色同时移除其他按钮的颜色kindBtn-setStyleSheet(background-color: #F1FDFF;color:#3ECEFF;);QListQPushButton* btns ui-classify-findChildrenQPushButton*();for(int i 0;i btns.size();i){if(btns[i] ! kindBtn btns[i]-text() ! 分类){btns[i]-setStyleSheet(color : #222222;);}}//重置标签栏resetTags(tags[kindBtn-text()]);//更新界面中的视频clearShowVideos();videoListStyle KindStyle;dataCenter-getAlltypeVideoListAsync(curKind);
}void HomePageWidget::onTagBtnClicked(QPushButton *tagBtn)
{auto dataCenter model::DataCenter::getInstance();//重复点击不响应-同时正好解决了没有选择分类直接选择标签引起的问题int tag dataCenter-getKindsAndTags()-getTagId(currentKind,tagBtn-text());if(curTag tag){return;}//更新curTagcurrentTag tagBtn-text();curTag tag;//设置当前按钮的点击颜色同时移除其他按钮的颜色tagBtn-setStyleSheet(background-color: #F1FDFF;color:#3ECEFF;);QListQPushButton* btns ui-labels-findChildrenQPushButton*();for(int i 0;i btns.size();i){if(btns[i] ! tagBtn btns[i]-text() ! 标签){btns[i]-setStyleSheet(color : #222222;);}}//更新界面中的视频clearShowVideos();videoListStyle TagStyle;dataCenter-getAlltagVideoListAsync(curTag);
}void HomePageWidget::onRefreshBtnClicked()
{//清空当前界面中的所有视频clearShowVideos();//到服务端获取视频数据auto dataCenter model::DataCenter::getInstance();switch(videoListStyle){case AllStyle:dataCenter-getAllVideoListAsync();break;case KindStyle:dataCenter-getAlltypeVideoListAsync(curKind);break;case TagStyle:dataCenter-getAlltagVideoListAsync(curTag);break;case SearchStyle:dataCenter-getAllkeyVideoListAsync(currentSearch);break;default:LOG()暂不⽀持的数据类型;}
}void HomePageWidget::onTopBtnClicked()
{//置顶ui-videoScroll-verticalScrollBar()-setValue(0);
}void HomePageWidget::resetTags(const QListQString kindToTags)
{QListQPushButton* btns ui-labels-findChildrenQPushButton*();//移除并释放原来所有的标签for(int i 0;i btns.size();i){ui-labelHLayout-removeWidget(btns[i]);delete btns[i];}QPushButton* label buildSelectBtn(ui-labels,#3ECEFF,标签);label-setDisabled(true);ui-labelHLayout-addWidget(label);for(auto tag : kindToTags){QPushButton* tagBtn buildSelectBtn(ui-labels,#222222,tag);ui-labelHLayout-addWidget(tagBtn);connect(tagBtn,QPushButton::clicked,this,[](){onTagBtnClicked(tagBtn);});}
}void HomePageWidget::connectSingalsAndSlotsNet()
{auto dataCenter model::DataCenter::getInstance();//处理视频信息更新好的消息connect(dataCenter,model::DataCenter::getAllVideoListDone,this,HomePageWidget::updateShowVideos);connect(dataCenter,model::DataCenter::getAlltypeVideoListDone,this,HomePageWidget::updateShowVideos);connect(dataCenter,model::DataCenter::getAlltagVideoListDone,this,HomePageWidget::updateShowVideos);connect(dataCenter,model::DataCenter::getAllkeyVideoListDone,this,HomePageWidget::updateShowVideos);connect(ui-search,SearchLineEdit::searchRequest,this,HomePageWidget::searchRequest);//当竖直滚动条滑动到底部时获取的新视频connect(ui-videoScroll-verticalScrollBar(),QScrollBar::valueChanged,this,HomePageWidget::onSrcollAreaValueChanged);
}void HomePageWidget::updateShowVideos()
{//更新视频到首页auto dataCenter model::DataCenter::getInstance();auto VideoList dataCenter-getHomeVideoListPtr()-getVideoList();int count ui-videoGLayout-count();for(int i count;i VideoList.size();i){VideoBox* video new VideoBox(VideoList[i]);ui-videoGLayout-addWidget(video,i/4,i%4);}LOG() 视频更新完毕首页中此时一共有 : ui-videoGLayout-count() 个视频;//更新视频页数-辅助视频列表的更新功能dataCenter-getHomeVideoListPtr()-pageIndex;
}void HomePageWidget::clearShowVideos()
{//重置滑动条位置ui-videoScroll-verticalScrollBar()-setValue(0);//清空dataCenter中的videoListauto dataCenter model::DataCenter::getInstance();dataCenter-getHomeVideoListPtr()-clearVideoList();//清空界面中的所有视频QLayoutItem* VideoItem;while ((VideoItem ui-videoGLayout-takeAt(0)) ! nullptr) {// 先删除item中的widget如果有的话if (QWidget* widget VideoItem-widget()) {delete widget;}// 然后删除item本身delete VideoItem;}
}void HomePageWidget::searchRequest(const QString searchKey)
{clearShowVideos();videoListStyle SearchStyle;auto dataCenter model::DataCenter::getInstance();currentSearch searchKey;dataCenter-getAllkeyVideoListAsync(searchKey);
}void HomePageWidget::onSrcollAreaValueChanged(int value)
{//当置顶时不触发更新策略if(value 0){return;}//当获取视频数以达到上限时不再获取auto dataCenter model::DataCenter::getInstance();auto videoList dataCenter-getHomeVideoListPtr();if(videoList-getVideoCount() videoList-getVideoTotalCount()){return;}//当进度条到达底部时再去进行视频更新if(value ui-videoScroll-verticalScrollBar()-maximum()){// 继续获取下⼀⻚的视频数据// 到服务器获取视频数据switch(videoListStyle){case AllStyle:dataCenter-getAllVideoListAsync();break;case KindStyle:dataCenter-getAlltypeVideoListAsync(curKind);break;case TagStyle:dataCenter-getAlltagVideoListAsync(curTag);break;case SearchStyle:dataCenter-getAllkeyVideoListAsync(currentSearch);break;default:LOG()暂不⽀持的数据类型;}}
}HomePageWidget::~HomePageWidget()
{delete ui;
}//searchlineedit.h
#ifndef SEARCHLINEEDIT_H
#define SEARCHLINEEDIT_H#include QLineEditclass SearchLineEdit : public QLineEdit
{Q_OBJECT
public:explicit SearchLineEdit(QWidget *parent nullptr);void searchBtnClicked();//处理搜索事件
signals:void searchRequest(const QString searchKey);
};#endif // SEARCHLINEEDIT_H//searchlineedit.cpp
#include searchlineedit.h
#include QLabel
#include QPushButton
#include QHBoxLayoutSearchLineEdit::SearchLineEdit(QWidget *parent): QLineEdit{parent}
{// 搜索框图标QLabel* searchImg new QLabel(this);searchImg-setFixedSize(16, 16);searchImg-setPixmap(QPixmap(:/images/homePage/sousuo.png));// 搜索框按钮QPushButton* searchBtn new QPushButton(搜索,this);searchBtn-setCursor(QCursor(Qt::PointingHandCursor));//设置手型光标searchBtn-setFixedSize(62, 32);searchBtn-setStyleSheet(background-color : #3ECEFE;border-radius : 16px;font-size : 14px;color : #FFFFFF;font-style : normal;);this-setTextMargins(33, 0, 0, 0);//设置搜索框中的文字向右靠一些QHBoxLayout* hLayout new QHBoxLayout(this);hLayout-addWidget(searchImg);hLayout-addStretch();//添加一个水平弹簧把二者撑到两边hLayout-addWidget(searchBtn);hLayout-setContentsMargins(11, 0, 2, 0);//左上右下//绑定回车与点击事件connect(searchBtn, QPushButton::clicked, this,SearchLineEdit::searchBtnClicked);connect(this, QLineEdit::returnPressed, this,SearchLineEdit::searchBtnClicked);
}void SearchLineEdit::searchBtnClicked()
{emit searchRequest(this-text());//清空搜索文本this-setText();
}五.videoBox页-下载图片接口
我们首先来分析下videoBox需要显示图片的部分一处是视频的封面还有一处是上传视频的用户头像那么我们可以通过之前在videoInfo中存储的userAvatarId-用户头像ID , photoFiled-封面文件ID向服务端发送请求获取图片资源。而这些信息并不是敏感信息所以我们这里换用GET的方法向服务端发送下载图片的请求: 请求URL : GET /HttpService/downloadPhoto?requestIdxxxsessionIdxxxfileIdxxx 请求参数也不用再设计了因为它已经在URL中包含了。然后需要将Content-Type头部中的application/json更改为application/octet-stream。 客户端定义请求下载图片方法:
//datacenter.h
namespace model{class DataCenter : public QObject
{Q_OBJECT
public:// 下载图⽚void downloadPhotoAsync(const QString photoFileId);
signals:// 下载图⽚处理完毕// 每个VideoBox上都要下载视频封⾯和图⽚导致下载图⽚完成信号会被绑定多次// ⼀个信号被绑定多次时当该信号触发时会被执⾏多次// 添加imageId参数表明是某控件触发的下载图⽚请求才处理该次图⽚下载的界⾯显⽰void downloadPhotoDone(const QString imageId, QByteArray imageData);
};
//datacenter.cpp
void DataCenter::downloadPhotoAsync(const QString photoFileId)
{// 下载图⽚netClient.downloadPhoto(photoFileId);
}//netclient.hvoid downloadPhoto(const QString photoFileId);//下载图片请求//netclient.cpp
void NetClient::downloadPhoto(const QString photoFileId)
{// 1. 构造请求QString queryString;queryString requestId;queryString makeRequestId();queryString ;queryString sessionId;queryString dataCenter-getSessionId();queryString ;queryString fileId;queryString photoFileId;// 2. 发送请求QNetworkRequest httpReq;httpReq.setUrl(QUrl(HTTP_URL /HttpService/downloadPhoto? queryString));QNetworkReply* httpReply netClientManager.get(httpReq);// 3. 异步处理响应connect(httpReply, QNetworkReply::finished, this, [](){// 解析响应if (httpReply-error() ! QNetworkReply::NoError) {LOG() httpReply-errorString();httpReply-deleteLater();return;}//2.获取图⽚数据// 发射信号通知界⾯更视频显⽰httpReply-deleteLater();QByteArray imageData httpReply-readAll();emit dataCenter-downloadPhotoDone(photoFileId, imageData);LOG() downloadPhoto请求结束图片下载成功;});
}这里也可以使用post但一般不这样干因为POST 请求虽然也可以在 URL 中带有查询参数但这通常用于非核心的、辅助性的参数而不是用于传递要创建或更新的主体数据。 不过我们现在并没有图片数据所以只能先造一批假的图片数据了: 然后根据我们之前在处理视频列表中造的假数据设置相应图片ID与图片的映射关系:
//mockserver.h
class MockServer : public QWidget
{Q_OBJECTpublic:// 构造响应数据void buildResponseData();
private:// 存放资源id和路径的对应关系QMapint, QString idPathTable;
};//mockserver.cpp
void MockServer::buildResponseData()
{int resourceId 1000;for(int i 0;i 20;i){idPathTable[resourceId] /images/avatar1.png;idPathTable[resourceId] /images/photofile1.jpg;}resourceId 2000;for(int i 0;i 20;i){idPathTable[resourceId] /images/avatar2.png;idPathTable[resourceId] /images/photofile2.jpg;}resourceId 3000;for(int i 0;i 20;i){idPathTable[resourceId] /images/avatar3.png;idPathTable[resourceId] /images/photofile3.png;}resourceId 4000;for(int i 0;i 20;i){idPathTable[resourceId] /images/avatar4.png;idPathTable[resourceId] /images/photofile4.jpg;}
}接下来我们利用这一批假数据返回响应给客户端:
//mockserver.hQHttpServerResponse downloadPhoto(const QHttpServerRequest request);//下载图片的接口
//mockserver中的util.h
//将上传的文件转化为二进制流
static inline QByteArray loadFileToByteArray(const QString filePath)
{QFile file(filePath);//以只读方式打开if(!file.open(QIODevice::ReadOnly)){LOG() 文件打开失败;return QByteArray();}QByteArray res file.readAll();file.close();return res;
}// 把 QByteArray 中的内容, 写⼊到某个指定⽂件⾥
static inline void writeByteArrayToFile(const QString path, const QByteArray content) {QFile file(path);bool ok file.open(QFile::WriteOnly);if (!ok) {LOG() ⽂件打开失败!;return;}file.write(content);file.flush();file.close();
}
//mockserver.cpp
QHttpServerResponse MockServer::downloadPhoto(const QHttpServerRequest request){// 解析查询字符串QUrlQuery query(request.url());QString requstId query.queryItemValue(requestId);QString fileId query.queryItemValue(fileId);QString sessionId query.queryItemValue(sessionId);LOG() [downloadPhoto] 收到 downloadPhoto 请求, requestId requstId sessionId sessionId;// 构造图⽚路径QDir dir(QDir::currentPath());dir.cdUp();dir.cdUp();QString imagePath dir.absolutePath();imagePath idPathTable[fileId.toInt()];LOG()图片IDfileId--imagePath;// 读取图⽚数据QByteArray imageData loadFileToByteArray(imagePath);// 构造 HTTP 响应QHttpServerResponse httpResp(imageData,QHttpServerResponse::StatusCode::Ok);httpResp.setHeader(Content-Type, application/octet-stream);return httpResp;
}六.videoBox页-更新视频封面
这里我们就可以使用上面实现的下载图片接口来获取视频封面了但是有一个问题因为之前我们的imageBox是QWidget类型的也就意味着我们不能像QLabel那样使用QPixmap加载服务端给的二进制流数据然后直接设置为封面图了。 所以我们有两种解决方法一种就是重写videoBox的paintEvent的方法另一种便是将imageBox类型修改为QLabel。后者比较简单但是考虑到部分读者需要去改之前相关部分的代码这里我们选用第一种方式之前写的代码我们也不需要进行修改了:
//videobox.h
namespace Ui {
class VideoBox;
}class VideoBox : public QWidget
{Q_OBJECTpublic:// 重写paintEvent事件避免图⽚平铺重叠以正常显示背景图片void paintEvent(QPaintEvent *event) override;
private:QPixmap videoCoverImage;
};//videobox.cpp
#include videobox.h
#include ui_videobox.h
#include util.h
#include QDir
#include QStringvoid VideoBox::paintEvent(QPaintEvent *event)
{// 启用自动填充背景功能// 当设置为true时Qt会在每次绘制控件前自动使用当前调色板中的画刷填充控件背景// 这确保了背景内容会在其他绘制操作之前被正确绘制ui-imageBox-setAutoFillBackground(true);// 获取imageBox控件当前的调色板对象// QPalette包含了控件各种状态下的颜色和画刷设置正常、禁用、激活等状态// 这里我们获取当前调色板以便修改其中的背景画刷设置QPalette palette ui-imageBox-palette();// 对原始视频封面图片进行缩放处理以适应控件尺寸// 原始图片尺寸可能与界面控件尺寸存在较大差异需要进行适当的缩放QPixmap scaledImage videoCoverImage.scaled(ui-imageBox-size(), // 目标尺寸使用imageBox的当前大小Qt::KeepAspectRatioByExpanding, // 缩放模式保持原始宽高比但尽可能扩展以填满控件// 这种模式可能会使图片的一部分超出控件边界但能确保控件被完全填满Qt::SmoothTransformation // 变换模式采用高质量的双线性插值算法进行缩放// 这能显著提高缩放后图片的视觉质量避免锯齿和模糊);// 使用缩放后的图片创建画刷对象// QBrush定义了如何填充形状的背景模式这里使用图片作为填充图案QBrush brush(scaledImage);// 将创建的画刷设置到调色板的Window角色中// QPalette::Window表示控件背景区域的画刷这会影响整个控件的背景填充// 其他常用的角色还包括Base文本输入背景、Text文本颜色、Button按钮背景等palette.setBrush(QPalette::Window, brush);// 将修改后的调色板应用回imageBox控件// 这会更新控件的视觉表现使用新的背景画刷进行绘制// 注意此处不能通过qss设置圆角样式因为样式表会覆盖QPalette的背景设置// 导致背景图片无法显示(相反的后者也会覆盖前者也就是说图片显示与圆角使用这种方法无法兼得)因此需要采用其他方法实现圆角效果例如// 1. 使用自定义绘制在paintEvent中直接绘制图片和圆角// 2. 使用事件过滤器拦截绘制事件// 3. 创建自定义QWidget子类重写paintEvent方法//因为原来的直角并不难看为了不增加代码的复杂度便不再添加圆角了ui-imageBox-setPalette(palette);
}接下来便可以编写代码向服务端发送获取视频封面图片的请求并处理返回的响应数据。
//videobox.h
namespace Ui {
class VideoBox;
}class VideoBox : public QWidget
{Q_OBJECT
private:// 设置视频封⾯void setVideoImage(const QString photoFileId);
private slots:void getVideoImageDown(const QString photoId,QByteArray imageData);
};//videobox.cpp
#include videobox.h
#include ui_videobox.h
#include util.h
#include QDir
#include QStringVideoBox::VideoBox(model::VideoInfo videoInfo,QWidget *parent): QWidget(parent), ui(new Ui::VideoBox), videoInfo(videoInfo)
{// 获取视频封⾯图⽚成功auto dataCenter model::DataCenter::getInstance();connect(dataCenter, model::DataCenter::downloadPhotoDone, this, VideoBox::getVideoImageDown);
}void VideoBox::updateVideoUi()
{setVideoImage(videoInfo.photoFileId);//设置视频封面信息
}void VideoBox::setVideoImage(const QString photoFileId)
{// 向服务器请求视频封⾯图⽚auto dataCenter model::DataCenter::getInstance();dataCenter-downloadPhotoAsync(photoFileId);
}void VideoBox::getVideoImageDown(const QString photoId, QByteArray imageData)
{//当图片不属于当前box的封面拒绝更新if(photoId ! videoInfo.photoFileId){return;}// 将图⽚更新到界⾯videoCoverImage.loadFromData(imageData);
}七.videoBox页-更新上传视频的用户头像
这就比较简单了我们直接使用QPixmap加载服务端给的二进制流数据给他设置上去就ok了
//videobox.h
namespace Ui {
class VideoBox;
}class VideoBox : public QWidget
{Q_OBJECT
private://设置用户头像void setUserImage(const QString userAvatarId);
private slots:void getAvatarImageDown(const QString photoId,QByteArray imageData);
private:QPixmap userImage;
};//videobox.cpp
void VideoBox::setUserImage(const QString userAvatarId)
{if(userAvatarId.isEmpty()){ui-userIcon-setStyleSheet(border-image :url(:/images/myself/defaultAvatar.png););}else{auto dataCenter model::DataCenter::getInstance();dataCenter-downloadPhotoAsync(userAvatarId);}
}void VideoBox::getAvatarImageDown(const QString photoId, QByteArray imageData)
{if(photoId ! videoInfo.userAvatarId){return;}userImage makeCircleIcon(imageData,ui-userIcon-width() / 2).pixmap(ui-userIcon-size());ui-userIcon-setPixmap(userImage);
}这些业务逻辑处理完毕之后我们能看到如下效果: 全部视频列表: 分类视频列表: 标签视频列表: 搜索视频列表: