当前位置: 首页 > news >正文

基于脚手架微服务的视频点播系统-客户端业务逻辑处理部分(二)

基于脚手架微服务的视频点播系统-客户端业务逻辑处理部分二

  • 一.播放m3u8文件
  • 二.下载弹幕
  • 三.播放界面数据更新
  • 四.更新播放数
  • 五.检测视频是否点赞过
  • 六.更新点赞数
  • 七.发送弹幕
  • 八.我的界面部分-用户信息结构的设定
  • 九.获取当前用户信息
  • 十.上传与修改用户头像
  • 十一.获取用户视频列表
  • 十二.删除视频
  • 十三.获取其他用户信息
  • 十四.关注与取消关注

写这部分代码的时候碰到了两次bug,耗时很久就解决了一个,所以才鸽了一个月再来更新。这里先提前说一下本文结束时代码的bug。当然不是代码的问题经排查,是ffmpeg我最开时时版本使用的有问题。导致mpv通过url播放远端服务器上的m3u8文件时不能通过调整进度条正确设置播放位置。为什么这里不解决了呢,是因为这个问题仅出现在本地环回测试上,而博主在写服务端时搁服务端弄了个m3u8文件让我这个“有问题”的客户端去播放,发现可以正常播放了。所以如果读者发现本地测试无法正常播放,不用担心,等我们服务端也写好之后一联调就没有这问题了。ok,废话不多说,我们继续客户端代码的编写。
还需要说明的一点是,因为我目前的代码量已经比上篇文章多很多了,我不会再像之前那样给出每一步的具体实现代码,因为每个人的大致逻辑虽然相同,但细节实现上大有不同,给出我自己细节部分的实现反而会造成干扰。所以只给出关键部分和主要逻辑在文章中,如有更详细的代码需要可以参考我前文给的源码连接。

一.播放m3u8文件

__前⾯实现视频播放时,播放的视频是从本地加载的,正常情况下,当⽤⼾点击⾸⻚的视频封⾯或标题时,应该从服务器获取要播放的视频⽂件,然后交给mpv库进⾏播放。要想播放服务器上视频⽂件,实现⼀个从服务器获取视频⽂件的接口即可。但问题是,⼀个视频⽂件可能⽐较⼤,如果直接从服务器获取完成视频⽂件后再播放,则客⼾端等待的时间太⻓了,可以尝试将原视频进⾏分⽚进⾏传输播放。
 m3u8是⼀种⽤于流媒体播放的索引⽂件格式,包含了多个媒体⽂件的路径或URL,本质上是基于⽂本的播放列表,⽤于指定视频或⾳频的分段⽂件以及播放顺序,使播放器能够按需加载和播放内容,是苹果公司为HLS(HTTP Live Streaming 流媒体⽹络传输协议)流媒体设计的标准格式,⼴泛⽤于在线视频和直播场景。
 当⽤⼾将视频⽂件上传服务器后,服务器会将视频⽂件切割成多个⼩⽂件(通常是.ts后缀的分⽚),M3U8⽂件记录这些分⽚的URL和顺序,播放器拿到M3U8⽂件后,根据内部管理的视频URL,下载并拼接播放,⼤⼤提升了流畅性。注意:mpv库⽀持M3U8⽂件播放。
 ffmpeg大家可以自己去网上搜索教程进行下载安装,如果不想安装可以用我在gitee中提交的样例视频。
 因为mpv播放器支持直接通过url播放远端服务器上的m3u8文件。所以客户端我们只需要改变startplay方法即可。
我们这里规定下请求的url格式:

GET /HttpService/downloadVideo?fileId=xxx
//body参数
application/octet-stream
//playerpage.cpp
void PlayerPage::startPlayer()
{//如果说弹幕框没有被初始化,初始化弹幕框if(bulletScreen == nullptr)initBulletScreen();bulletScreen->show();if(player == nullptr){//用户点击播放时再去实例化player对象player = new MpvPlayer(ui->screen);//进度条同步播放时间connect(player,&MpvPlayer::timePosChanged,this,&PlayerPage::onTimePosChanged);//绑定所有视频分片播放完毕发出的信号connect(player,&MpvPlayer::endOfPlaylist,this,&PlayerPage::startPlayer);//设置默认音量为50%player->setVolume(50.0);}auto datacenter = model::DataCenter::getInstance();QString videoPath = datacenter->getHttpUrl() + "/HttpService/downloadVideo?fileId=" + videoInfo.videoFileId;player->startPlay(videoPath);
}

我们在本地服务端的视频文件摆放路径如下:
在这里插入图片描述
所以假服务端需要新增两个路由:

//mockServer.cpp
QHttpServerResponse MockServer::downloadVideo(const QHttpServerRequest &request)
{// 解析查询字符串QUrlQuery query(request.url());QString fileId = query.queryItemValue("fileId");LOG() << "[downloadVideo] 收到 downloadVideo 请求, videoFileId=" << fileId;// 构造m3u8文件路径QDir dir(QDir::currentPath());dir.cdUp();dir.cdUp();QString videoPath = dir.absolutePath();videoPath += idPathTable[fileId.toInt()];LOG()<<"视频ID:"<<fileId<<"--"<<videoPath;// 读取视频m3u8文件数据QByteArray videoData = loadFileToByteArray(videoPath);// 构造 HTTP 响应QHttpServerResponse httpResp(videoData,QHttpServerResponse::StatusCode::Ok);httpResp.setHeader("Content-Type", "application/octet-stream");return httpResp;
}QHttpServerResponse MockServer::downloadVideoSegmentation(const QString& fileName)
{// 构造视频文件的路径QDir dir(QDir::currentPath());dir.cdUp();dir.cdUp();QString videoPath = dir.absolutePath();videoPath += "/videos/" + fileName;LOG() << videoPath;// 读取视频分片数据QByteArray videoData = loadFileToByteArray(videoPath);// 构造HTTP响应并返回给客户端QHttpServerResponse httpResp(videoData, QHttpServerResponse::StatusCode::Ok);httpResp.setHeader("Content-Type", "application/octet-stream");return httpResp;
}

而我们之前播放视频时是通过playTime去检测是否播放完毕的,不够严谨,所以我们这里需要通过mpv去获取播放时间,同时根据当前播放的是那个分片与总分片个数进行对比来判断视频是否播放完毕:

//mpvplayer.cpp
void MpvPlayer::handleMpvEvent(mpv_event *event)
{switch (event->event_id) {case MPV_EVENT_PROPERTY_CHANGE:{mpv_event_property* eventPropery = (mpv_event_property*)event->data;if(eventPropery->data == nullptr){//判断数据是否为空-因为程序刚启动时不一定立马就有视频播放return;}//判断事件是否为timePosif (strcmp(eventPropery->name, "time-pos") == 0) {// 获取当前分片的起始时间double segmentStartTime = 0;mpv_get_property(mpv, "demuxer-start-time", MPV_FORMAT_DOUBLE, &segmentStartTime);// 获取当前分片内的播放时间double segmentCurrentTime = 0;mpv_get_property(mpv, "time-pos", MPV_FORMAT_DOUBLE, &segmentCurrentTime);currentTime = (int64_t)(segmentStartTime + segmentCurrentTime);emit timePosChanged(currentTime);}break;}case MPV_EVENT_END_FILE:{mpv_event_end_file* endFile = (mpv_event_end_file*)event->data;if(endFile->reason == MPV_END_FILE_REASON_EOF){// 检查是否播放最后⼀个视频分⽚int64_t playlist_pos = -1;int64_t playlist_count = -1;mpv_get_property(mpv, "playlist-pos", MPV_FORMAT_INT64,&playlist_pos);mpv_get_property(mpv, "playlist-count", MPV_FORMAT_INT64, &playlist_count);LOG() << playlist_pos << ":" <<playlist_count;// 综合判断条件if(((playlist_count > 0) && (playlist_pos == playlist_count - 1)) || playlist_pos == -1){LOG() << "所有视频分片播放完毕";emit endOfPlaylist();}else{LOG() << "单个分片播放结束";}}break;}case MPV_EVENT_SHUTDOWN:{//如果是mpv关闭事件则释放mpv对象if(mpv){mpv_terminate_destroy(mpv);mpv = nullptr;}break;}default:break;}
}

上面代码中在mpvPlayer类中新增currentTime 成员变量。剩余部分与playTime相关的修改部分不再给出。

二.下载弹幕

视频在播放时,需要同步加载各播放时间的弹幕数据,并显示在视频界⾯上。弹幕数据存储在服务器上,在视频播放前获取到弹幕数据后,再播放视频。
获取弹幕的接口定义如下:
请求URL

POST /HttpService/getBarrage 

请求参数:

字段名称字段类型字段说明
requestIdstring请求ID
sessionIdstring客户端会话ID
videoIdstring视频ID

返回响应:200 OK

响应参数

字段名称字段类型字段说明
requestIdstring请求ID
errorCodeinteger错误码;0-成功
errorMsgstring错误信息
resultobject返回结果
barrageListarray弹幕列表
barrageIdstring弹幕ID
userIdstring发送用户
barrageContentstring弹幕内容
barrageTimeinteger弹幕时间-相对秒数

接下来就是经典步骤了dataCenter定义异步方法与完成信号,服务端接收请求返回响应。

//dataCenter.h//下载视频的弹幕数据void downloadVideoBarrage(const QString& videoId);signals://下载弹幕数据请求成功响应void downloadVideoBarrageDone(const QString& videoId,const QJsonObject& barrages);
//datacenter.cpp
void DataCenter::downloadVideoBarrage(const QString &videoId)
{netClient.downloadVideoBarrage(videoId);
}//netclient.h
void downloadVideoBarrage(const QString &videoId);//下载视频的弹幕数据的请求
//netclient.cpp
void NetClient::downloadVideoBarrage(const QString &videoId)
{/*{"requestId": "string","sessionId": "string","videoId": "string"}*///请求报文的bodyQJsonObject reqbody;reqbody["requestId"] = makeRequestId();reqbody["sessionId"] = dataCenter->getSessionId();reqbody["videoId"] = videoId;QNetworkReply* reply = sendReqToHttpServer("/HttpService/getBarrage",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();emit dataCenter->downloadVideoBarrageDone(videoId,result);});
}//mockserver.h
QHttpServerResponse downloadVideoBarrage(const QHttpServerRequest &request);//下载视频的弹幕数据的接口
QJsonArray bulidBarrages();//构造弹幕假数据的接口
//mockserver.cpp
QJsonArray MockServer::bulidBarrages()
{//只构造前100s的弹幕数据int totalTime = 100;QJsonArray barrageLists;for(int i = 0;i < 100;i++){QJsonObject barrage;barrage["barrageId"] = QString::number(i);barrage["userId"] = "2";barrage["barrageContent"] = QString("测试弹幕-%1").arg(i);barrage["barrageTime"] = i;barrageLists.append(barrage);}return barrageLists;
}QHttpServerResponse MockServer::downloadVideoBarrage(const QHttpServerRequest &request)
{//构建body信息QJsonObject reqData = QJsonDocument::fromJson(request.body()).object();LOG() << "[downloadVideoBarrage] 收到 downloadVideoBarrage 请求, requestId = " <<reqData["requestId"].toString() << "sessionId = " << reqData["sessionId"].toString();QJsonObject respData;respData["requestId"] = reqData["requestId"].toString();respData["errorCode"] = 0;respData["errorMsg"] = "";QJsonObject result;result["barrageList"] = bulidBarrages();respData["result"] = result;//构建http响应报文QHttpServerResponse response(QJsonDocument(respData).toJson(),QHttpServerResponse::StatusCode::Ok);response.setHeader("Content-Type","application/json;charset=utf-8");return response;
}

那么当客户端成功收到服务端传过来的json报文后,则需要进行解析并加载到当前播放器的弹幕列表中。同时我们需要在弹幕数据加载完毕之后再去播放视频,所以添加新函数去处理响应信号的同时还需要将原来的播放逻辑修改为一下形式:

//playerpage.cpp
void PlayerPage::startPlayer()
{//如果说弹幕框没有被初始化,初始化弹幕框if(bulletScreen == nullptr)initBulletScreen();bulletScreen->show();if(player == nullptr){//用户点击播放时再去实例化player对象player = new MpvPlayer(ui->screen);//进度条同步播放时间connect(player,&MpvPlayer::timePosChanged,this,&PlayerPage::onTimePosChanged);//绑定所有视频分片播放完毕发出的信号connect(player,&MpvPlayer::endOfPlaylist,this,&PlayerPage::startPlayer);//设置默认音量为50%player->setVolume(50.0);}loadBulletScreenData();//加载完毕弹幕数据后再进行播放
}void PlayerPage::loadBulletScreenData()
{//向服务器发射弹幕数据获取请求model::DataCenter* dataCenter = model::DataCenter::getInstance();dataCenter->downloadVideoBarrage(videoInfo.videoId);
}void PlayerPage::downloadVideoBarrageSuccess(const QString& videoId,const QJsonObject& barrages)
{QJsonArray barrageLists = barrages["barrageList"].toArray();//不是我当前视频的弹幕直接拒绝,弹幕数据为空也返回if(videoId != videoInfo.videoId || barrageLists.size() == 0){return;}//提取弹幕数据QList<BulletScreenInfo> curbarrages;for(int i = 0;i < barrageLists.size();i++){QJsonObject object = barrageLists[i].toObject();int64_t barrageTime = object["barrageTime"].toInteger();if(i != 0){//如果和上条弹幕时间相同,则直接插入int64_t lastBarrageTime = curbarrages.back().playTime;if(barrageTime != lastBarrageTime){//时间不同则将目前的barrage插入哈希表中,清空再进行下次插入bulletScreenLists.insert(lastBarrageTime,curbarrages);curbarrages.clear();}}curbarrages.append(BulletScreenInfo(object["userId"].toString(),barrageTime,object["barrageContent"].toString()));}//将最后一段弹幕数据插入弹幕哈希集合中bulletScreenLists.insert(curbarrages.back().playTime,curbarrages);//弹幕数据加载完毕,开始视频数据的加载auto datacenter = model::DataCenter::getInstance();QString videoPath = datacenter->getHttpUrl() + "/HttpService/downloadVideo?fileId=" + videoInfo.videoFileId;player->startPlay(videoPath);
}

ok,播放视频与弹幕加载部分到这里也就完成了。对了,这里我们需要改一下原来playerpage时的关闭逻辑,具体逻辑是关闭时直接释放播放器对象以及mpv对象,当用户重新播放视频时再去new一个playerpage与mpv播放器对象。这样子改能显著降低我们播放器的内存消耗(同时存在一堆播放器内存占用肯定大啊,之前设计时没考虑到这一点)。大家根据自己的代码按照上面的逻辑进行修改即可。

三.播放界面数据更新

因为播放界面的上层videoBox已经存储有视频的相关信息了,所以我们直接在播放器的类中新增一个VideoInfo类型的成员变量并让Videobox在初始构造时传入视频信息即可。

//playerpage.cpp
void PlayerPage::updatePlayerPageUi()
{ui->videoTittle->setText(videoInfo.videoTitle);//标题ui->userNikeName->setText(videoInfo.nickname);//用户昵称ui->loadupTime->setText(videoInfo.videoUpTime);//更新时间ui->playNum->setText(intToString(videoInfo.playCount));//播放数ui->likeNum->setText(intToString(videoInfo.likeCount));//喜欢数ui->videoDesc->setText(videoInfo.videoDesc);//视频简介信息videoTotalTime = videoInfo.videoDuration;ui->videoDuration->setText(secondsFormat(0) + "/" + secondsFormat(videoTotalTime));//当前播放进度/播放总时长
}void PlayerPage::setUserAvatar(const QPixmap &userAvatar)
{ui->userAvatar->setIcon(QIcon(userAvatar));
}

当然用户头像需要在videoBox界面获取到用户头像之后再进行获取。

//videobox.cpp
bool VideoBox::eventFilter(QObject *watched, QEvent *event)
{if(watched == ui->imageBox || watched == ui->videoTitleBox){//如果是鼠标键按下才播放视频if(event->type() == QEvent::MouseButtonPress){//初始化视频播放窗口-默认不显示-当点击视频界面时再去创建PlayerPage且不需要手动释放,窗口关闭时PlayerPage会自己释放videoPlayer = new PlayerPage(videoInfo);auto dataCenter = model::DataCenter::getInstance();videoPlayer->hide();videoPlayer->setUserAvatar(userImage);LOG() << "播放视频-打开播放窗口";videoPlayer->show();videoPlayer->startPlayer();//事件已处理return true;}}//其他事件继续向下传递return QObject::eventFilter(watched,event);
}

四.更新播放数

播放次数的更新通常与实际播放⾏为相关,例如当⽤⼾点击播放按钮并开始播放视频时,播放次数就会增加;如果⽤⼾只是打开视频但没有点击播放按钮,则播放次数通常不会更新,这是因为播放次数通常指⽤⼾实际观看了内容,⽽不是打开了⽂件。因此在PlayerPage的播放按钮槽函数中,可以增加⽂件的播放次数。
请求Url:

POST /HttpService/setPlay

请求参数

字段名称字段类型字段说明
requestIdstring请求ID
sessionIdstring客户端会话ID
videoIdstring视频ID

返回响应:200 OK

响应参数

字段名称字段类型字段说明
requestIdstring请求ID
errorCodeinteger错误码;0-成功
errorMsgstring错误信息

那我们播放次数的增加就按照用户开始播放当前视频几次来计算吧,也就是说用户每触发一次startplayer我们给当前视频增加一次播放量(当然也可以视频结束时去增加播放量,无非就是把发送播放量改变的请求放到mpvplayer里面而已)。

//playerpage.cpp
void PlayerPage::downloadVideoBarrageSuccess(const QString& videoId,const QJsonObject& barrages)
{QJsonArray barrageLists = barrages["barrageList"].toArray();//不是我当前视频的弹幕直接拒绝,弹幕数据为空也返回if(videoId != videoInfo.videoId || barrageLists.size() == 0){return;}//提取弹幕数据QList<BulletScreenInfo> curbarrages;for(int i = 0;i < barrageLists.size();i++){QJsonObject object = barrageLists[i].toObject();int64_t barrageTime = object["barrageTime"].toInteger();if(i != 0){//如果和上条弹幕时间相同,则直接插入int64_t lastBarrageTime = curbarrages.back().playTime;if(barrageTime != lastBarrageTime){//时间不同则将目前的barrage插入哈希表中,清空再进行下次插入bulletScreenLists.insert(lastBarrageTime,curbarrages);curbarrages.clear();}}curbarrages.append(BulletScreenInfo(object["userId"].toString(),barrageTime,object["barrageContent"].toString()));}//将最后一段弹幕数据插入弹幕哈希集合中bulletScreenLists.insert(curbarrages.back().playTime,curbarrages);//弹幕数据加载完毕,开始视频数据的加载auto datacenter = model::DataCenter::getInstance();QString videoPath = datacenter->getHttpUrl() + "/HttpService/downloadVideo?fileId=" + videoInfo.videoFileId;player->startPlay(videoPath);//更新视频播放数videoInfo.playCount++;datacenter->setPlayNumberAsync(videoInfo.videoId);datacenter->updateVideoPlayNumber(videoInfo.videoId,videoInfo.playCount);//发射更新请求并更新本地存储的数据ui->playNum->setText(intToString(videoInfo.playCount));//更新播放器界面的播放数emit this->videoBoxUpdatePlayNum(videoInfo.playCount);//更新videoBox界面的播放数
}//videobox.cpp
bool VideoBox::eventFilter(QObject *watched, QEvent *event)
{if(watched == ui->imageBox || watched == ui->videoTitleBox){//如果是鼠标键按下才播放视频if(event->type() == QEvent::MouseButtonPress){//......//绑定播放数更新的函数connect(videoPlayer,&PlayerPage::videoBoxUpdatePlayNum,this,[&](int64_t playNum){this->videoInfo.playCount = playNum;ui->playNum->setText(intToString(playNum));});//.....}}//其他事件继续向下传递return QObject::eventFilter(watched,event);
}//datacenter.hvoid updateVideoPlayNumber(const QString& videoId,int64_t playcount);//更新视频的播放数//客户端发送更新播放数的请求void setPlayNumberAsync(const QString& videoId);signals://datacenter.cpp
void DataCenter::updateVideoPlayNumber(const QString &videoId, int64_t playcount)
{for(auto& videoInfo : homeVideoList->videoInfos){if(videoInfo.videoId == videoId){videoInfo.playCount = playcount;break;}}
}void DataCenter::setPlayNumberAsync(const QString &videoId)
{netClient.setPlayNumber(videoId);
}//netclient.cpp
void NetClient::setPlayNumber(const QString &videoId)
{/*{"requestId": "string","sessionId": "string","videoId": "string"}*///请求报文的bodyQJsonObject reqbody;reqbody["requestId"] = makeRequestId();reqbody["sessionId"] = dataCenter->getSessionId();reqbody["videoId"] = videoId;QNetworkReply* reply = sendReqToHttpServer("/HttpService/setPlay",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();//解析成功,处理响应信息LOG() << "setPlay请求结束,修改播放次数成功, requestId: " << respBody["requestId"].toString();});
}

假服务端逻辑如下:

//mockserver.cpp//处理客户端播放数更新的请求httpServer.route("/HttpService/setPlay", [=](const QHttpServerRequest& req) {return this->setPlayNumber(req);});
QHttpServerResponse MockServer::setPlayNumber(const QHttpServerRequest &request)
{//构建body信息QJsonObject reqData = QJsonDocument::fromJson(request.body()).object();LOG() << "[setPlayNumber] 收到 setPlayNumber 请求, requestId = " <<reqData["requestId"].toString() << "sessionId = " << reqData["sessionId"].toString();LOG() << "更新播放量的videoId:" << reqData["videoId"].toString();QJsonObject respData;respData["requestId"] = reqData["requestId"].toString();respData["errorCode"] = 0;respData["errorMsg"] = "";//构建http响应报文QHttpServerResponse response(QJsonDocument(respData).toJson(),QHttpServerResponse::StatusCode::Ok);response.setHeader("Content-Type","application/json;charset=utf-8");return response;
}

因为我们这里是假服务器,就不再进行详细的播放数更新操作了,到服务端部分再。

五.检测视频是否点赞过

打开播放页面后,点赞按钮上图⽚应该根据用户是否对该视频点赞过合理显⽰,因此需要知道⽤⼾是否对该视频点赞过。
请求url:

POST /HttpService/judgeLike 

请求参数

字段名称字段类型字段说明
requestIdstring请求ID
sessionIdstring客户端会话ID
videoIdstring视频ID

返回响应:200 OK
响应参数

字段名称字段类型字段说明
requestIdstring请求ID
errorCodeinteger错误码;0-成功
errorMsgstring错误信息
resultobject结果信息
isLikeboolean是否点赞结果
//playerpage.cpp
void PlayerPage::downloadVideoBarrageSuccess(const QString& videoId,const QJsonObject& barrages)
{//.....//点赞相关datacenter->judgeLikeAsync(videoId);
}//datacenter.h//判断当前用户是否对本视频点赞过void judgeLikeAsync(const QString& videoId);//判断视频是否被点赞成功响应void judgeLikeDone(const QString& videoId,bool isLike);//datacenter.cpp
void DataCenter::judgeLikeAsync(const QString &videoId)
{netClient.judgeLike(videoId);
}//netclient.cpp
void NetClient::setLike(const QString &videoId)
{/*{"requestId": "string","sessionId": "string","videoId": "string"}*///请求报文的bodyQJsonObject reqbody;reqbody["requestId"] = makeRequestId();reqbody["sessionId"] = dataCenter->getSessionId();reqbody["videoId"] = videoId;QNetworkReply* reply = sendReqToHttpServer("/HttpService/setLike",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();//解析成功,处理响应信息LOG() << "setLike请求结束,获取到了当前用户对此视频的点赞情况";});
}

假服务端处理:

//mockserver.cpp//判断用户是否对相应视频点赞过httpServer.route("/HttpService/judgeLike", [=](const QHttpServerRequest& req) {return this->judgeLike(req);});QHttpServerResponse MockServer::judgeLike(const QHttpServerRequest &request)
{//构建body信息QJsonObject reqData = QJsonDocument::fromJson(request.body()).object();LOG() << "[judgeLike] 收到 judgeLike 请求, requestId = " <<reqData["requestId"].toString() << "sessionId = " << reqData["sessionId"].toString();QJsonObject respData;respData["requestId"] = reqData["requestId"].toString();respData["errorCode"] = 0;respData["errorMsg"] = "";QJsonObject result;result["isLike"] = true;respData["result"] = result;//构建http响应报文QHttpServerResponse response(QJsonDocument(respData).toJson(),QHttpServerResponse::StatusCode::Ok);response.setHeader("Content-Type","application/json;charset=utf-8");return response;
}

老逻辑走完之后,我们需要根据服务端传回来的响应数据去改变点赞情况显示:

//videobox.cpp
bool VideoBox::eventFilter(QObject *watched, QEvent *event)
{if(watched == ui->imageBox || watched == ui->videoTitleBox){//如果是鼠标键按下才播放视频if(event->type() == QEvent::MouseButtonPress){//....//检测用户是否对当前视频点赞过connect(dataCenter,&model::DataCenter::judgeLikeDone,videoPlayer,&PlayerPage::changeLikeStatu);//....}}//其他事件继续向下传递return QObject::eventFilter(watched,event);
}//playerpage.cpp
void PlayerPage::changeLikeStatu(const QString &videoId, bool isLike)
{if(videoId != this->videoInfo.videoId){return;}likeCount = videoInfo.likeCount;//辅助判断用户在本次观看视频时其点赞情况是否更改this->isLike = isLike;if(isLike){ui->likeImageBtn->setStyleSheet("border-image : url(:/images/PlayPage/dianzan.png)");}
}

六.更新点赞数

用户在观看视频时有可能会进行点赞操作表达对视频的喜爱。所以这里我们需要处理下,因为用户可能在观看视频是多次点击点赞按钮,所以我们最后播放窗口关闭时再去进行点赞情况的更新。
请求url:

POST /HttpService/setLike 

请求参数

字段名称字段类型字段说明
requestIdstring请求ID
sessionIdstring客户端会话ID
videoIdstring视频ID

返回响应:200 OK

响应参数

字段名称字段类型字段说明
requestIdstring请求ID
errorCodeinteger错误码;0-成功
errorMsgstring错误信息

注意到我们上面判断用户是否对当前视频点赞过的添加的一个bool类型的成员变量likeCount了吗,最后如果该值不等于刚开始videobox传给我的videoinfo中的likecount时我们再更新:

//playerpage.cpp
void PlayerPage::onLikeImageBtnClicked()
{//todoif(false){Login* login = new Login();Toast::showToast("登录之后才可以点赞哦~",login);}//因为用户在观看视频时可能频繁的点击点赞按钮,所以我们在关闭窗口时再去检验点赞情况发送如有需要修改请求即可isLike = !isLike;if(isLike){likeCount++;ui->likeImageBtn->setStyleSheet("border-image : url(:/images/PlayPage/dianzan.png)");}else{likeCount--;ui->likeImageBtn->setStyleSheet("border-image : url(:/images/PlayPage/quxiaodianzan.png)");}ui->likeNum->setText(intToString(likeCount));
}void PlayerPage::playerClose()
{//判断点赞修改情况if(likeCount != videoInfo.likeCount){videoInfo.likeCount = likeCount;//不相等时说明点赞情况已修改,需要向服务端发送请求auto dataCenter = model::DataCenter::getInstance();dataCenter->setLikeAsync(videoInfo.videoId);dataCenter->updateVideoLikeNumber(videoInfo.videoId,likeCount);emit this->videoBoxUpdateLikeNum(likeCount);}//关闭窗口时直接释放当前窗口对象,避免内存占用过大this->deleteLater();
}//videobox.cpp
bool VideoBox::eventFilter(QObject *watched, QEvent *event)
{if(watched == ui->imageBox || watched == ui->videoTitleBox){//如果是鼠标键按下才播放视频if(event->type() == QEvent::MouseButtonPress){//...connect(videoPlayer,&PlayerPage::videoBoxUpdateLikeNum,this,[&](int64_t likeNum){this->videoInfo.likeCount = likeNum;ui->likeNum->setText(intToString(likeNum));});//...}}//其他事件继续向下传递return QObject::eventFilter(watched,event);
}//datacenter.h//当用户退出当前视频播放时,检验点赞情况有需要发送更新请求到服务端void setLikeAsync(const QString& videoId);void updateVideoLikeNumber(const QString& videoId,int64_t likeCount);//更新视频的点赞数
//datacenter.cpp
void DataCenter::setLikeAsync(const QString &videoId)
{netClient.setLike(videoId);
}void DataCenter::updateVideoLikeNumber(const QString &videoId, int64_t likeCount)
{for(auto& videoInfo : homeVideoList->videoInfos){if(videoInfo.videoId == videoId){videoInfo.likeCount = likeCount;break;}}
}//netclinet.cpp
void NetClient::setLike(const QString &videoId)
{/*{"requestId": "string","sessionId": "string","videoId": "string"}*///请求报文的bodyQJsonObject reqbody;reqbody["requestId"] = makeRequestId();reqbody["sessionId"] = dataCenter->getSessionId();reqbody["videoId"] = videoId;QNetworkReply* reply = sendReqToHttpServer("/HttpService/setLike",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();//解析成功,处理响应信息LOG() << "setLike请求结束,获取到了当前用户对此视频的点赞情况";});
}

假服务端的处理逻辑:

//mockserver.cpp//客户端请求修改点赞情况httpServer.route("/HttpService/setLike", [=](const QHttpServerRequest& req) {return this->setLike(req);});QHttpServerResponse MockServer::setLike(const QHttpServerRequest &request)
{//构建body信息QJsonObject reqData = QJsonDocument::fromJson(request.body()).object();LOG() << "[setLike] 收到 setLike 请求, requestId = " <<reqData["requestId"].toString() << "sessionId = " << reqData["sessionId"].toString();LOG() << "更新点赞情况的videoId:" << reqData["videoId"].toString();QJsonObject respData;respData["requestId"] = reqData["requestId"].toString();respData["errorCode"] = 0;respData["errorMsg"] = "";//构建http响应报文QHttpServerResponse response(QJsonDocument(respData).toJson(),QHttpServerResponse::StatusCode::Ok);response.setHeader("Content-Type","application/json;charset=utf-8");return response;
}

七.发送弹幕

⽤⼾在观看视频的过程中,如果⽤⼾有社交互动需求,或者视频内容引起⽤⼾的情感共鸣等,⽤⼾可能会发送弹幕,表达⾃⼰的情感或观点。发送弹幕前⽤⼾必须先登录,发送弹幕成功后,弹幕信息需同步到服务器。
请求url:

POST /HttpService/newBarrage

请求参数

字段名称字段类型字段说明
requestIdstring请求ID
sessionIdstring客户端会话ID
videoIdstring视频ID
barrageInfoobject弹幕信息
barrageContentstring弹幕内容
barrageTimeinteger弹幕时间-相对秒数

返回响应:200 OK

响应参数

字段名称字段类型字段说明
requestIdstring请求ID
errorCodeinteger错误码;0-成功
errorMsgstring错误信息
//playerpage.cpp
void PlayerPage::onBulletLaunch(const QString &bullet)
{if(isShowBullet){//...//向服务端发射弹幕auto dataCenter = model::DataCenter::getInstance();dataCenter->newBarrageAsync(videoInfo.videoId,barrageInfo);}
}//datacenter.cpp
void NetClient::newBarrage(const QString &videoId, const BulletScreenInfo &barrage)
{/*{"requestId": "string","sessionId": "string","videoId": "string","barrageInfo": {"barrageContent":"string","barrageTime": 0}}*///请求报文的bodyQJsonObject reqbody;reqbody["requestId"] = makeRequestId();reqbody["sessionId"] = dataCenter->getSessionId();reqbody["videoId"] = videoId;QJsonObject barrageInfo;barrageInfo["barrageContent"] = barrage.text;barrageInfo["barrageTime"] = barrage.playTime;reqbody["barrageInfo"] = barrageInfo;QNetworkReply* reply = sendReqToHttpServer("/HttpService/newBarrage",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();//解析成功,处理响应信息LOG() << "newBarrage请求结束,客户端的弹幕发送成功";});
}

假服务端的处理逻辑:

//mockserver.cpp//客户端发射弹幕的请求httpServer.route("/HttpService/newBarrage", [=](const QHttpServerRequest& req) {return this->newBarrage(req);});QHttpServerResponse MockServer::newBarrage(const QHttpServerRequest &request)
{//构建body信息QJsonObject reqData = QJsonDocument::fromJson(request.body()).object();LOG() << "[newBarrage] 收到 newBarrage 请求, requestId = " <<reqData["requestId"].toString() << "sessionId = " << reqData["sessionId"].toString();LOG() << "更新弹幕情况的videoId:" << reqData["videoId"].toString();QJsonObject barrageInfo = reqData["barrageInfo"].toObject();LOG() << "弹幕内容:" << barrageInfo["barrageContent"].toString() << ",视频对应时间:" << barrageInfo["barrageTime"].toInteger();QJsonObject respData;respData["requestId"] = reqData["requestId"].toString();respData["errorCode"] = 0;respData["errorMsg"] = "";//构建http响应报文QHttpServerResponse response(QJsonDocument(respData).toJson(),QHttpServerResponse::StatusCode::Ok);response.setHeader("Content-Type","application/json;charset=utf-8");return response;
}

八.我的界面部分-用户信息结构的设定

当⽤⼾点击主界⾯左侧栏我的⻚⾯切换按钮时,假设⽤⼾已经登录,此时该⻚⾯会加载当前登录⽤⼾的个⼈信息,并在界⾯上进⾏展⽰,包含⽤⼾头像、⽤⼾昵称、关注数、粉丝数、获赞数、播放数,以及⽤⼾之前上传的视频等信息,如下图所⽰:
在这里插入图片描述
注意:如果⽤⼾在播放⻚⾯观看视频时,想要查看视频上传者的信息,通过点击⽤⼾头像可以跳转到上传视频⽤⼾界⾯,此时就要获取视频上传者的个⼈信息,即获取其他⽤⼾信息。
在这里插入图片描述
为了⽅便对⽤⼾信息进⾏管理,定义如下描述⽤⼾信息结构:

//data.h
////////////////////////////
/////////用户信息的相关结构
////////////////////////////
enum RoleType{SuperAdmin = 1, // 超级管理员Admin, // 普通管理员User, // 普通⽤⼾TempUser // 临时⽤⼾
};
enum IdentityType{CUser = 1, // C 端⽤⼾BUser // B 端⽤⼾
};
class UserInfo
{public:QString userId; // ⽤⼾IDQString phoneNum; // ⽤⼾⼿机号QString nickname; // ⽤⼾昵称QList<int> roleType; // ⻆⾊类型:QList<int> identityType; // ⾝份类型:C端⽤⼾ 或 B端⽤⼾int64_t likeCount; // 点赞量int64_t playCount; // 播放量int64_t followedCount; // 关注数int64_t followerCount; // 粉丝数量int userStatus; // ⽤⼾状态int isFollowing; // 是否关注QString userMemo; // ⽤⼾备注信息QString userCTime; // ⽤⼾创建时间QString avatarFileId; // ⽤⼾头像Id// 将JSON对象转换为UserInfovoid loadUserInfo(const QJsonObject& jsonObj);// 检测⽤⼾是否为B端用户bool isBUser()const;// 检测用户是否为临时用户bool isTempUser()const;//构建临时用户对象void buildTempUser();
};//data.cpp
void UserInfo::loadUserInfo(const QJsonObject &jsonObj)
{/*"userId": "string","phoneNum": "string","nickname": "string","roleType": [3],"identityType": [1],"likeCount": 0,"playCount": 0,"followedCount": 0,"followerCount": 0,"userStatus": 0,"isFollowing": 0,"userMemo": "string","userCTime": "string","avatarFileId":"string"*///先清空之前填入的信息this->identityType.clear();this->roleType.clear();//再填入新的信息userId = jsonObj["userId"].toString(); // ⽤⼾IDphoneNum = jsonObj["phoneNum"].toString(); // ⽤⼾⼿机号nickname = jsonObj["nickname"].toString(); // ⽤⼾昵称QJsonArray roleTypeList = jsonObj["roleType"].toArray();for(int i = 0;i < roleTypeList.size();i++){roleType.append(roleTypeList[i].toInt());}QJsonArray identityTypeList = jsonObj["identityType"].toArray();for(int i = 0;i < identityTypeList.size();i++){identityType.append(identityTypeList[i].toInt());}// ⾝份类型:C端⽤⼾ 或 B端⽤⼾likeCount = jsonObj["likeCount"].toInteger(); // 点赞量playCount = jsonObj["playCount"].toInteger();; // 播放量followedCount = jsonObj["followedCount"].toInteger();; // 关注数followerCount = jsonObj["followerCount"].toInteger();; // 粉丝数量userStatus = jsonObj["userStatus"].toInt(); // ⽤⼾状态isFollowing = jsonObj["isFollowing"].toInt(); // 是否关注userMemo = jsonObj["userMemo"].toString(); // ⽤⼾备注信息userCTime = jsonObj["userCTime"].toString(); // ⽤⼾创建时间avatarFileId = jsonObj["avatarFileId"].toString(); // ⽤⼾头像Id
}bool UserInfo::isBUser() const
{for(auto& identity : identityType){if(identity == BUser){return true;}}return false;
}bool UserInfo::isTempUser() const
{for(auto& role : roleType){if(role == TempUser){return true;}}return false;
}void UserInfo::buildTempUser()
{userId = "";phoneNum = "";nickname = "临时用户";roleType.append(TempUser);identityType.append(CUser);likeCount = 0;playCount = 0;followedCount = 0;followerCount = 0;userStatus = 0;isFollowing = 0;userMemo = "";userCTime = "";avatarFileId = "";
}

让dataCenter持有⼀份⽤⼾信息的指针:

//datacenter.hvoid setMyselfUserInfo(const QJsonObject& userInfo);UserInfo* getMyselfUserInfo();//个人信息相关set与get接口void setOtherUserInfo(const QJsonObject& userInfo);UserInfo* getOtherUserInfo();//其他用户信息相关的get与set接口private:UserInfo* myselfUserInfo = nullptr;//当前用户的个人信息UserInfo* otherUserInfo = nullptr;//其他用户的个人信息//datacenter.cpp
void DataCenter::setMyselfUserInfo(const QJsonObject &userInfo)
{if(myselfUserInfo == nullptr){myselfUserInfo = new UserInfo();}myselfUserInfo->loadUserInfo(userInfo);
}UserInfo *DataCenter::getMyselfUserInfo()
{return myselfUserInfo;
}void DataCenter::setOtherUserInfo(const QJsonObject &userInfo)
{if(otherUserInfo == nullptr){otherUserInfo = new UserInfo();}otherUserInfo->loadUserInfo(userInfo);
}UserInfo *DataCenter::getOtherUserInfo()
{return otherUserInfo;
}

九.获取当前用户信息

请求url:

POST /Httpvice/getUserInfo

请求参数

字段名称字段类型字段说明
requestIdstring请求ID
sessionIdstring客户端会话ID
userIdstring用户ID

返回响应:200 OK

响应参数

字段名称字段类型字段说明
requestIdstring请求ID
errorCodeinteger错误码;0-成功
errorMsgstring错误信息
resultobject响应结果
userInfoobject用户信息
userIdstring用户ID
phoneNumstring绑定手机号
nicknamestring用户昵称
roleTypearray角色类型
identityTypearray身份类型
likeCountinteger点赞量
playCountinteger播放量
followedCountinteger关注量
followerCountinteger粉丝量
userStatusinteger用户状态
isFollowinginteger是否已关注
userMemostring用户备注信息
userCTimestring用户创建时间
avatarFileIdstring头像文件ID

如果是⽤⼾获取⾃⼰的个⼈信息, 则在 json 中不填写 userId 属性, 服务器通过会话 id 来获取当前⽤⼾信息. 获取其他⽤⼾信息时,需要传递该⽤⼾的userId。服务器可以通过userId是否为空确认获取⾃⼰的个⼈信息还是其他⽤⼾的个⼈信息。
客⼾端添加获取⾃⼰和他⼈个⼈信息请求接⼝:

//datacenter.h//客户端请求自己和其他用户信息时的请求接口void getMyselfUserInfoAsync();void getOtherUserInfoAsync(const QString& userId);signals://获取当前登录用户信息成功void getMyselfUserInfoDone();//获取其他用户信息成功void getOtherUserInfoDone();//datacenter.cpp
void DataCenter::getMyselfUserInfoAsync()
{netClient.getUserInfo("");
}void DataCenter::getOtherUserInfoAsync(const QString &userId)
{netClient.getUserInfo(userId);
}//netclinet.cpp
void NetClient::getUserInfo(const QString &userId)
{/*"requestId": "string","sessionId": "string","userId": "string",*///请求报文的bodyQJsonObject reqbody;reqbody["requestId"] = makeRequestId();reqbody["sessionId"] = dataCenter->getSessionId();reqbody["userId"] = userId;QNetworkReply* reply = sendReqToHttpServer("/HttpService/getUserInfo",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();if(userId.isEmpty()){dataCenter->setMyselfUserInfo(result["userInfo"].toObject());emit dataCenter->getMyselfUserInfoDone();}else{dataCenter->setOtherUserInfo(result["userInfo"].toObject());emit dataCenter->getOtherUserInfoDone();}LOG() << "getUserInfo请求结束,用户信息获取成功";});
}

假服务端的处理逻辑:

//mockserver.cpp//客户端获取用户信息的请求httpServer.route("/HttpService/getUserInfo", [=](const QHttpServerRequest& req) {return this->getUserInfo(req);});QHttpServerResponse MockServer::getUserInfo(const QHttpServerRequest &request)
{/*"userId": "string","phoneNum": "string","nickname": "string","roleType": [3],"identityType": [1],"likeCount": 0,"playCount": 0,"followedCount": 0,"followerCount": 0,"userStatus": 0,"isFollowing": 0,"userMemo": "string","userCTime": "string","avatarFileId":"string"*///构建body信息QJsonObject reqData = QJsonDocument::fromJson(request.body()).object();QString userId = reqData["userId"].toString();LOG() << "[getUserInfo] 收到 getUserInfo 请求, requestId = " <<reqData["requestId"].toString() << "sessionId = " << reqData["sessionId"].toString();LOG() << "收到客户端的用户信息请求,userId为:" << userId;QJsonObject respData;respData["requestId"] = reqData["requestId"].toString();respData["errorCode"] = 0;respData["errorMsg"] = "";QJsonObject result;QJsonObject userInfo;if(userId == ""){//获取用户个人信息-构造一批假数据userInfo["userId"] = "10001";userInfo["phoneNum"] = "19000000001";userInfo["nickname"] = "114514";QJsonArray roleType;roleType.append(3);//普通用户userInfo["roleType"] = roleType;QJsonArray identityType;identityType.append(1);//C端用户userInfo["identityType"] = identityType;userInfo["likeCount"] = 114;//获赞数userInfo["playCount"] = 114;//播放量userInfo["followedCount"] = 1919;//关注数userInfo["followerCount"] = 1919;//粉丝数userInfo["userStatus"] = 0;//用户状态userInfo["isFollowing"] = 0;//是否关注userInfo["userMemo"] = "";//管理界面的备注信息userInfo["userCTime"] = "2025-1-14";//用户的注册日期userInfo["avatarFileId"] = "1000";//用户的头像路径}else{//获取其他用户的个人信息userInfo["userId"] = userId;userInfo["phoneNum"] = "19000000002";userInfo["nickname"] = "1919180";QJsonArray roleType;roleType.append(1);//超级管理员userInfo["roleType"] = roleType;QJsonArray identityType;identityType.append(2);//B端用户userInfo["identityType"] = identityType;userInfo["likeCount"] = 1919180;//获赞数userInfo["playCount"] = 1919180;//播放量userInfo["followedCount"] = 114;//关注数userInfo["followerCount"] = 114;//粉丝数userInfo["userStatus"] = 0;//用户状态userInfo["isFollowing"] = 0;//是否关注userInfo["userMemo"] = "超级管理员";//管理界面的备注信息userInfo["userCTime"] = "2025-1-1";//用户的注册日期userInfo["avatarFileId"] = "2000";//用户的头像路径}result["userInfo"] = userInfo;respData["result"] = result;//构建http响应报文QHttpServerResponse response(QJsonDocument(respData).toJson(),QHttpServerResponse::StatusCode::Ok);response.setHeader("Content-Type","application/json;charset=utf-8");return response;
}

此时我们在客户端的界面处理相关逻辑即可:

//myselfwidget.hvoid getMyselfUserInfo();//获取当前登录用户的个人信息void loadMyself();//当点击我的页面时,显示当前登录用户的个人信息//获取用户信息成功收到响应void getMyselfUserInfoDone();
private:// 绑定信号槽void connectSignalAndSlots();QString userId;//判断当前展示的是当前登录用户信息还是其他用户信息
//myselfwidget.cpp
void MyselfWidget::getMyselfUserInfo()
{auto dataCenter = model::DataCenter::getInstance();if(dataCenter->getMyselfUserInfo() == nullptr || dataCenter->getMyselfUserInfo()->isTempUser()){//此时用户信息还未获取,先获取dataCenter->getMyselfUserInfoAsync();}else{//此时用户信息已经获取,直接更新界面getMyselfUserInfoDone();}
}void MyselfWidget::loadMyself()
{getMyselfUserInfo();//加载用户上传视频的信息//todoui->avatarBtn->isHideMask(true);ui->avatarBtn->setEnabled(true);
}void MyselfWidget::getMyselfUserInfoDone()
{auto dataCenter = model::DataCenter::getInstance();auto myselfInfo = dataCenter->getMyselfUserInfo();auto limePlayer = LimePlayer::getInstance();limePlayer->showSystemPageBtn(false);if(myselfInfo->isTempUser()){//当前登录用户为临时用户,需要隐藏登录用户相关的控件ui->avatarBtn->setIcon(QIcon(":/image/myself/defaultAvatar.png"));ui->nicknameBtn->setText("点击登录");ui->nicknameBtn->adjustSize();//根据文本调整控件大小ui->avatarBtn->setEnabled(false);ui->nicknameBtn->setEnabled(true);hideWidget(true);return;}else if(myselfInfo->isBUser()){//说明当前登录用户为B端用户,需要显示管理界面limePlayer->showSystemPageBtn(true);}//普通用户及以上权限用户的共同处理部分hideWidget(false);ui->nicknameBtn->setText(myselfInfo->nickname);ui->nicknameBtn->adjustSize();QRect rect = ui->nicknameBtn->geometry();//调整设置按钮的位置ui->settingBtn->move(rect.x() + rect.width() + 8, ui->settingBtn->geometry().y());ui->attentionCountLabel->setText(intToString2(myselfInfo->followedCount));ui->playCountLabel->setText(intToString2(myselfInfo->playCount));ui->likeCountLabel->setText(intToString2(myselfInfo->likeCount));ui->fansCountLabel->setText(intToString2(myselfInfo->followerCount));ui->attentionBtn->hide();ui->myVideolabel->setText("我的视频");ui->nicknameBtn->setEnabled(false);if(myselfInfo->avatarFileId.isEmpty()){//如果用户之前没有设置过头像,那么使用默认头像ui->avatarBtn->setIcon(QIcon(":/images/myself/defaultAvatar.png"));}else{//使用用户设置的头像dataCenter->downloadPhotoAsync(myselfInfo->avatarFileId);}userId = "";
}void MyselfWidget::hideWidget(bool isHide)
{// 临时⽤⼾需要隐藏界⾯上控件,⾮临时⽤⼾显⽰控件if(isHide){ui->attentionBtn->hide();ui->attentionCountLabel->hide();ui->attentionLabel->hide();ui->fansLabel->hide();ui->fansCountLabel->hide();ui->likeLabel->hide();ui->likeCountLabel->hide();ui->playLabel->hide();ui->playCountLabel->hide();ui->settingBtn->hide();ui->quitBtn->hide();ui->uploadVideoBtn->hide();// scrollArea隐藏后,控件的位置仍旧保留QSizePolicy sizePolicy = ui->scrollArea->sizePolicy();sizePolicy.setRetainSizeWhenHidden(true);ui->scrollArea->setSizePolicy(sizePolicy);ui->scrollArea->hide();sizePolicy = ui->titleBar->sizePolicy();sizePolicy.setRetainSizeWhenHidden(true);ui->titleBar->setSizePolicy(sizePolicy);ui->titleBar->hide();}else{ui->attentionBtn->show();ui->attentionCountLabel->show();ui->attentionLabel->show();ui->fansLabel->show();ui->fansCountLabel->show();ui->likeLabel->show();ui->likeCountLabel->show();ui->playLabel->show();ui->playCountLabel->show();ui->settingBtn->show();ui->quitBtn->show();ui->uploadVideoBtn->show();ui->scrollArea->show();ui->titleBar->show();}
}void MyselfWidget::connectSignalAndSlots()
{connect(ui->settingBtn,&QPushButton::clicked,this,&MyselfWidget::onModifyMyselfClicked);connect(ui->uploadVideoBtn,&QPushButton::clicked,this,&MyselfWidget::onUpLoadVideoBtnClicked);auto dataCenter = model::DataCenter::getInstance();connect(dataCenter,&model::DataCenter::getMyselfUserInfoDone,this,&MyselfWidget::getMyselfUserInfoDone);connect(dataCenter,&model::DataCenter::downloadPhotoDone,this,&MyselfWidget::setUserAvatar);//当用户头像下载成功之后设置用户头像
}void MyselfWidget::setUserAvatar(const QString &fileId, const QByteArray &photoData)
{auto myselfInfo = model::DataCenter::getInstance()->getMyselfUserInfo();if(myselfInfo != nullptr && fileId == myselfInfo->avatarFileId){ui->avatarBtn->setIcon(makeCircleIcon(photoData,ui->avatarBtn->height()/2));}
}//如果非b端用户,则需要通知主界面隐藏系统界面按钮:
//limeplayer.cpp
void LimePlayer::showSystemPageBtn(bool isShow)
{if(isShow){ui->sysPageBtn->show();}else{QSizePolicy sizePolicy = ui->sysPageBtn->sizePolicy();sizePolicy.setRetainSizeWhenHidden(true);ui->sysPageBtn->setSizePolicy(sizePolicy);ui->sysPageBtn->hide();}
}void LimePlayer::switchPage(ButtonType buttonType)
{else if(buttonType == MyPageBtn){//...ui->myPage->loadMyself();}
}

十.上传与修改用户头像

⽤⼾登录之后,可以通过点击⽤⼾头像按钮来修改头像,修改图像需要:
• 从磁盘获取新图⽚作为⽤⼾头像
• 上传⽤⼾图像到服务器
• 图⽚上传成功后,⽤返回的图⽚id修改服务器中⽤⼾信息中头像id
• ⽤⼾头像修改成功后,⽤新头像id重新下载图⽚,并将图⽚设置到控件中

其中从磁盘获取⽤⼾图像的操作已经在uploadAvatarBtnClicked函数中完成完成
请求URL: POST /HttpService/uploadPhoto?requestId=xxx&sessionId=xxx
请求参数

Content-Type: application/octet-stream

字段名称字段类型字段说明
requestIdstring请求ID
sessionIdstring客户端会话ID
bodybinary文件数据

返回响应:200 OK

响应参数

字段名称字段类型字段说明
requestIdstring请求ID
errorCodeinteger错误码;0-成功
errorMsgstring错误信息
resultobject响应结果
fileIdstring文件ID

客⼾端新增上传图⽚请求:

//avatarbutton.cpp
void AvatarButton::onAvatarBtnClicked()
{//裁剪并设置用户上传的头像//this->setIcon(makeCircleIcon(byteArray,30));//上传图片数据到服务端//...dataCenter->uploadPhotoAsync(byteArray);
}//datacenter.h//客户端请求修改自己头像的请求void uploadPhotoAsync(const QByteArray& photoData);signals://向服务端上传图片请求成功响应void uploadPhotoDone(const QString& fileId);//datacenter.cpp
void DataCenter::uploadPhotoAsync(const QByteArray &photoData)
{netClient.uploadPhoto(photoData);
}//netclient.cpp
void NetClient::uploadPhoto(const QByteArray &photoData)
{// 1. 构造请求QString queryString;queryString += "requestId=";queryString += makeRequestId();queryString += "&";queryString += "sessionId=";queryString += dataCenter->getSessionId();// 2. 发送请求QNetworkRequest httpReq;httpReq.setUrl(QUrl(HTTP_URL + "/HttpService/uploadPhoto?" + queryString));httpReq.setHeader(QNetworkRequest::ContentTypeHeader, "application/octetstream");QNetworkReply* reply = netClientManager.post(httpReq,photoData);// 3. 异步处理响应connect(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();emit dataCenter->uploadPhotoDone(result["fileId"].toString());LOG() << "uploadPhoto请求结束,图片上传成功,fileId为:" << result["fileId"].toString();});
}

假服务端处理逻辑:

//mockserver.cpp//客户端上传图片的请求httpServer.route("/HttpService/uploadPhoto",[=](const QHttpServerRequest& req){return this->uploadPhoto(req);});QHttpServerResponse MockServer::uploadPhoto(const QHttpServerRequest &request)
{// 解析查询字符串QUrlQuery query(request.url());QString requestId = query.queryItemValue("requestId");QString sessionId = query.queryItemValue("sessionId");LOG() << "[uploadPhoto] 收到 uploadPhoto 请求, requestId=" << requestId << "sessionId =" << sessionId;idPathTable[5000] = "/images/temp.png";// 构造图⽚路径QDir dir(QDir::currentPath());dir.cdUp();dir.cdUp();writeByteArrayToFile(dir.absolutePath() + "/images/temp.png",request.body());// 构造响应json报文QJsonObject respBody;respBody["requestId"] = requestId;respBody["errorCode"] = 0;respBody["errorMsg"] = "";QJsonObject result;result["fileId"] = "5000";respBody["result"] = result;//构建http响应报文QHttpServerResponse response(QJsonDocument(respBody).toJson(),QHttpServerResponse::StatusCode::Ok);response.setHeader("Content-Type","application/json;charset=utf-8");return response;
}

客户端收到新头像文件的id时,再根据此id向服务端获取新的头像:
请求URL: POST /HttpService/setAvatar
请求参数

字段名称字段类型字段说明
requestIdstring请求ID
sessionIdstring客户端会话ID
fileIdstring文件ID

返回响应:200 OK

响应参数

字段名称字段类型字段说明
requestIdstring请求ID
errorCodeinteger错误码;0-成功
errorMsgstring错误信息
//myselfwidget.h//图⽚上传分两个阶段处理:①上传图⽚⽂件 ②修改图⽚void uploadPhotoDone1(const QString& fileId);void uploadPhotoDone2();//myselfwidget.cpp
void MyselfWidget::connectSignalAndSlots()
{connect(ui->settingBtn,&QPushButton::clicked,this,&MyselfWidget::onModifyMyselfClicked);connect(ui->uploadVideoBtn,&QPushButton::clicked,this,&MyselfWidget::onUpLoadVideoBtnClicked);auto dataCenter = model::DataCenter::getInstance();connect(dataCenter,&model::DataCenter::getMyselfUserInfoDone,this,&MyselfWidget::getMyselfUserInfoDone);connect(dataCenter,&model::DataCenter::downloadPhotoDone,this,&MyselfWidget::setUserAvatar);//当用户头像下载成功之后设置用户头像connect(dataCenter,&model::DataCenter::uploadPhotoDone,this,&MyselfWidget::uploadPhotoDone1);//服务端获取到用户上传的图片数据之后,返回新的头像idconnect(dataCenter,&model::DataCenter::setAvatarDone,this,&MyselfWidget::uploadPhotoDone2);
}void MyselfWidget::uploadPhotoDone1(const QString &fileId)
{//首先更新当前用户的头像Idauto dataCenter = model::DataCenter::getInstance();dataCenter->setAvatarId(fileId);//发送设置用户头像请求dataCenter->setAvatarAsync(fileId);
}void MyselfWidget::uploadPhotoDone2()
{//服务端更新头像文件ID完毕,重新申请设置当前客户端展示的用户头像auto dataCenter = model::DataCenter::getInstance();auto myselfInfo = dataCenter->getMyselfUserInfo();dataCenter->downloadPhotoAsync(myselfInfo->avatarFileId);
}//datacenter.cpp
void DataCenter::setAvatarId(const QString &fileId)
{//更新当前用户头像对应的文件Idthis->myselfUserInfo->avatarFileId = fileId;
}void DataCenter::setAvatarAsync(const QString &fileId)
{netClient.setAvatar(fileId);
}//netclient.cpp
void NetClient::setAvatar(const QString &fileId)
{/*"requestId": "string","sessionId": "string","setAvatar": "string",*///请求报文的bodyQJsonObject reqbody;reqbody["requestId"] = makeRequestId();reqbody["sessionId"] = dataCenter->getSessionId();reqbody["fileId"] = fileId;QNetworkReply* reply = sendReqToHttpServer("/HttpService/setAvatar",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();//解析成功,处理响应信息emit dataCenter->setAvatarDone();LOG() << "setAvatar请求结束,用户信息获取成功";});
}

假服务端处理逻辑:

//mockserver.cpp//客户端请求更新当前用户头像文件Id的请求httpServer.route("/HttpService/setAvatar",[=](const QHttpServerRequest& req){return this->setAvatar(req);});QHttpServerResponse MockServer::setAvatar(const QHttpServerRequest &request)
{//构建body信息QJsonObject reqData = QJsonDocument::fromJson(request.body()).object();LOG() << "[setAvatar] 收到 setAvatar 请求, requestId = " <<reqData["requestId"].toString() << "sessionId = " << reqData["sessionId"].toString();LOG() << "更新用户头像文件Id的fileId:" << reqData["fileId"].toString();QJsonObject respData;respData["requestId"] = reqData["requestId"].toString();respData["errorCode"] = 0;respData["errorMsg"] = "";//构建http响应报文QHttpServerResponse response(QJsonDocument(respData).toJson(),QHttpServerResponse::StatusCode::Ok);response.setHeader("Content-Type","application/json;charset=utf-8");return response;
}

十一.获取用户视频列表

其实逻辑与之前获取首页视频列表差不多,不过因为考虑到管理界面的原因,我们需要新增几个字段:

//data.h
////////////////////////////
/////////单条视频的信息结构
////////////////////////////
// 视频状态
enum VideoStatus{noStatus = 0, // ⽆状态waitForChecking, // 待审核putaway, // 审核通过 or 上架reject, // 审核驳回discard // 已下架
};
class VideoInfo{
public://...int videoStatus; // 视频状态QString checkerId; // 审核者idQString checkerName; // 审核者昵称QString checkerAvatar; // 审核者⽤⼾头像id//加载json对象到单个VideoInfo中void loadJsonResultToVideoInfo(const QJsonObject& result);
};
//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();//以下为给管理员看的信息videoStatus = result["videoStatus"].toInt();checkerId = result["checkerId"].toString();checkerName = result["checkerName"].toString();checkerAvatar = result["checkerAvatar"].toString();
}

前面的代码不需要修改,因为首页视频列表也不需要新增的这几个字段。
那么接下来我们的思路就是,在datacenter中新增一个用户视频列表。跟获取用户信息部分一样,传入的userId为空则标识获取当前登录用户的视频列表,不为空则是获取其他用户视频列表。
请求url:POST /HttpService/userVideoList
请求参数

字段名称字段类型字段说明
requestIdstring请求ID
sessionIdstring客户端会话ID
userIdstring用户ID
pageIndexinteger页码
pageCountinteger每页条目数量

返回响应:200 OK 按时间排序

响应参数

字段名称字段类型字段说明
requestIdstring请求ID
errorCodeinteger错误码;0-成功
errorMsgstring错误信息
resultobject响应结果
totalCountinteger总数量
videoListarray视频列表
videoIdstring视频ID
userIdstring所属用户ID
userAvatarIdstring用户头像ID
nicknamestring所属用户名称
checkerIdstring审核者用户ID
checkerAvatarstring审核者用户头像ID
checkerNamestring审核者用户名称
videoFileIdstring视频文件ID
photoFileIdstring封面文件ID
likeCountinteger点赞数量
playCountinteger播放数量
videoSizeinteger视频大小
videoDescstring视频简介
videoTitlestring视频标题
videoDurationinteger视频时长
videoUpTimestring视频上架时间
videoStatusinteger视频状态
//datacenter.h//用户界面的视频列表VideoList* userVideoList = nullptr;//客户端请求用户视频列表的请求-userId为空请求当前用户,不为空请求其他用户void getUserVideoListAsync(const QString& userId,int pageIndex);//datacenter.cpp
void DataCenter::getUserVideoListAsync(const QString &userId, int pageIndex)
{netClient.getUserVideoList(userId,pageIndex);
}//netclient.cpp
void NetClient::getUserVideoList(const QString &userId, int pageIndex)
{/** {"requestId": "string","sessionId": "string","userId": "string","pageIndex": 0,"pageCount": 0}*///请求报文的bodyQJsonObject reqbody;reqbody["requestId"] = makeRequestId();reqbody["sessionId"] = dataCenter->getSessionId();reqbody["userId"] = userId;reqbody["pageIndex"] = pageIndex;reqbody["pageCount"] = model::VideoList::PAGE_COUNT;QNetworkReply* reply = sendReqToHttpServer("/HttpService/userVideoList",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->setUserVideoList(result);LOG() << "getUserVideoList请求结束,获取用户视频列表请求成功!";emit dataCenter->getUserVideoListDone(userId);});
}

假服务端的处理逻辑:

//mockserver.cpp//客户端用户视频列表的获取请求httpServer.route("/HttpService/userVideoList",[=](const QHttpServerRequest& req){return this->getUserVideoList(req);});
QHttpServerResponse MockServer::getUserVideoList(const QHttpServerRequest &request)
{//构建body信息QJsonObject reqData = QJsonDocument::fromJson(request.body()).object();QString uploadUserId = reqData["userId"].toString();LOG() << "[getUserVideoList] 收到 getUserVideoList 请求, requestId = " <<reqData["requestId"].toString() << "sessionId = " << reqData["sessionId"].toString() << "userId = " << uploadUserId;//获取页号与获取视频数目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;if(uploadUserId.isEmpty()){QJsonArray videoList;//构造假数据int videoId = 10000;int userId = 10000;int resourceId = 1000;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(resourceId++);videoJsonObj["likeCount"] = 9867;videoJsonObj["playCount"] = 2105;videoJsonObj["videoSize"] = 10240;//videoJsonObj["videoDesc"] = "『Bad Apple!! feat.SEKAI』\n25時、ナイトコードで。 × 初音ミク\n作詞:Haruka-作曲:ZUN(上海アリス幻樂団)-編曲:ビートまりお×まろん、まらしぃ、Masayoshi Minoshima";videoJsonObj["videoDesc"] = "111";//videoJsonObj["videoTitle"] = "【25時、ナイトコードで。 × 初音ミク】Bad Apple!!【2DMV/世界计划 × 东方project 联动收录曲】";videoJsonObj["videoTitle"] = "111";videoJsonObj["videoDuration"] = 190;videoJsonObj["videoUpTime"] = "2024-05-04 17:11:11";//假审核人员数据videoJsonObj["videoStatus"] = 0;videoJsonObj["checkerId"] = "114514";videoJsonObj["checkerName"] = "咻114514";videoJsonObj["checkerAvatar"] = "5000";videoList.append(videoJsonObj);}result["videoList"] = videoList;}else{QJsonArray videoList;//构造假数据int videoId = 20000;int userId = 20000;int resourceId = 2000;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"] = "咻";videoJsonObj["userAvatarId"] = QString::number(resourceId++);videoJsonObj["photoFileId"] = QString::number(resourceId++);videoJsonObj["videoFileId"] = QString::number(resourceId++);videoJsonObj["likeCount"] = 9867;videoJsonObj["playCount"] = 2105000;videoJsonObj["videoSize"] = 10240;videoJsonObj["videoDesc"] = "中文翻译取自巴哈姆特 - 月勳 / 星櫻@翻譯委託開放 / 榎宮月 / 三無氣體 | b站 - 萌萌哒汪帕 \n音频取自:砂塚タカシ\n宵崎奏 (CV:楠木灯)";videoJsonObj["videoTitle"] = "25時、ナイトコードで。ANVO专 - 宵崎奏 | 中字 - Cutlery";videoJsonObj["videoDuration"] = 237;videoJsonObj["videoUpTime"] = "2024-05-10 21:46:59";//假审核人员数据videoJsonObj["videoStatus"] = 0;videoJsonObj["checkerId"] = "114514";videoJsonObj["checkerName"] = "咻114514";videoJsonObj["checkerAvatar"] = "5000";videoList.append(videoJsonObj);}result["videoList"] = videoList;}respData["result"] = result;//构建http响应报文QHttpServerResponse response(QJsonDocument(respData).toJson(),QHttpServerResponse::StatusCode::Ok);response.setHeader("Content-Type","application/json;charset=utf-8");return response;
}

客户端对收到的用户视频列表进行展示:

//myselfwidget.cpp
void MyselfWidget::getUserVideoListDone(const QString &userId)
{auto* dataCenter = model::DataCenter::getInstance();auto userVideoList = dataCenter->getUserVideoListPtr();//每一行显示4个视频const int rowCount = 4;for (int i = ui->layout->count(); i < userVideoList->getVideoCount(); i++){int row = i / rowCount;int col = i % rowCount;VideoBox* videoBox = new VideoBox(userVideoList->videoInfos[i]);ui->layout->addWidget(videoBox, row, col);}//对视频列表的pageIndex进行++,方便下一次获取视频列表userVideoList->pageIndex++;
}//发射获取用户视频列表请求:
void MyselfWidget::getUserVideoList(const QString &userId, int pageIndex)
{auto* dataCenter = model::DataCenter::getInstance();auto userVideoList = dataCenter->getUserVideoListPtr();if(pageIndex == 1){//说明此时有可能是切换不同用户界面显示了,所以要清空界面中展示的视频clearVideoList();}dataCenter->getUserVideoListAsync(userId, pageIndex);
}void MyselfWidget::loadMyself()
{getMyselfUserInfo();//加载用户上传视频的信息getUserVideoList("",1);ui->avatarBtn->isHideMask(true);ui->avatarBtn->setEnabled(true);
}//获取下一页视频列表:
void MyselfWidget::clearVideoList()
{auto dataCenter = model::DataCenter::getInstance();auto userVideoList = dataCenter->getUserVideoListPtr();//清空用户视频列表中的数据userVideoList->clearVideoList();//清空我的界面中的视频数据QLayoutItem* VideoItem;while ((VideoItem = ui->layout->takeAt(0)) != nullptr) {// 先删除item中的widget(如果有的话)if (QWidget* widget = VideoItem->widget()) {delete widget;}// 然后删除item本身delete VideoItem;}
}void MyselfWidget::onSrcollAreaValueChanged(int value)
{if(value == 0){return;}// 正在更新视频,再出发该信号时选择忽略,将以此更新的视频信息显⽰到界⾯后再处理下⼀次更新if(value == ui->scrollArea->verticalScrollBar()->maximum()){//继续获取下一页视频的数据auto dataCenter = model::DataCenter::getInstance();auto userVideListPtr = dataCenter->getUserVideoListPtr();dataCenter->getUserVideoListAsync(userId, userVideListPtr->getPageIndex());}
}

十二.删除视频

我的⻚⾯中,当前登录⽤⼾可以通过点击视频框中的 … 按钮,删除之前上传的视频。
注意,… 按钮只有在我的⻚⾯中使⽤的VideoBox中才显⽰,⾸⻚和其他⽤⼾⻚⾯中的VideoBox中…都是隐藏的,即⾸⻚和其他⻚⾯中的视频是不能删除的。
在VideoBox中,设置删除按钮的隐藏和显式⽅式,并给该按钮绑定槽函数,当按钮点击时发射信号通过我的⻚⾯,删除列表中的视频。

//videobox.cpp
void VideoBox::showDelBtn(bool isshow)
{if(isshow){ui->delVideoBtn->show();}else{ui->delVideoBtn->hide();}
}void VideoBox::onDelBtnClicked()
{// 定义菜单的样式QString style = "QMenu { ""background-color:#FFFFFF;""border:none;""border-radius: 6px;""padding: 0; }";style += "QMenu::item { ""background-color:#FFFFFF;""border: none; ""border-radius: 6px;""min-width: 50px;""min-height: 32px;""font-size: 12px;""color: #222222;""padding-left: 24px;}";style += "QMenu::item:selected { ""background-color: rgb(62, 206, 254); ""color: #FFFFFF; }";QMenu menu(this);menu.setStyleSheet(style);// 要想让 QMenu 圆⻆⽣效, 需要设置下列两步操作1.去掉窗⼝框架和阴影2.设置为透明窗口menu.setWindowFlags(menu.windowFlags() | Qt::FramelessWindowHint | Qt::NoDropShadowWindowHint);menu.setAttribute(Qt::WA_TranslucentBackground, true);// 添加菜单项menu.addAction("删除");// 在鼠标点击位置弹出上下文菜单QPoint point = QCursor::pos();QAction* action = menu.exec(point);//action为用户选择的指定菜单项对应的QAction对象的指针;如果用户取消菜单则返回nullptrif (action == nullptr) {return;}if (action->text() == "删除") {LOG() << "删除视频: " << videoInfo.videoId;//向服务器发射删除视频的信号emit deleteVideo(videoInfo.videoId);}
}//myselfwidget.cpp
void MyselfWidget::getUserVideoListDone(const QString &userId)
{for (int i = ui->layout->count(); i < userVideoList->getVideoCount(); i++){//...if(userId.isEmpty())connect(videoBox,&VideoBox::deleteVideo,this,&MyselfWidget::delMyVideo);if(userId.isEmpty()){videoBox->showDelBtn(true);}//...}
}

接下来便需要告诉服务端我要删除视频,需要注意的时删除视频时,传⼊的是视频ID,⽽不是视频⽂件ID,因为需要删除数据库的视频元信息。
请求url:POST /HttpService/removeVideo
请求参数

字段名称字段类型字段说明
requestIdstring请求ID
sessionIdstring客户端会话ID
videoIdstring视频ID

返回响应:200 OK

响应参数

字段名称字段类型字段说明
requestIdstring请求ID
errorCodeinteger错误码;0-成功
errorMsgstring错误信息
//myselfwidget.cpp
void MyselfWidget::delMyVideo(const QString &videoId)
{auto dataCenter = model::DataCenter::getInstance();dataCenter->deleteVideoAsync(videoId);
}void MyselfWidget::onDeleteVideoDone()
{//这里必定是当前用户自己的视频被删除,所以不使用userIdgetUserVideoList("",1);
}//datacenter.cpp
void DataCenter::deleteVideoAsync(const QString &videoId)
{netClient.deleteVideo(videoId);
}//netclient.cpp
void NetClient::deleteVideo(const QString &videoId)
{//请求报文的bodyQJsonObject reqbody;reqbody["requestId"] = makeRequestId();reqbody["sessionId"] = dataCenter->getSessionId();reqbody["videoId"] = videoId;QNetworkReply* reply = sendReqToHttpServer("/HttpService/removeVideo",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();//解析成功,处理响应信息emit dataCenter->deleteVideoDone();LOG() << "removeVideo请求结束,服务端已将对应视频删除了";});
}

假服务端的处理逻辑:

//mockserver.cpp//客户端请求删除自己当前登录用户视频的请求httpServer.route("/HttpService/removeVideo",[=](const QHttpServerRequest& req){return this->deleteVideo(req);});QHttpServerResponse MockServer::deleteVideo(const QHttpServerRequest &request)
{QJsonObject reqData = QJsonDocument::fromJson(request.body()).object();LOG() << "[deleteVideo] 收到 deleteVideo 请求, requestId = " <<reqData["requestId"].toString() << "sessionId = " << reqData["sessionId"].toString();LOG() << "删除对应的视频,视频Id为:" << reqData["videoId"].toString();QJsonObject respData;respData["requestId"] = reqData["requestId"].toString();respData["errorCode"] = 0;respData["errorMsg"] = "";//构建http响应报文QHttpServerResponse response(QJsonDocument(respData).toJson(),QHttpServerResponse::StatusCode::Ok);response.setHeader("Content-Type","application/json;charset=utf-8");return response;
}

十三.获取其他用户信息

目前我们只有一个地方就是播放器界面的头像按钮,需要点击之后跳转到其他用户信息界面:

//playerpage.cpp
void PlayerPage::onAvatarBtnClicked()
{LimePlayer::getInstance()->showUserInfoOnPage(videoInfo.userId);playerClose();//关闭当前窗口
}//limeplayer.cpp
void LimePlayer::showUserInfoOnPage(const QString &userId)
{//切换界面到当前对应的userId的用户个人信息界面ui->stackedWidget->setCurrentIndex(MyPageBtn);ui->myPage->loadOtherUser(userId);
}

接下来不需要新的请求,直接使用我们前面写好的请求即可获取其他用户信息:

//myselfwidget.cpp
void MyselfWidget::loadOtherUser(const QString &userId)
{getOtherUserInfo(userId);//加载当前用户上传视频的信息getUserVideoList(userId,1);ui->avatarBtn->isHideMask(false);ui->avatarBtn->setEnabled(false);
}void MyselfWidget::getOtherUserInfo(const QString &userId)
{auto dataCenter = model::DataCenter::getInstance();auto otherUserInfo = dataCenter->getOtherUserInfo();if(otherUserInfo == nullptr || otherUserInfo->userId != userId){//此时对应userId的用户信息还未获取,先获取dataCenter->getOtherUserInfoAsync(userId);}else{//此时用户信息已经获取过了,直接更新界面getOtherUserInfoDone();}
}void MyselfWidget::getOtherUserInfoDone()
{auto dataCenter = model::DataCenter::getInstance();auto otherUserInfo = dataCenter->getOtherUserInfo();userId = otherUserInfo->userId;//更新用户Id//因为这里是其他用户,是能够从服务端获取其对应信息的,所以必定为非临时用户hideWidget(false);ui->nicknameBtn->setText(otherUserInfo->nickname);ui->nicknameBtn->adjustSize();QRect rect = ui->nicknameBtn->geometry();//调整设置按钮的位置ui->settingBtn->move(rect.x() + rect.width() + 8, ui->settingBtn->geometry().y());ui->attentionCountLabel->setText(intToString2(otherUserInfo->followedCount));ui->playCountLabel->setText(intToString2(otherUserInfo->playCount));ui->likeCountLabel->setText(intToString2(otherUserInfo->likeCount));ui->fansCountLabel->setText(intToString2(otherUserInfo->followerCount));ui->quitBtn->hide();ui->myVideolabel->setText("TA的视频");ui->nicknameBtn->setEnabled(false);ui->uploadVideoBtn->hide();if(otherUserInfo->avatarFileId.isEmpty()){//如果用户之前没有设置过头像,那么使用默认头像ui->avatarBtn->setIcon(QIcon(":/images/myself/defaultAvatar.png"));}else{//使用用户设置的头像dataCenter->downloadPhotoAsync(otherUserInfo->avatarFileId);}
}void MyselfWidget::setUserAvatar(const QString &fileId, const QByteArray &photoData)
{auto otherUserInfo = model::DataCenter::getInstance()->getOtherUserInfo();if(otherUserInfo != nullptr && fileId == otherUserInfo->avatarFileId){ui->avatarBtn->setIcon(makeCircleIcon(photoData,ui->avatarBtn->height()/2));}
}

十四.关注与取消关注

当发布视频⽤⼾的关注状态为被关注或者被取消关注时,关注按钮上⽂本和样式是不⼀样的,将来需要根据按钮的点击设置关注或取消关注,为简单起⻅,下⾯对关注按钮简单进⾏封装。

//myselfwidget.h
///////////////////////////////////////AttentionButton部分
class AttentionButton : public QPushButton
{Q_OBJECT
public:explicit AttentionButton(QWidget* parent = nullptr);bool attentionStatu();//当前处于关注还是未关注状态void changeAttentionStatus(bool attentionStatu);//当用户点击按钮之后改变按钮状态
private:bool isAttention;
};//myselfwidget.cpp
///////////////////////////////////////AttentionButton部分
AttentionButton::AttentionButton(QWidget *parent):QPushButton(parent),isAttention(false)
{changeAttentionStatus(false);
}bool AttentionButton::attentionStatu()
{return isAttention;
}void AttentionButton::changeAttentionStatus(bool attentionStatu)
{this->isAttention = attentionStatu;if(attentionStatu){this->setText("已关注");this->setStyleSheet("QPushButton{""background-color: transparent;""color: #3ECEFE;""font-size : 14px;""border-radius: 18px;""border: 1px solid #3ECEFE;""padding-left: 13px;""padding-right: 13px;}");this->setIconSize(QSize(24, 24));this->setIcon(QIcon(":/images/myself/guanzhu.png"));}else{this->setText("关注");this->setStyleSheet("border-radius : 18px;""border : 1px solid #DDDDDD;""color : #999999;""font-size : 14px;");//取消图标this->setIcon(QIcon());}
}

将关注按钮类型提升为AttentionButton,就能看到按钮的效果。
在其他⽤⼾界⾯显⽰时,关注按钮上的⽂本刚开始并不⼀定是"关注",此处需要根据当前⽤⼾对视频上传⽤的实际关注情况来进⾏设置。

void MyselfWidget::getOtherUserInfoDone()
{//...//判断当前登录用户是否关注当前界面展示的用户ui->attentionBtn->changeAttentionStatus(otherUserInfo->isFollowing == 1);//...
}

完成之后,给关注按钮绑定槽函数,根据⽤⼾的点击发送关注或取消关注请求。
新增关注请求url:POST /HttpService/newAttention
请求参数

字段名称字段类型字段说明
requestIdstring请求ID
sessionIdstring客户端会话ID
userIdstring用户ID

返回响应:200 OK

响应参数

字段名称字段类型字段说明
requestIdstring请求ID
errorCodeinteger错误码;0-成功
errorMsgstring错误信息

取消关注请求url:POST /HttpService/delAttention
请求参数

字段名称字段类型字段说明
requestIdstring请求ID
sessionIdstring客户端会话ID
userIdstring用户ID

返回响应:200 OK

响应参数

字段名称字段类型字段说明
requestIdstring请求ID
errorCodeinteger错误码;0-成功
errorMsgstring错误信息
//myselfwidget.h//当关注状态改变时触发void attentionChanged();void newAttentionDone();void delAttentionDone();private:Login* login = nullptr;
//myselfwidget.cpp
void MyselfWidget::attentionChanged()
{auto dataCenter = model::DataCenter::getInstance();if(dataCenter->getMyselfUserInfo()->isTempUser()){if(login == nullptr)login = new Login();Toast::showToast("还没有登录~",login);return;}bool isAttention = !ui->attentionBtn->attentionStatu();ui->attentionBtn->changeAttentionStatus(isAttention);auto otherUserInfo = dataCenter->getOtherUserInfo();if(isAttention){dataCenter->newAttentionAsync(otherUserInfo->userId);}else{dataCenter->delAttentionAsync(otherUserInfo->userId);}
}void MyselfWidget::newAttentionDone()
{//更新数据中心的数据同时更新界面中的数据auto dataCenter = model::DataCenter::getInstance();auto myselfInfo = dataCenter->getMyselfUserInfo();auto otherInfo = dataCenter->getOtherUserInfo();myselfInfo->followedCount++;otherInfo->followerCount++;ui->fansCountLabel->setText(intToString2(otherInfo->followerCount));
}void MyselfWidget::delAttentionDone()
{//更新数据中心的数据同时更新界面中的数据auto dataCenter = model::DataCenter::getInstance();auto myselfInfo = dataCenter->getMyselfUserInfo();auto otherInfo = dataCenter->getOtherUserInfo();myselfInfo->followedCount--;otherInfo->followerCount--;ui->fansCountLabel->setText(intToString2(otherInfo->followerCount));
}//datacenter.h//新关注-取消关注void newAttentionAsync(const QString& userId);void delAttentionAsync(const QString& userId);//datacenter.cpp
void DataCenter::newAttentionAsync(const QString &userId)
{netClient.newAttention(userId);
}void DataCenter::delAttentionAsync(const QString &userId)
{netClient.delAttention(userId);
}//netclient.cpp
void NetClient::newAttention(const QString &userId)
{//请求报文的bodyQJsonObject reqbody;reqbody["requestId"] = makeRequestId();reqbody["sessionId"] = dataCenter->getSessionId();reqbody["userId"] = userId;QNetworkReply* reply = sendReqToHttpServer("/HttpService/newAttention",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();//解析成功,处理响应信息emit dataCenter->newAttentionDone();LOG() << "newAttention请求结束,服务端成功设置当前用户关注目标用户";});
}void NetClient::delAttention(const QString &userId)
{//请求报文的bodyQJsonObject reqbody;reqbody["requestId"] = makeRequestId();reqbody["sessionId"] = dataCenter->getSessionId();reqbody["userId"] = userId;QNetworkReply* reply = sendReqToHttpServer("/HttpService/delAttention",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();//解析成功,处理响应信息emit dataCenter->delAttentionDone();LOG() << "delAttention请求结束,服务端成功取消设置当前用户关注目标用户";});
}

假服务端的处理逻辑:

//mockserver.cpp//客户端请求新增关注httpServer.route("/HttpService/newAttention",[=](const QHttpServerRequest& req){return this->newAttention(req);});//客户端请求取消关注httpServer.route("/HttpService/delAttention",[=](const QHttpServerRequest& req){return this->delAttention(req);});QHttpServerResponse MockServer::newAttention(const QHttpServerRequest &request)
{QJsonObject reqData = QJsonDocument::fromJson(request.body()).object();LOG() << "[newAttention] 收到 newAttention 请求, requestId = " <<reqData["requestId"].toString() << "sessionId = " << reqData["sessionId"].toString();LOG() << "新增对对应用户的关注,目标用户Id为:" << reqData["userId"].toString();QJsonObject respData;respData["requestId"] = reqData["requestId"].toString();respData["errorCode"] = 0;respData["errorMsg"] = "";//构建http响应报文QHttpServerResponse response(QJsonDocument(respData).toJson(),QHttpServerResponse::StatusCode::Ok);response.setHeader("Content-Type","application/json;charset=utf-8");return response;
}QHttpServerResponse MockServer::delAttention(const QHttpServerRequest &request)
{QJsonObject reqData = QJsonDocument::fromJson(request.body()).object();LOG() << "[delAttention] 收到 delAttention 请求, requestId = " <<reqData["requestId"].toString() << "sessionId = " << reqData["sessionId"].toString();LOG() << "取消对对应用户的关注,目标用户Id为:" << reqData["userId"].toString();QJsonObject respData;respData["requestId"] = reqData["requestId"].toString();respData["errorCode"] = 0;respData["errorMsg"] = "";//构建http响应报文QHttpServerResponse response(QJsonDocument(respData).toJson(),QHttpServerResponse::StatusCode::Ok);response.setHeader("Content-Type","application/json;charset=utf-8");return response;
}
http://www.dtcms.com/a/515452.html

相关文章:

  • 电商网站开发 文献综述百度网址大全 旧版本
  • 网站平台建设保密协议新网域名续费
  • 机器学习之生成对抗网络(GAN)
  • 零基础-动手学深度学习-13.11. 全卷积网络
  • JMeter测试关系数据库: JDBC连接
  • Linux(五):进程优先级
  • 【算法专题训练】26、队列的应用-广度优先搜索
  • 可靠性SLA:服务稳定性的量化承诺
  • 收集飞花令碎片——C语言内存函数
  • c语言-字符串
  • 红帽Linux -章8 监控与管理进程
  • 企业网站规范简述seo的优化流程
  • LLaMA Factory进行微调训练的时候,有哪些已经注册的数据集呢?
  • 【人工智能系列:走近人工智能03】概念篇:人工智能中的数据、模型与算法
  • 江苏品牌网站设计如何做旅游休闲网站
  • 个人Z-Library镜像技术实现:从爬虫到部署
  • MySQL 索引深度指南:原理 · 实践 · 运维(适配 MySQL 8.4 LTS)
  • SVG修饰属性
  • Labelme格式转yolo格式
  • react的生命周期
  • 保险行业网站模板东莞阳光网站投诉平台
  • Mychem在Ubuntu 24.04 平台上的编译与配置
  • 自定义部署Chrony同步时间
  • 力扣热题100道之73矩阵置零
  • 概述网站建设的流程网站模板之家
  • AI智能体编程的挑战有哪些?
  • 偏振工业相机的简单介绍和场景应用
  • Linux小课堂: SSH协议之安全远程连接的核心技术原理与实现
  • 建网站淄博企业门户网站建设案例
  • C primer plus (第六版)第十一章 编程练习第11题