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

  • 零.提示窗口
  • 一.登录界面逻辑处理
    • 1.1获取验证码
    • 1.2短信登录
    • 1.3密码登录
    • 1.4session登录
    • 1.5退出登录逻辑处理
  • 二.修改个人信息界面处理
    • 2.1加载用户当前信息到修改界面
    • 2.2修改密码
    • 2.3修改昵称
  • 三.上传视频界面
    • 3.1上传视频
    • 3.2获取视频首帧图
    • 3.3上传视频封面图
    • 3.4获取视频总时长
    • 3.5上传视频描述信息
  • 四.视频审核界面
    • 4.1获取审核视频列表并更新到审核界面
    • 4.2审核界面视频播放支持
    • 4.3状态视频列表的获取
    • 4.4审核界面审核功能的支持
  • 五.管理员角色管理界面
    • 5.0整体思路
    • 5.1定义管理员信息与管理员列表
    • 5.2角色管理界面主体逻辑实现
      • 5.2.1通过手机号获取管理员信息
      • 5.2.2 通过状态获取管理员信息
      • 5.2.3新增管理员
      • 5.2.4编辑管理员信息
      • 5.2.5启用或禁用管理员
      • 5.2.6删除管理员
  • 六.补充

本文结束后客户端的主体部分已全部完成,剩余的需要进行更正或添加的细节在服务端编写完毕后的联调才方便进行,客户端源码链接如下:
ly121381-cpp-videoplayer

零.提示窗口

当用户需要进行一些比较敏感的操作时,我们需要为用户提供一个缓冲,再次确认用户是否需要进行此操作。比如退出登录,所以我们这里新增一个提示窗口,因为其构造较为简单,所以我们这里直接选择普通类即可,继承自QDialog:

//confirmdialog.h
#ifndef CONFIRMDIALOG_H
#define CONFIRMDIALOG_H

#include <QWidget>
#include <QDialog>
#include <QLabel>

class ConfirmDialog : public QDialog
{
    Q_OBJECT
public:
    explicit ConfirmDialog(QWidget *parent = nullptr);
    bool getConfirmStatu();
    void setConfirmDialogText(const QString& text);
signals:
private:
    bool isConfirm = false;
    QLabel* textLabel = nullptr;
};

#endif // CONFIRMDIALOG_H

//confirmdialog.cpp
#include "confirmdialog.h"
#include "limeplayer.h"

ConfirmDialog::ConfirmDialog(QWidget *parent)
    : QDialog{parent}
{
    //移除标题栏并设置背景透明
    setWindowFlag(Qt::FramelessWindowHint);
    setAttribute(Qt::WA_TranslucentBackground);
    //设置初始位置
    this->move(LimePlayer::getInstance()->mapToGlobal(QPoint(25,25)));
    //设置固定大小
    setFixedSize(1485,890);
    //设置透明黑色遮罩
    QWidget* background = new QWidget(this);
    background->setFixedSize(maximumSize());
    background->setObjectName("bg");
    background->setStyleSheet("#bg{background-color : rgba(0,0,0,0.3);"
                              "border-radius : 20px;}");
    //消息展示对话框
    QFrame* frame = new QFrame(background);
    frame->move(564,351);
    frame->setFixedSize(358,169);
    frame->setObjectName("frame");
    //确认与取消按钮
    QPushButton* confirmBtn = new QPushButton(frame);
    confirmBtn->setFixedSize(100, 38);
    confirmBtn->move(194, 111);
    confirmBtn->setText("确认");
    confirmBtn->setObjectName("confirmBtn");
    confirmBtn->setStyleSheet("border-radius:8px;"
                              "border: 1px solid #DEDEDE;");
    connect(confirmBtn,&QPushButton::clicked,this,[this](){
        this->isConfirm = true;
        close();
    });
    QPushButton* cancelBtn = new QPushButton(frame);
    cancelBtn->setText("取消");
    cancelBtn->setFixedSize(100, 38);
    cancelBtn->move(64, 111);
    cancelBtn->setObjectName("cancelBtn");
    cancelBtn->setStyleSheet("border-radius:8px;"
                             "border: 1px solid #DEDEDE;");
    connect(cancelBtn,&QPushButton::clicked,this,[this](){
        this->isConfirm = false;
        close();
    });
    //初始化文本显示框
    textLabel = new QLabel(frame);
    textLabel->setObjectName("operatorText");
    textLabel->setStyleSheet("color:#000000;");
    textLabel->move(115, 40);
    frame->setStyleSheet("#frame{background-color: #FFFFFF;"
                         "border-radius : 10px;}"
                         " *{"
                         "font-family: 幼圆, Microsoft YaHei, sans-serif;"
                         "font-size : 14px;}"
                         "#cancelBtn:hover{"
                         "background-color:#66CCFF;"
                         "border-radius:8px;}"
                         "#confirmBtn:hover{"
                         "background-color:#66CCFF;"
                         "border-radius:8px;"
                         "color:#FFFFFF;}");
}

bool ConfirmDialog::getConfirmStatu()
{
    return isConfirm;
}

void ConfirmDialog::setConfirmDialogText(const QString &text)
{
    textLabel->setText(text);
}

效果如下:
提示窗口效果

一.登录界面逻辑处理

我们这里有两种登录方式,一种是在已经有账户的情况下可以直接进行账密登录。而另一种的登录方式则是验证码登录,有账号时直接登录,没有则进行注册。我们先来处理后一种情况。

1.1获取验证码

请求url:POST /HttpService/getCode
请求参数

字段名称字段类型字段说明
requestIdstring请求ID
sessionIdstring客户端会话ID
phoneNumberstring手机号

返回响应:200 OK

响应参数

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

设计好请求与响应报文后,那就是走datacenter->netclient->mockserver->netclient->处理界面响应这套流程了:

//datacenter.h
    //获取验证码
    void getCodeAsync(const QString& phoneNumber);
signal:
    //获取验证码成功
    void getCodeDone(const QString& codeId);

//datacenter.cpp
void DataCenter::getCodeAsync(const QString &phoneNumber)
{
    netClient.getCode(phoneNumber);
}

//netclient.cpp
void NetClient::getCode(const QString &phoneNumber)
{
    //请求报文的body
    QJsonObject reqbody;
    reqbody["requestId"] = makeRequestId();
    reqbody["sessionId"] = dataCenter->getSessionId();
    reqbody["phoneNumber"] = phoneNumber;
    QNetworkReply* reply = sendReqToHttpServer("/HttpService/getCode",reqbody);
    //异步处理服务端的response
    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->getCodeDone(result["codeId"].toString());
        LOG() << "getCode请求结束,成功获取验证码ID";
    });
}

//mockserver.cpp
QHttpServerResponse MockServer::getCode(const QHttpServerRequest &request)
{
    QJsonObject reqData = QJsonDocument::fromJson(request.body()).object();
    LOG() << "[getCode] 收到 getCode 请求, requestId = " <<reqData["requestId"].toString() << "sessionId = " << reqData["sessionId"].toString();
    LOG() << "向目标用户发送验证码,目标用户手机号为:" << reqData["phoneNumber"].toString();
    //构造响应信息
    QJsonObject respData;
    respData["requestId"] = reqData["requestId"].toString();
    respData["errorCode"] = 0;
    respData["errorMsg"] = "";
    QJsonObject result;
    result["codeId"] = "111111";
    respData["result"] = result;
    //构建http响应报文
    QHttpServerResponse response(QJsonDocument(respData).toJson(),QHttpServerResponse::StatusCode::Ok);
    response.setHeader("Content-Type","application/json;charset=utf-8");
    return response;
}

客⼾端发起获取短信验证码请求:

//login.h
    void onGetCodeBtnClicked();//获取验证码按钮被点击时
    void getAuthCodeDone(const QString& codeId);//成功获取验证码
    void initSignalsAndSlots()

//login.cpp
void Login::initSignalsAndSlots()
{
    connect(ui->authcodeBtn,&QPushButton::clicked,this,&Login::onGetCodeBtnClicked);
    connect(dataCenter,&model::DataCenter::getCodeDone,this,&Login::getAuthCodeDone);
}

void Login::onGetCodeBtnClicked()
{
    //如果手机号为空则不允许发送
    QString phoneNumber = ui->accountEdit->text();
    if(phoneNumber.size() != 11)
    {
        Toast::showToast("手机号格式好像不太对~~~");
        return;
    }
    //向服务端发送获取验证码请求
    auto dataCenter = model::DataCenter::getInstance();
    dataCenter->getCodeAsync(phoneNumber);
    //将验证码按钮设为禁用状态
    ui->authcodeBtn->setEnabled(false);
}

void Login::getAuthCodeDone(const QString &codeId)
{
    //将codeId保存并取消获取验证码的禁用状态
    LOG() << "获取验证码成功,验证码id为: " << codeId;
    ui->authcodeBtn->setEnabled(true);
    this->codeId = codeId;
}

1.2短信登录

请求URL:POST /HttpService/vcodeLogin
请求参数

字段名称字段类型字段说明
requestIdstring请求ID
sessionIdstring客户端会话ID
phoneNumberstring手机号
verifyCodestring验证码
codeIdstring验证码ID

返回响应:200 OK

响应参数

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

先走老流程:

//datacenter.h
    //短信登录
    void vcodeLoginAsync(const QString& phoneNumber,const QString& verifyCode,const QString& codeId);
signal:
    //短信登录成功-失败
    void vcodeLoginDone();
    void vcodeLoginFailed(const QString& errorInfo);

//datacenter.cpp
void DataCenter::vcodeLoginAsync(const QString &phoneNumber, const QString &verifyCode, const QString &codeId)
{
    netClient.vcodeLogin(phoneNumber,verifyCode,codeId);
}

//netclient.cpp
void NetClient::vcodeLogin(const QString &phoneNumber, const QString &verifyCode, const QString &codeId)
{
    //请求报文的body
    QJsonObject reqbody;
    reqbody["requestId"] = makeRequestId();
    reqbody["sessionId"] = dataCenter->getSessionId();
    reqbody["phoneNumber"] = phoneNumber;
    reqbody["verifyCode"] = verifyCode;
    reqbody["codeId"] = codeId;
    QNetworkReply* reply = sendReqToHttpServer("/HttpService/vcodeLogin",reqbody);
    //异步处理服务端的response
    connect(reply,&QNetworkReply::finished,this,[=](){
        bool ok = true;
        QString reason;
        QJsonObject respBody = handleRespFromHttpServer(reply,ok,reason);
        if(!ok)
        {
            //说明reply有问题将错误消息提供给界面提示用户
            emit dataCenter->vcodeLoginFailed(reason);
            reply->deleteLater();
            return;
        }
        reply->deleteLater();
        //解析成功,处理响应信息
        emit dataCenter->vcodeLoginDone();
        LOG() << "vcodeLogin请求结束,短信登录服务端验证通过";
    });
}

//mockserver.cpp
QHttpServerResponse MockServer::vcodeLogin(const QHttpServerRequest &request)
{
    QJsonObject reqData = QJsonDocument::fromJson(request.body()).object();
    LOG() << "[vcodeLogin] 收到 vcodeLogin 请求, requestId = " <<reqData["requestId"].toString() << "sessionId = " << reqData["sessionId"].toString();
    QString phoneNumber = reqData["phoneNumber"].toString();//用户手机号
    QString verifyCode = reqData["verifyCode"].toString();//用户输入的验证码
    QString codeId = reqData["codeId"].toString();//服务端保存的发给此手机号的验证码对应的验证码Id
    int errorCode = 0;
    QString errorMsg = "";
    if(phoneNumber != "19000000001")
    {
        errorCode = 600;
        errorMsg = "好像还没有获取验证码?";
    }
    else if(verifyCode != "123456")
    {
        errorCode = 601;
        errorMsg = "验证码输入有误~~~";
    }
    else if(codeId != "111111")
    {
        errorCode = 602;
        errorMsg = "验证码Id无法正确匹配";
    }
    //构造响应信息
    QJsonObject respData;
    respData["requestId"] = reqData["requestId"].toString();
    respData["errorCode"] = errorCode;
    respData["errorMsg"] = errorMsg;
    //构建http响应报文
    QHttpServerResponse response(QJsonDocument(respData).toJson(),QHttpServerResponse::StatusCode::Ok);
    response.setHeader("Content-Type","application/json;charset=utf-8");
    return response;
}

客户端发起短信登录请求并对成功与失败进行相关处理:

//login.h
    void onLoginOrRegisterBtnClicked();//短信登录页面登录或注册按钮被点击
    void vcodeLoginSuccess();//短信登录请求成功
    void vcodeLoginFailed(const QString& errorInfo);//短信登录请求失败
//login.cpp
void Login::onLoginOrRegisterBtnClicked()
{
    //需要确保输入的手机号和验证码都为正确格式再去发送登录请求
    QString phoneNumber = ui->accountEdit->text();
    QString authCode = ui->passwordEdit->text();
    if(phoneNumber.size() != 11 || authCode.size() != 6)
    {
        Toast::showToast("手机号或验证码输入格式有误~~~");
        return;
    }
    //向服务端发送登录请求
    auto dataCenter = model::DataCenter::getInstance();
    dataCenter->vcodeLoginAsync(phoneNumber,authCode,codeId);
}

void Login::vcodeLoginSuccess()
{
    //通知我的页面进行数据更新
    LOG() << "短信登录请求成功,更新当前用户信息~~~";
    auto dataCenter = model::DataCenter::getInstance();
    dataCenter->clearMyselfInfo();
    emit loginSuccess();
    //关闭登录页面显示并清空用户输入信息
    reset();
    hide();
}

void Login::vcodeLoginFailed(const QString &errorInfo)
{
    //将服务端返回的错误消息打印到屏幕上
    Toast::showToast(errorInfo);
}

void Login::reset()
{
    ui->accountEdit->setText("");
    ui->passwordEdit->setText("");
}

//myselfwidget.cpp
void MyselfWidget::createLogin()
{
    if(login == nullptr)
    {
        login = new Login();
        connect(login,&Login::loginSuccess,this,[=](){
            loadMyself();
        });//链接登录成功的信号槽
    }
}

记得进行对应按钮与信号槽的链接。

1.3密码登录

在密码登录页面时,如果用户已经注册则直接登录,如果⽤⼾没有注册则需要先注册,此时直接跳转到短信登录页面进行注册。

//login.cpp
void Login::initSignalsAndSlots()
{
    connect(ui->registerBtn,&QPushButton::clicked,this,&Login::onMessageBtnClicked);//点击注册按钮时切换到短信登录界面
}

然后我们进行密码登录的处理。请求响应接口如下:
请求URL:POST /HttpService/passwdLogin
请求参数

字段名称字段类型字段说明
requestIdstring请求ID
sessionIdstring客户端会话ID
phoneNumberstring手机号
passwordstring登录密码

返回响应:200 OK

响应参数

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

接下来走老流程:

//datacenter.h
//密码登录
    void passwdLoginAsync(const QString& phoneNumber,const QString& password);
signals:
    //密码登录成功-失败
    void passwdLoginDone();
    void passwdLoginFailed(const QString& errorInfo);
//datacenter.cpp
void DataCenter::passwdLoginAsync(const QString &phoneNumber, const QString &password)
{
    netClient.passwdLogin(phoneNumber,password);
}

//netclient.cpp
void NetClient::passwdLogin(const QString &phoneNumber, const QString &password)
{
    //请求报文的body
    QJsonObject reqbody;
    reqbody["requestId"] = makeRequestId();
    reqbody["sessionId"] = dataCenter->getSessionId();
    reqbody["phoneNumber"] = phoneNumber;
    reqbody["password"] = password;
    QNetworkReply* reply = sendReqToHttpServer("/HttpService/passwdLogin",reqbody);
    //异步处理服务端的response
    connect(reply,&QNetworkReply::finished,this,[=](){
        bool ok = true;
        QString reason;
        QJsonObject respBody = handleRespFromHttpServer(reply,ok,reason);
        if(!ok)
        {
            //说明reply有问题将错误消息提供给界面提示用户
            emit dataCenter->passwdLoginFailed(reason);
            reply->deleteLater();
            return;
        }
        reply->deleteLater();
        //解析成功,处理响应信息
        emit dataCenter->passwdLoginDone();
        LOG() << "passwdLogin请求结束,密码登录服务端验证通过";
    });
}

//mockserver.cpp
QHttpServerResponse MockServer::passwdLogin(const QHttpServerRequest &request)
{
    QJsonObject reqData = QJsonDocument::fromJson(request.body()).object();
    LOG() << "[passwdLogin] 收到 passwdLogin 请求, requestId = " <<reqData["requestId"].toString() << "sessionId = " << reqData["sessionId"].toString();
    QString phoneNumber = reqData["phoneNumber"].toString();//用户手机号
    QString password = reqData["password"].toString();//用户输入的密码
    int errorCode = 0;
    QString errorMsg = "";
    if(phoneNumber != "19000000001")
    {
        errorCode = 603;
        errorMsg = "无法在这个星球找到对应账号的信息。";
    }
    else if(password != "@1145141919180")
    {
        errorCode = 604;
        errorMsg = "密码输入有误~~~";
    }
    //构造响应信息
    QJsonObject respData;
    respData["requestId"] = reqData["requestId"].toString();
    respData["errorCode"] = errorCode;
    respData["errorMsg"] = errorMsg;
    //构建http响应报文
    QHttpServerResponse response(QJsonDocument(respData).toJson(),QHttpServerResponse::StatusCode::Ok);
    response.setHeader("Content-Type","application/json;charset=utf-8");
    return response;
}

客户端发起密码登录请求在按下对应按钮之后:

//login.h
    void onLoginBtnClicked();//登录按钮被点击
    void passwordLoginDone();//密码登录成功
    void passwordLoginFailed(const QString& errorInfo);//密码登录失败

//login.cpp
void Login::onLoginBtnClicked()
{
    //需要确保输入的手机号和验证码都为正确格式再去发送登录请求
    QString phoneNumber = ui->accountEdit->text();
    QString password = ui->passwordEdit->text();
    if(phoneNumber.isEmpty() || password.isEmpty())
    {
        Toast::showToast("手机号或密码输入不能为空~~~");
        return;
    }
    //发送登录请求
    auto dataCenter = model::DataCenter::getInstance();
    dataCenter->passwdLoginAsync(phoneNumber,password);
}

void Login::passwordLoginDone()
{
    //通知我的页面进行数据更新
    LOG() << "密码登录请求成功,更新当前用户信息";
    auto dataCenter = model::DataCenter::getInstance();
    dataCenter->clearMyselfInfo();
    emit loginSuccess();
    reset();
    hide();
}

void Login::passwordLoginFailed(const QString &errorInfo)
{
    //将服务端返回的错误消息打印到屏幕上
    Toast::showToast(errorInfo);
}

1.4session登录

因为每次登录之后如果用户退出程序了,下次登录还需要去手动输入账号密码进行登陆很麻烦,所以我们需要在用户进入程序时加载用户的账户信息从文件中(如果有的话),退出时将用户的信息保存到appdata下的对应文件夹下:

//datacenter.h
    void initDataFile();//初始化数据文件
    void saveDataFile();//保存数据到文件中-程序结束时
    void loadDataFile();//加载文件数据到程序中-程序开始时

//datacenter.cpp
void DataCenter::initDataFile()
{
    //构建用户信息文件路径
    QString filePath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation);
    QString fileName = "/limePlayer.json";
    LOG() << "用户信息路径为:" << filePath + fileName;
    //检验目标路径是否存在-不存在则创建
    QDir dir;
    if(!dir.exists(filePath))
    {
        dir.mkpath(filePath);
    }
    //打开对应文件
    QFile file(filePath + fileName);
    if(!file.open(QIODevice::WriteOnly | QIODevice::Text))
    {
        LOG() << "文件打开失败!失败原因:" << file.errorString();
        return;
    }
    //写一段初始的空白json文件
    file.write(QString("{\n}").toUtf8());
    file.close();
}

void DataCenter::saveDataFile()
{
    //主要保存三个数据,sessionId,用户身份信息,用户角色信息-程序结束时调用
    QString filePath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/limePlayer.json";
    QFileInfo fileInfo(filePath);
    if(!fileInfo.exists())
    {
        initDataFile();//保证对应文件存在
    }
    QFile file(filePath);
    if(!file.open(QIODevice::WriteOnly | QIODevice::Text))
    {
        LOG() << "文件打开失败!失败原因:" << file.errorString();
        return;
    }
    //提取相关信息到磁盘文件中
    QJsonObject userData;
    userData["loginsessionId"] = getSessionId();
    QJsonArray roleType;
    for(auto& role : myselfUserInfo->roleType)
    {
        roleType.append(role);
    }
    QJsonArray identityType;
    for(auto& identity : myselfUserInfo->identityType)
    {
        identityType.append(identity);
    }
    userData["roleType"] = roleType;
    userData["identityType"] = identityType;
    QJsonDocument document(userData);
    QString s = document.toJson();
    file.write(s.toUtf8());
    file.close();
}

void DataCenter::loadDataFile()
{
    //如果没有个人信息对象,则需要new一个出来
    if(myselfUserInfo == nullptr)
    {
        myselfUserInfo = new UserInfo();
    }
    //从文件中加载数据
    QString filePath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/limePlayer.json";
    QFileInfo fileInfo(filePath);
    if(!fileInfo.exists())
    {
        initDataFile();//保证对应文件存在
    }
    QFile file(filePath);
    if(!file.open(QIODevice::ReadOnly | QIODevice::Text))
    {
        LOG() << "文件打开失败!失败原因:" << file.errorString();
        return;
    }
    QByteArray userData = file.readAll();
    file.close();
    QJsonDocument document = QJsonDocument::fromJson(userData);
    if(document.isNull())
    {
        LOG() << "json文件格式有误!";
        return;
    }
    QJsonObject obj = document.object();
    //设置用户相关信息
    setSessionId(obj["loginsessionId"].toString());
    QJsonArray roleType = obj["roleType"].toArray();
    QJsonArray identityType = obj["identityType"].toArray();
    for(int i = 0;i < roleType.size();i++)
    {
        myselfUserInfo->roleType.append(roleType[i].toInt());
    }
    for(int i = 0;i < identityType.size();i++)
    {
        myselfUserInfo->identityType.append(identityType[i].toInt());
    }
}

接下来根据我们自己程序的代码结构放到程序开始与结束时调用即可。
账户数据文件处理完毕了,当程序启动时,会⾃动加载session信息,然后完成session登录。
请求url:POST /HttpService/sessionLogin
请求参数

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

返回响应:200 OK

响应参数

字段名称字段类型字段说明
requestIdstring请求ID
errorCodeinteger错误码;0-成功
errorMsgstring错误信息
resultobject响应结果
isGuestboolean是否是临时用户
false-否;true-是

然后就是走老流程:

//datacenter.h
    //sessionId登录
    void sessionLoginAsync();
signals:
    //sessionId登录成功响应-失败
    void sessionLoginDone(bool isGuest);
    void sessionLoginFailed(const QString& errorInfo);
//datacenter.cpp
void DataCenter::sessionLoginAsync()
{
    netClient.sessionLogin();
}

//netclient.cpp
void NetClient::sessionLogin()
{
    //请求报文的body
    QJsonObject reqbody;
    reqbody["requestId"] = makeRequestId();
    reqbody["sessionId"] = dataCenter->getSessionId();
    QNetworkReply* reply = sendReqToHttpServer("/HttpService/sessionLogin",reqbody);
    //异步处理服务端的response
    connect(reply,&QNetworkReply::finished,this,[=](){
        bool ok = true;
        QString reason;
        QJsonObject respBody = handleRespFromHttpServer(reply,ok,reason);
        if(!ok)
        {
            //说明reply有问题打印错误信息并返回
            emit dataCenter->sessionLoginFailed(reason);
            reply->deleteLater();
            return;
        }
        reply->deleteLater();
        //解析成功,处理响应信息
        QJsonObject result = respBody["result"].toObject();
        emit dataCenter->sessionLoginDone(result["isGuest"].toInt() == 1);
        LOG() << "sessionLogin请求结束,sessionID登录成功响应";
    });
}

//mockserver.cpp
QHttpServerResponse MockServer::sessionLogin(const QHttpServerRequest &request)
{
    QJsonObject reqData = QJsonDocument::fromJson(request.body()).object();
    LOG() << "[sessionLogin] 收到 sessionLogin 请求, requestId = " <<reqData["requestId"].toString() << "sessionId = " << reqData["sessionId"].toString();
    //构造响应信息
    QJsonObject respData;
    respData["requestId"] = reqData["requestId"].toString();
    respData["errorCode"] = 0;
    respData["errorMsg"] = "";
    QJsonObject result;
    result["isGuest"] = 0;
    respData["result"] = result;
    //构建http响应报文
    QHttpServerResponse response(QJsonDocument(respData).toJson(),QHttpServerResponse::StatusCode::Ok);
    response.setHeader("Content-Type","application/json;charset=utf-8");
    return response;
}

客户端发送并响应服务端的响应:
//因为这部分需要修改之前的临时登录逻辑,所以我直接把启动页的代码全部展示出来:

//startpage.h
#ifndef STARTPAGE_H
#define STARTPAGE_H

#include <QWidget>
#include <QDialog>
#include <QJsonObject>

class StartPage : public QDialog
{
    Q_OBJECT
public:
    explicit StartPage(QWidget *parent = nullptr);
    void startUp();
    void tempLoginDone();
    void sessionLoginDone(bool isGuest);
private:
    bool isLogin = false;//默认登录状态为false
    bool getMyselfInfoSucess = false;
};

#endif // STARTPAGE_H

//startpage.cpp
#include "startpage.h"
#include "./model/datacenter.h"
#include "util.h"

#include <QLabel>
#include <QTimer>

StartPage::StartPage(QWidget *parent)
    : QDialog(parent)
{
    //设置尺寸大小与背景颜色,同时设置tool保证
    setFixedSize(697,392);
    setStyleSheet("background-color : #FFFFFF;");
    setWindowFlags(Qt::FramelessWindowHint | Qt::Tool);

    //设置Label显示图片
    QLabel* imageLabel = new QLabel(this);
    imageLabel->setPixmap(QPixmap(":/images/startupPage/limeplayer.png"));
    imageLabel->move(148,118);
    //绑定临时登录的信号槽
    auto dataCenter = model::DataCenter::getInstance();
    connect(dataCenter,&model::DataCenter::tempLoginDone,this,&StartPage::tempLoginDone);
    connect(dataCenter,&model::DataCenter::sessionLoginDone,this,&StartPage::sessionLoginDone);
    connect(dataCenter,&model::DataCenter::getMyselfUserInfoDone,this,[=](){
        getMyselfInfoSucess = true;
    });
    connect(dataCenter,&model::DataCenter::sessionLoginFailed,this,[=](const QString& errorInfo){
        LOG() << "session登录失败,错误原因: " << errorInfo;
        //失败发起临时用户登录请求
        dataCenter->tempLoginAsync();
    });
}

void StartPage::startUp()
{
    QTimer* timer = new QTimer(this);
    auto dataCenter = model::DataCenter::getInstance();
    if(dataCenter->getSessionId().isEmpty())
    {
        dataCenter->tempLoginAsync();//发送登录请求
    }
    else{
        //不为空判断是否为临时用户,若不是再进行session登录
        if(dataCenter->getMyselfUserInfo()->isTempUser())
        {
            dataCenter->tempLoginAsync();//发送登录请求
        }
        else{
            dataCenter->sessionLoginAsync();
        }
    }
    timer->setSingleShot(true);//允许重复触发定时器
    connect(timer,&QTimer::timeout,this,[=](){
        if(isLogin && getMyselfInfoSucess){
            timer->stop();
            close();
            timer->deleteLater();
        }//当登录成功之后再关闭启动页面
    });
    timer->start(2000);
}

void StartPage::tempLoginDone()
{
    //设置确认登录状态
    isLogin = true;
    auto dataCenter = model::DataCenter::getInstance();
    dataCenter->buildTempUser();
    getMyselfInfoSucess = true;
}

void StartPage::sessionLoginDone(bool isGuest)
{
    //如果为临时用户则再进行临时登录,否则获取当前用户信息
    auto dataCenter = model::DataCenter::getInstance();
    isLogin = true;
    if(isGuest)
    {
        dataCenter->buildTempUser();
        getMyselfInfoSucess = true;
        return;
    }
    //根据文件中的sessionId进行用户信息获取
    dataCenter->getMyselfUserInfoAsync();
}

到这里,我们的登录逻辑已经基本处理完毕了。那么就需要去处理下点赞,发送弹幕,是否显示系统管理界面,这部分比较简单,读者可根据自己的代码进行响应的修改。

1.5退出登录逻辑处理

假设用户已经登录成功,⽤⼾在播放平台上操作完毕后,可以点击"退出登录"按钮,退出当前登录状态。退出登录接口定义如下:
请求URL: POST /HttpService/logout
请求参数

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

返回响应:200 OK

响应参数

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

然后就是走老流程:

//datacenter.h
    //退出登录
    void logoutAsync();
signals:
    //退出登录成功响应
    void logoutDone(const QString& sessionId);
//datacenter.cpp
void DataCenter::logoutAsync()
{
    netClient.logout();
}

//netclient.cpp
void NetClient::logout()
{
    //请求报文的body
    QJsonObject reqbody;
    reqbody["requestId"] = makeRequestId();
    reqbody["sessionId"] = dataCenter->getSessionId();
    QNetworkReply* reply = sendReqToHttpServer("/HttpService/logout",reqbody);
    //异步处理服务端的response
    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->logoutDone(result["sessionId"].toString());
        LOG() << "logout请求结束,退出登录成功响应";
    });
}

//mockserver.cpp
QHttpServerResponse MockServer::logout(const QHttpServerRequest &request)
{
    QJsonObject reqData = QJsonDocument::fromJson(request.body()).object();
    LOG() << "[logout] 收到 logout 请求, requestId = " <<reqData["requestId"].toString() << "sessionId = " << reqData["sessionId"].toString();
    const QString& sessionId = reqData["requestId"].toString();
    //构造响应信息
    QJsonObject respData;
    respData["requestId"] = reqData["requestId"].toString();
    respData["errorCode"] = 0;
    respData["errorMsg"] = "";
    QJsonObject result;
    result["sessionId"] = sessionId;
    respData["result"] = result;
    //构建http响应报文
    QHttpServerResponse response(QJsonDocument(respData).toJson(),QHttpServerResponse::StatusCode::Ok);
    response.setHeader("Content-Type","application/json;charset=utf-8");
    return response;
}

客户端发出退出登录请求:

//myselfwidget.h
    //当用户需要退出登陆时,弹出提示界面
    void onQuitLoginBtnClicked();
    void createLogin();//构建登录页面
    void logoutDone(const QString& sessionId);//用户退出登录服务端成功响应

//myselfwidget.cpp
void MyselfWidget::onQuitLoginBtnClicked()
{
    ConfirmDialog confirmDialog;
    confirmDialog.setConfirmDialogText("确定要退出登录吗?");
    confirmDialog.exec();
    if(confirmDialog.getConfirmStatu())
    {
        LOG() << "用户退出登录";
        auto dataCenter = model::DataCenter::getInstance();
        dataCenter->logoutAsync();
    }
}

void MyselfWidget::createLogin()
{
    if(login == nullptr)
    {
        login = new Login();
        connect(login,&Login::loginSuccess,this,[=](){
            loadMyself();
        });//链接登录成功的信号槽
    }
}

void MyselfWidget::logoutDone(const QString &sessionId)
{
    //清空用户界面的数据
    clearVideoList();
    //将当前用户设置为临时用户
    auto dataCenter = model::DataCenter::getInstance();
    dataCenter->clearMyselfInfo();
    dataCenter->buildTempUser();
    //重新加载个人界面信息
    getMyselfUserInfoDone();
}

二.修改个人信息界面处理

2.1加载用户当前信息到修改界面

打开修改个人信息界面时,需要将当前的用户信息加载到界面中,因为用户的手机号属于隐私信息,所以我们还需要截取中间四位进行隐藏:

//modifymyselfdialog.cpp
ModifyMyselfDialog::ModifyMyselfDialog(QWidget *parent)
    : QDialog(parent)
    , ui(new Ui::ModifyMyselfDialog)
{
    ui->setupUi(this);
    //默认隐藏修改密码后的widget
    ui->passwordWidget->hide();
    //设置背景透明 | 无边框
    setWindowFlag(Qt::FramelessWindowHint);
    setAttribute(Qt::WA_TranslucentBackground);
    //连接信号槽
    connect(ui->cancelBtn,&QPushButton::clicked,this,&ModifyMyselfDialog::close);
    connect(ui->submitBtn,&QPushButton::clicked,this,&ModifyMyselfDialog::onSubmitBtnClicked);
    connect(ui->passwordBtn, &QPushButton::clicked, this,&ModifyMyselfDialog::showPasswordDlg);
    connect(ui->changePasswordBtn, &QPushButton::clicked, this,&ModifyMyselfDialog::showPasswordDlg);
    //设置昵称与手机号
    auto dataCenter = model::DataCenter::getInstance();
    auto myselfInfo = dataCenter->getMyselfUserInfo();
    ui->nicknameEdit->setText(myselfInfo->nickname);
    ui->phoneNumberLabel->setText(hidePhoneNum(myselfInfo->phoneNum));
}

//util.h
// 隐藏⼿机号码中间四位.
static inline QString hidePhoneNum(const QString& phoneNum) {
    if (phoneNum.length() < 11) {
        return phoneNum;
    }
    return phoneNum.left(3) + "****" + phoneNum.right(4);
}

2.2修改密码

密码⽂本后的控件上提⽰,点击设置密码,点击设置密码后,弹出新的对话框专门来设置密码。
我们先来定义相关请求与响应接口:
请求URL: POST /HttpService/setPassword
请求参数

字段名称字段类型字段说明
requestIdstring请求ID
sessionIdstring客户端会话ID
passwordstring密码

返回响应:200 OK

响应参数

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

然后就是走老流程:

//datacenter.h
    //用户更改自己的账户密码
    void setPasswordAsync(const QString& password);
signals:
    //用户更改自己账户密码成功响应
    void setPasswordDone();
//datacenter.cpp
void DataCenter::setPasswordAsync(const QString &password)
{
    netClient.setPassword(password);
}

//netclinet.cpp
void NetClient::setPassword(const QString &password)
{
    //请求报文的body
    QJsonObject reqbody;
    reqbody["requestId"] = makeRequestId();
    reqbody["sessionId"] = dataCenter->getSessionId();
    reqbody["password"] = password;
    QNetworkReply* reply = sendReqToHttpServer("/HttpService/setPassword",reqbody);
    //异步处理服务端的response
    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();
        //解析成功,处理响应信息
        emit dataCenter->setPasswordDone();
        LOG() << "setPassword请求结束,修改密码请求成功响应";
    });
}

//mockserver.cpp
QHttpServerResponse MockServer::setPassword(const QHttpServerRequest &request)
{
    QJsonObject reqData = QJsonDocument::fromJson(request.body()).object();
    LOG() << "[setPassword] 收到 setPassword 请求, requestId = " <<reqData["requestId"].toString() << "sessionId = " << reqData["sessionId"].toString();
    LOG() << "用户新密码为:" << reqData["password"].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;
}

客户端发送修改请求并响应到界面上:

//modifymyselfdialog.cpp
void ModifyMyselfDialog::showPasswordDlg()
{
    NewPasswordDialog* newPassWordDialog = new NewPasswordDialog();
    newPassWordDialog->exec();
    const QString currentPassword = newPassWordDialog->getNewPassWord();
    if(currentPassword.isEmpty())
    {
        LOG() << "密码修改已取消";
    }
    else
    {
        newPassword = currentPassword;
        ui->passwordWidget->show();
        ui->passwordBtn->hide();
    }
    delete newPassWordDialog;
}

//newpassworddialog.h
#ifndef NEWPASSWORDDIALOG_H
#define NEWPASSWORDDIALOG_H

#include <QDialog>

namespace Ui {
class NewPasswordDialog;
}

class NewPasswordDialog : public QDialog
{
    Q_OBJECT

public:
    explicit NewPasswordDialog(QWidget *parent = nullptr);
    const QString& getNewPassWord();
    ~NewPasswordDialog();

private:
    void onSubmitBtnClicked();
    void onEditingFinished();
    bool checkPasswordEdit();
private:
    Ui::NewPasswordDialog *ui;
    QString newPassWord;
};

#endif // NEWPASSWORDDIALOG_H

//newpassworddialog.cpp
#include "newpassworddialog.h"
#include "ui_newpassworddialog.h"

NewPasswordDialog::NewPasswordDialog(QWidget *parent)
    : QDialog(parent)
    , ui(new Ui::NewPasswordDialog)
{
    ui->setupUi(this);
    //设置背景透明 | 无边框
    setWindowFlag(Qt::FramelessWindowHint);
    setAttribute(Qt::WA_TranslucentBackground);
    //连接信号槽
    connect(ui->cancelBtn,&QPushButton::clicked,this,&NewPasswordDialog::close);
    connect(ui->submitBtn,&QPushButton::clicked,this,&NewPasswordDialog::onSubmitBtnClicked);
    connect(ui->passwordEdit1,&QLineEdit::editingFinished,this,&NewPasswordDialog::onEditingFinished);
    connect(ui->passwordEdit2,&QLineEdit::editingFinished,this,&NewPasswordDialog::onEditingFinished);
}

const QString &NewPasswordDialog::getNewPassWord()
{
    return newPassWord;
}

NewPasswordDialog::~NewPasswordDialog()
{
    delete ui;
}

void NewPasswordDialog::onSubmitBtnClicked()
{
    if(!checkPasswordEdit())
        return;
    newPassWord = ui->passwordEdit1->text();
    close();
}

bool NewPasswordDialog::checkPasswordEdit()
{
    //核验密码的正确性
    //1.检验输入密码是否为空
    if(ui->passwordEdit1->text().isEmpty())
    {
        ui->messageLabel->setText("设置的新密码不能为空!");
            return false;
    }
    if(ui->passwordEdit2->text().isEmpty())
    {
        ui->messageLabel->setText("两次输入的密码不一致!");
        return false;
    }
    //2.检验输入的密码长度以及是否至少包含两种字符
    if(ui->passwordEdit1->text().size() < 8 || ui->passwordEdit2->text().size() > 16)
    {
        ui->messageLabel->setText("密码长度不合法!");
        return false;
    }
    QVector<bool> kinds(4,false);
    int kindsNum = 0;
    for(auto& c : ui->passwordEdit1->text())
    {
        if(QChar(c).isDigit())
        {
            kindsNum += kinds[0] ? 0 : 1;
            kinds[0] = true;
        }
        else if(QChar(c).isUpper())
        {
            kindsNum += kinds[1] ? 0 : 1;
            kinds[1] = true;
        }
        else if(QChar(c).isLower())
        {
            kindsNum += kinds[2] ? 0 : 1;
            kinds[2] = true;
        }
        else if(QChar(c).isPunct())
        {
            kindsNum += kinds[3] ? 0 : 1;
            kinds[3] = true;
        }
        else
        {
            ui->messageLabel->setText("密码中含有非法字符!");
            return false;
        }
    }
    if(kindsNum < 2)
    {
        ui->messageLabel->setText("密码中必须包含两种及以上字符!");
        return false;
    }
    //3.检验两次输入密码是否一致
    if(ui->passwordEdit1->text() != ui->passwordEdit2->text())
    {
        ui->messageLabel->setText("两次输入的密码不一致!");
        return false;
    }
    //到这里说明密码合法,清空错误信息
    ui->messageLabel->setText("");
    return true;
}

void NewPasswordDialog::onEditingFinished()
{
    checkPasswordEdit();
}

因为用户修改密码可能会有多次,所以我们在修改页面关闭时再根据密码的输入情况看是否需要向服务端发送修改请求。

2.3修改昵称

请求URL: POST /HttpService/setNickname
请求参数

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

返回响应:200 OK

响应参数

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

走老流程:

//datacenter.h
    //用户修改自己的昵称
    void setNicknameAsync(const QString& nickName);
signals:
    //用户成功修改自己的账户昵称
    void setNicknameDone(const QString& nickname);
//datacenter.cpp
void DataCenter::setNicknameAsync(const QString &nickName)
{
    netClient.setNickname(nickName);
}

//netclient.cpp
void NetClient::setNickname(const QString &nickName)
{
    //请求报文的body
    QJsonObject reqbody;
    reqbody["requestId"] = makeRequestId();
    reqbody["sessionId"] = dataCenter->getSessionId();
    reqbody["nickname"] = nickName;
    QNetworkReply* reply = sendReqToHttpServer("/HttpService/setNickname",reqbody);
    //异步处理服务端的response
    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();
        //解析成功,处理响应信息
        emit dataCenter->setNicknameDone(nickName);
        LOG() << "setNickname请求结束,修改昵称请求成功响应";
    });
}

//mockserver.cpp
QHttpServerResponse MockServer::setNickname(const QHttpServerRequest &request)
{
    QJsonObject reqData = QJsonDocument::fromJson(request.body()).object();
    LOG() << "[setNickname] 收到 setNickname 请求, requestId = " <<reqData["requestId"].toString() << "sessionId = " << reqData["sessionId"].toString();
    LOG() << "用户新昵称为:" << reqData["nickname"].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;
}

界面发起请求并处理响应到界面上:

//modifymyselfdialog.cpp
void ModifyMyselfDialog::onSubmitBtnClicked()
{
    LOG() << "用户信息已修改";
    //上传修改信息到服务端
    auto dataCenter = model::DataCenter::getInstance();
    if (!newPassword.isEmpty()) {
        auto dataCenter = model::DataCenter::getInstance();
        dataCenter->setPasswordAsync(newPassword);
    }
    //修改昵称
    auto myself = dataCenter->getMyselfUserInfo();
    const QString& newNickname = ui->nicknameEdit->text().trimmed();
    if (newNickname != myself->nickname) {
        dataCenter->setNicknameAsync(newNickname);
    }
    close();
}

三.上传视频界面

3.1上传视频

在我的页面中,如果用户需要上传视频,可以先选择本地文件,然后我们的程序进行读取再上传至服务端,请求接口定义如下:
请求URL: POST /HttpService/uploadVideo?requestId=xxx&sessionId=xxx
请求参数:multipart/form-data

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

返回响应:200 OK

响应参数

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

然后就是走老流程:

//datacenter.h
    //用户上传视频的请求
    void uploadVideoAsync(const QString& videoPath);
signals:
    //用户上传视频成功响应
    void uploadVideoDone(const QString& fileId);
//datacenter.cpp
void DataCenter::uploadVideoAsync(const QString &videoPath)
{
    netClient.uploadVideo(videoPath);
}

//netclient.cpp
void NetClient::uploadVideo(const QString &videoPath)
{
    // 1. 构造请求
    QUrl url(HTTP_URL + "/HttpService/uploadVideo");
    QUrlQuery query;
    query.addQueryItem("requestId",makeRequestId());
    query.addQueryItem("sessionId",dataCenter->getSessionId());
    url.setQuery(query);
    //获取视频数据
    QByteArray videoData = loadFileToByteArray(videoPath);
    // 2. 发送请求
    QNetworkRequest httpReq;
    httpReq.setUrl(url);
    httpReq.setHeader(QNetworkRequest::ContentTypeHeader, "multipart/form-data");
    QNetworkReply* httpReply = netClientManager.post(httpReq,videoData);
    // 3. 异步处理响应
    connect(httpReply, &QNetworkReply::finished, this, [=](){
        bool ok = true;
        QString reason;
        QJsonObject respBody = handleRespFromHttpServer(httpReply,ok,reason);
        if(!ok)
        {
            //说明reply有问题打印错误信息并返回
            LOG() << reason;
            httpReply->deleteLater();
            return;
        }
        // 发射信号,通知界面更视频显⽰
        httpReply->deleteLater();
        QJsonObject result = respBody["result"].toObject();
        QString fileId = result["fileId"].toString();
        emit dataCenter->uploadVideoDone(fileId);
        LOG() << "uploadVideo请求结束,视频上传成功";
    });
}

//mockserver.cpp
QHttpServerResponse MockServer::uploadVideo(const QHttpServerRequest &request)
{
    // 解析查询字符串
    QUrlQuery query(request.url());
    QString requestId = query.queryItemValue("requestId");
    QString sessionId = query.queryItemValue("sessionId");
    LOG() << "[uploadVideo] 收到 uploadVideo 请求, requestId=" << requestId << "sessionId =" << sessionId;
    // 构造视频文件路径
    QDir dir(QDir::currentPath());
    dir.cdUp();
    dir.cdUp();
    writeByteArrayToFile(dir.absolutePath() + "/videos/222/222.mp4",request.body());
    // 构造响应json报文
    QJsonObject respBody;
    respBody["requestId"] = requestId;
    respBody["errorCode"] = 0;
    respBody["errorMsg"] = "";
    QJsonObject result;
    result["fileId"] = "6000";
    respBody["result"] = result;
    //构建http响应报文
    QHttpServerResponse response(QJsonDocument(respBody).toJson(),QHttpServerResponse::StatusCode::Ok);
    response.setHeader("Content-Type","application/json;charset=utf-8");
    return response;
}

记得在假服务端构造相应的目录和添加初始假数据键值对映射。
接下来我们需要处理界面中的响应逻辑,没有上传成功时显示加载动画,上传成功之后显示成功标志:

//myselfwidget.cpp
void MyselfWidget::onUpLoadVideoBtnClicked()
{
    //弹出打开⽂件对话框,让用户选择要上传的视频⽂件
    QString videoFilePath = QFileDialog::getOpenFileName(nullptr, "上传视频",
                                                         "",
                                                         "Videos(*.mp4 *.rmvb *.avi *.mov)");
    if(!videoFilePath.isEmpty()){
        // 视频⼤小限制,上限为4G
        QFileInfo fileInfo(videoFilePath);
        qint64 fileSize = fileInfo.size();
        qlonglong maxVideoSize = 4294967296;
        if(fileSize > maxVideoSize){
            LOG()<<"视频文件必须小于4G";
            return;
        }
        emit switchUploadVideoPage(UpLoadPageBtn,videoFilePath);
        //发送上传视频请求
        auto dataCenter = model::DataCenter::getInstance();
        dataCenter->uploadVideoAsync(videoFilePath);
    }
}

//limeplayer.cpp
void LimePlayer::switchUploadVideoPage(ButtonType buttonType, const QString &videoFilePath)
{
    //修改对应设置表示文件正在上传中
    ui->uploadVideo->isUploading(true);
    QString fileName = videoFilePath.mid(videoFilePath.lastIndexOf("/") + 1);
    ui->uploadVideo->setFileName(fileName);
    ui->stackedWidget->setCurrentIndex(buttonType);
}

//uploadvideopage.h
#include <QWidget>
#include <QMovie>
#include "pageswitchbutton.h"
#include "./mpv/mpvplayer.h"

namespace Ui {
class UploadVideoPage;
}

class UploadVideoPage : public QWidget
{
    Q_OBJECT

public:
    explicit UploadVideoPage(QWidget *parent = nullptr);
    void isUploading(bool isLoading);//上传或上传完毕的显示
    void setFileName(const QString& fileName);//设置标题位置的文件名称
    //启动或停止loading动画
    void loadingAnimal(bool isstart);
    //视频文件上传完毕
    void uploadVideoFileDone(const QString& fileId);
private:
    QMovie* loading = nullptr;
    bool isUploadVideoOk = false;//视频文件是否上传完毕
    QString videoFileId;//上传的视频文件服务器返回的文件id
};

//uploadvideopage.cpp

UploadVideoPage::UploadVideoPage(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::UploadVideoPage)
{
    //初始化loading对像
    loading = new QMovie(":/images/uploadVideoPage/loading.gif",QByteArray(),ui->loadingLabel);
    ui->loadingLabel->setStyleSheet("QLabel { image-rendering: smooth; }");//设置抗锯齿
    ui->loadingLabel->setMovie(loading);
    ui->loadingLabel->setScaledContents(true);
    ui->loadingLabel->hide();
    //默认隐藏上传完毕的图标
    ui->downIcon->hide();
}
void UploadVideoPage::isUploading(bool isLoading)
{
    if(isLoading)
    {
        loadingAnimal(true);
        ui->uploadProgress->setText("上传中..");
        ui->downIcon->hide();
    }
    else
    {
        loadingAnimal(false);
        ui->uploadProgress->setText("文件上传完毕");
        ui->downIcon->show();
    }
}

void UploadVideoPage::setFileName(const QString &fileName)
{
    ui->fileName->setText(fileName);
}

void UploadVideoPage::loadingAnimal(bool isstart)
{
    if(isstart)
    {
        //显示loadingLabel并启动动画
        ui->loadingLabel->show();
        loading->start();
    }
    else
    {
        ui->loadingLabel->hide();
        loading->stop();
    }
}

void UploadVideoPage::uploadVideoFileDone(const QString &fileId)
{
    isUploading(false);
    isUploadVideoOk = true;
    videoFileId = fileId;
}

3.2获取视频首帧图

在工程构建目录下有一个名为ffmpeg的exe的文件,ffmpeg自然不用多说,如果需要详细了解ffmpeg的读者可以自行进行搜索,我们这里就是简单使用下获取视频首帧图,思路为:在我们当前程序中新启一个进程,然后将该进程替换为ffmpeg,并在程序中输入相应指令完成视频首帧图的获取,保存在工程目录下,然后设置到上传视频界面的封面图上根据首诊图片路径,获取视频首诊图的方法设置在mpvplayer类中,为一个静态方法:

//mpvplayer.cpp
QString MpvPlayer::getVideoFirstFrame(const QString &videoFileName)
{
    //获取视频首帧图片作为上传视频时的默认封面图
    QString ffmpegPath = QDir::currentPath() + "/ffmpeg/ffmpeg.exe";
    QString fristFrame = QDir::currentPath() + "/firstFrame.png";
    //使用新进程调用ffmpeg工具
    // 设置命令⾏参数
    QStringList cmd;
    cmd<<"-ss"<<"00:00:00"
        <<"-i"<<videoFileName
        <<"-vframes"<<"1"
        <<fristFrame;
    // 启动⼀个进程,⽤来管理ffmpeg⼯具
    QProcess ffmpegProgress;
    ffmpegProgress.start(ffmpegPath, cmd);
    // 等待进程完成:⽆限等待,直到进程结束
    if(!ffmpegProgress.waitForFinished(-1)){
        LOG()<<"ffmpeg 进程执⾏失败";
    }
    return fristFrame;
}

//设置到上传视频界面中:
void LimePlayer::switchUploadVideoPage(ButtonType buttonType, const QString &videoFilePath)
{
    //将视频首帧图设置到上传视频界面中
    ui->uploadVideo->setDefaultVideoFrame(videoFilePath);
}

void UploadVideoPage::setDefaultVideoFrame(const QString &videoFilePath)
{
    //获取视频首帧图并设置为默认视频封面图
    QString videoFirstFramePath = MpvPlayer::getVideoFirstFrame(videoFilePath);
    QPixmap pixmap(videoFirstFramePath);
    pixmap = pixmap.scaled(ui->imageLabel->size(),
                           Qt::KeepAspectRatioByExpanding,
                           Qt::SmoothTransformation);
    ui->imageLabel->setPixmap(pixmap);
    firstFramePath = videoFirstFramePath;
}

3.3上传视频封面图

这里我们可以复用之前上传头像的接口,但是这里有一个问题,如果我们直接去调用,那么返回的图像会被设置到头像区域。所以我们要在原来的上传图片的异步请求方法中新增一个参数来区分是上传头像还是上传视频封面图:

//uploadvideopage.cpp
void UploadVideoPage::upLoadPhoto(const QString &photoFilePath)
{
    //先将上传封面图设置为false状态,同时清空fileId
    isUploadVideoFrameOk = false;
    videoImageId = "";
    //发起上传图片请求
    auto dataCenter = model::DataCenter::getInstance();
    QByteArray imageData = loadFileToByteArray(photoFilePath);
    dataCenter->uploadPhotoAsync(imageData,ui->imageLabel);
}

void DataCenter::uploadPhotoAsync(const QByteArray &photoData,QWidget* whichPage)
{
    netClient.uploadPhoto(photoData,whichPage);
}

也就是凭借whichpage来区分是那个界面上传的图片,头像上传部分的修改不予展示,有需要的读者可以参考我文章开头提供的源码链接。
上传视频界面发起请求并处理响应:

//uploadvideopage.cpp
void UploadVideoPage::uploadVideoFileDone(const QString &fileId)
{
    isUploading(false);
    isUploadVideoOk = true;
    videoFileId = fileId;
    //进行视频封面图的上传
    upLoadPhoto(firstFramePath);
    QFile::remove(firstFramePath);//删除视频首帧图-切记要删除否则ffmpeg会因为原图片已存在而无法存放后续截取的视频首诊图片而导致程序崩溃
}


void UploadVideoPage::upLoadPhoto(const QString &photoFilePath)
{
    //先将上传封面图设置为false状态,同时清空fileId
    isUploadVideoFrameOk = false;
    videoImageId = "";
    //发起上传图片请求
    auto dataCenter = model::DataCenter::getInstance();
    QByteArray imageData = loadFileToByteArray(photoFilePath);
    dataCenter->uploadPhotoAsync(imageData,ui->imageLabel);
}

UploadVideoPage::UploadVideoPage(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::UploadVideoPage)
{
    //上传图片请求结束
    connect(dataCenter,&model::DataCenter::uploadPhotoDone,this,[=](const QString& fileId,QWidget* whichPage)
    {
        if(whichPage != ui->imageLabel)
        {
            return;
        }
        videoImageId = fileId;
        isUploadVideoFrameOk = true;
    });
}

当然如果用户需要修改封面图也需要进行对应的处理:

void UploadVideoPage::onChangeBtnClicked()
{
    QString imagePath = QFileDialog::getOpenFileName(nullptr,"选取视频封面","","Images (*.png *.xpm *.jpg)");
    if(!imagePath.isEmpty())
    {
        //设置视频封面图片并对图片进行裁剪
        QPixmap image(imagePath);
        //忽略原图像宽高比 | 设置为平滑缩放-scaled返回的是一个新的pixmap对象,而不会修改原图像
        image = image.scaled(ui->imageLabel->size(),Qt::IgnoreAspectRatio,Qt::SmoothTransformation);
        ui->imageLabel->setPixmap(image);
        //如果不立即重绘会导致图片显示是没有缩放的状态
        repaint();
        //将用户设置的新图片上传至服务端
        upLoadPhoto(imagePath);
    }
}

3.4获取视频总时长

这里的思路为,让mpv订阅duration事件,然后如果给定的渲染窗口为空,则禁用mpv的视频输出与音频输出接口。当使用mpv播放视频时,它的事件循环会收到一个总时长改变的事件,此时让其发送信号通知主界面程序去记录此总时长,即可成功获取用户上传视频总时长的信息:

//mpvplayer.cpp
MpvPlayer::MpvPlayer(QWidget* videoWindow,QObject *parent)
    : QObject{parent}
{
    if(videoWindow)
    {
        //获取视频窗口句柄
        int64_t wid = videoWindow->winId();
        //将窗口id传给mpv的mid选项,让mpv将视频渲染到指定窗口
        mpv_set_option(mpv,"wid",MPV_FORMAT_INT64,&wid);
    }
    else{
        mpv_set_option_string(mpv, "vo", "null");
        mpv_set_option_string(mpv, "ao", "null");
    }

    //订阅 duration 属性变化
    mpv_observe_property(mpv, 0, "duration", MPV_FORMAT_DOUBLE);
}

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;
        }
        //判断事件是否为timePos
        if (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);
        }else if (strcmp(eventPropery->name, "duration") == 0 && eventPropery->format == MPV_FORMAT_DOUBLE) {
            // 获取视频总时⻓
            int64_t duration = (int64_t)*(double *)eventPropery->data;
            emit durationChanged(duration);
        }
        break;
    }
}

在上传视频界面进行视频总时长的获取:

//limeplayer.cpp
void LimePlayer::switchUploadVideoPage(ButtonType buttonType, const QString &videoFilePath)
{
    //修改对应设置表示文件正在上传中
    ui->uploadVideo->isUploading(true);
    QString fileName = videoFilePath.mid(videoFilePath.lastIndexOf("/") + 1);
    ui->uploadVideo->setFileName(fileName);
    ui->stackedWidget->setCurrentIndex(buttonType);
    //将视频首帧图设置到上传视频界面中
    ui->uploadVideo->setDefaultVideoFrame(videoFilePath);
    //进行视频总时长的获取
    ui->uploadVideo->getVideoDuration(videoFilePath);
}

//uploadvideopage.cpp
void UploadVideoPage::getVideoDuration(const QString &videoPath)
{
    //获取总时长的请求结束
    mpvplayer = new MpvPlayer();
    //让mpv对当前视频进行播放后立马停止即可进行总时长改变事件的触发从而获取到当前视频的总时长
    mpvplayer->startPlay(videoPath);
    mpvplayer->pause();
    connect(mpvplayer,&MpvPlayer::durationChanged,this,[=](int64_t duration){
        LOG() << "获取到当前视频的总时长为:" << duration;
        this->duration = duration;
        if(mpvplayer != nullptr)
        {
            delete mpvplayer;
        }
        mpvplayer = nullptr;
    });
}

3.5上传视频描述信息

当用户选择提交按钮时,首先需要判断三个地方,视频文件是否已经上传至服务端,视频封面是否上传至服务端,视频总时长是否已经成功获取,一旦有一个没有完成,变弹出提示消息并禁止用户提交:

void UploadVideoPage::onCommitBtnClicked()
{
    if(ui->videoTitle->text().isEmpty())
    {
        //如果标题和简介为空不允许提交
        Toast::showToast("标题不可以为空T_T~~~");
        return;
    }
    //检查视频的重要内容是否全部获取成功,若上传成功再进行详细信息的整体上传
    if(!isUploadVideoOk)
    {
        Toast::showToast("视频文件还没有上传到服务端T_T");
        return;
    }
    if(!isUploadVideoFrameOk)
    {
        Toast::showToast("视频封面未上传完毕T_T");
        return;
    }
    if(duration == -1)
    {
        Toast::showToast("视频总时长还未获取完毕T_T");
        return;
    }
    if(isUploadVideoOk && isUploadVideoFrameOk && duration != -1)
    {
        model::VideoDesc desc;
        //此时视频相关的所有重要信息已经完全获取完毕
        desc.videoId = videoFileId;
        desc.photoId = videoImageId;
        desc.duration = duration;
        desc.tittle = ui->videoTitle->text();
        desc.desc = ui->videoDesc->toPlainText();
        desc.kind = ui->kinds->currentText();
        //获取视频的标签,若大于5个则让用户重新获取
        QList<QPushButton*> btns = ui->tagContent->findChildren<QPushButton*>();
        for(int i = 0;i < btns.size();i++)
        {
            if(btns[i]->isChecked())
            {
                //标签被选中进行添加
                desc.tags.push_back(btns[i]->text());
            }
        }
        if(desc.tags.size() > 5)
        {
            Toast::showToast("最多只可以选择5个标签");
            return;
        }
        //向服务端发射请求
        auto dataCenter = model::DataCenter::getInstance();
        dataCenter->newVideoAsync(desc);
        emit jumpToMyPage(MyPageBtn);
    }
}

data.h中新增视频信息的结构:

//data.h
// 视频描述信息-新增视频接⼝
class VideoDesc{
public:
    QString videoId; // 视频⽂件id
    QString photoId; // 视频封⾯id
    QString tittle; // 视频标题
    QString desc; // 视频描述
    QString kind; // 视频分类
    QList<QString> tags; // 视频标签
    int64_t duration; // 视频持续时⻓
};

接下来我们来设计服务端客户端的响应与请求接口:
请求URL:POST /HttpService/newVideo
请求参数

字段名称字段类型字段说明
requestIdstring请求ID
sessionIdstring客户端会话ID
videoInfoobject视频信息
videoFileIdstring视频文件ID
photoFileIdstring封面文件ID
durationinteger视频总时长
videoTypeinteger视频类型
videoTitlestring视频标题
videoDescstring视频简介
videoTagarray视频标签

返回响应:200 OK

响应参数

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

走老流程:

//datacenter.h
    //用户上传视频信息的请求
    void newVideoAsync(const model::VideoDesc& desc);
signals:
    //用户上传视频信息的请求成功响应
    void newVideoDone();
//datacenter.cpp
void DataCenter::newVideoAsync(const VideoDesc &desc)
{
    netClient.newVideo(desc);
}

//netclient.cpp
void NetClient::newVideo(const model::VideoDesc &desc)
{
    //请求报文的body
    QJsonObject reqbody;
    reqbody["requestId"] = makeRequestId();
    reqbody["sessionId"] = dataCenter->getSessionId();
    auto kindAndTags = dataCenter->getKindsAndTags();
    QJsonObject videoInfo;
    //填充视频的详细信息
    videoInfo["videoFileId"] = desc.videoId;
    videoInfo["photoFileId"] = desc.photoId;
    videoInfo["videoTitle"] = desc.tittle;
    videoInfo["duration"] = desc.duration;
    videoInfo["videoType"] = kindAndTags->getKindId(desc.kind);
    videoInfo["videoDesc"] = desc.desc;
    QJsonArray videoTag;
    for(auto& tag : desc.tags)
    {
        videoTag.append(kindAndTags->getTagId(desc.kind,tag));
    }
    videoInfo["videoTag"] = videoTag;
    reqbody["videoInfo"] = videoInfo;
    LOG() << reqbody;
    QNetworkReply* reply = sendReqToHttpServer("/HttpService/newVideo",reqbody);
    //异步处理服务端的response
    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();
        //解析成功,处理响应信息
        emit dataCenter->newVideoDone();
        LOG() << "newVideo请求结束,视频的对应信息已经上传至服务端";
    });
}

//mockserver.cpp
QHttpServerResponse MockServer::newVideo(const QHttpServerRequest &request)
{
    QJsonObject reqData = QJsonDocument::fromJson(request.body()).object();
    LOG() << "[newVideo] 收到 newVideo 请求, requestId = " <<reqData["requestId"].toString() << "sessionId = " << reqData["sessionId"].toString();
    QJsonObject videoInfo = reqData["videoInfo"].toObject();
    LOG() << "视频文件id为:" << videoInfo["videoFileId"].toString();
    LOG() << "图片文件id为:" << videoInfo["photoFileId"].toString();
    LOG() << "视频标题为:" << videoInfo["videoTitle"].toString();
    LOG() << "视频总时长为:" << videoInfo["duration"].toInteger();
    LOG() << "视频分类为:" << videoInfo["videoType"].toInt();
    LOG() << "视频描述信息为:" << videoInfo["videoDesc"].toString();
    QJsonArray tags = videoInfo["videoTag"].toArray();
    LOG() << "视频对应的标签如下:";
    for(int i = 0;i < tags.size();i++)
    {
        LOG() << tags[i].toInt();
    }
    //构造响应信息
    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;
}

视频上传成功需要清空上传视频界面原来的信息:

UploadVideoPage::UploadVideoPage(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::UploadVideoPage)
{
    //上传视频详细信息请求结束
    connect(dataCenter,&model::DataCenter::newVideoDone,this,[=](){
        //清空界面视频信息
        reset();
        LOG() << "视频的详细信息成功上传到服务端!";
    });
}

void UploadVideoPage::reset()
{
    //清空类成员对象中的残留内容
    isUploadVideoOk = false;
    videoFileId = "";
    isUploadVideoFrameOk = false;
    videoImageId = "";
    firstFramePath = "";
    duration = -1;
    //清空界面中的内容或还原为默认值
    ui->fileName->setText("【这⾥是⽂件名】直接取源⽂件名称即可");
    ui->uploadProgress->setText("上传中...");
    ui->imageLabel->setStyleSheet("border-image :url(:/images/uploadVideoPage/fengmian.png);");
    ui->videoTitle->setText("");
    ui->videoDesc->setPlainText("");
    ui->kinds->setCurrentIndex(-1);
    addTagsByKind("");//清空标签按钮
}

四.视频审核界面

4.1获取审核视频列表并更新到审核界面

先阐述下我们做这部分内容的思路,先在客户端添加响应的接口去进行视频列表的获取,然后当视频拉取下来之后,将视频更新到视频审核界面中,并让checkTableItem进行信息的填入与相应样式的修该,当然我们需要在datacenter中增加一个审核视频列表用来存储从服务端拉取下来的视频。
再说细节上的处理,因为我们的视频审核界面有两种视频获取方式,一种是根据用户id进行获取,一种是根据状态进行获取。我们的思路为,如果id不为空,按照用户id进行获取,如果id为空再看状态。有人要问了,那如果id与状态均不为空是否可以组合获取,不可以,因为懒不想实现~,需要添加这个功能读者可以自己设计套接口,我这里就偷下懒了。废话不多说,开始这部分的实现:

//datacenter.h
#ifndef DATACENTER_H
#define DATACENTER_H

#include <QObject>
#include "data.h"
#include "./../netclient/netclient.h"
#include <QJsonObject>
#include <QJsonArray>


namespace model{

class DataCenter : public QObject
{
    Q_OBJECT
public:
    void setCheckVideoList(const QJsonObject& result);//通过网络获取的json对象更新当前审核界面的视频
    VideoList* getCheckVideoListPtr();//获取审核视频列表指针
private:
    //审核界面的视频列表
    VideoList* checkVideoList = nullptr;

public:
    //客户端请求审核视频列表-与用户视频列表没有区别
    void getCheckVideoListAsync(const QString& userId,int pageIndex);
signals:
    //获取审核视频列表成功
    void getCheckVideoListDone();
};
}

#endif // DATACENTER_H

//datacenter.cpp
void DataCenter::setCheckVideoList(const QJsonObject &result)
{
    getCheckVideoListPtr();//防止CheckVideoListPtr为空
    model::VideoList* checkVideoList = getCheckVideoListPtr();
    checkVideoList->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);
        checkVideoList->addVideo(videoInfo);
    }
}

VideoList *DataCenter::getCheckVideoListPtr()
{
    if(checkVideoList == nullptr)
    {
        //初始化审核视频列表
        checkVideoList = new VideoList();
    }
    return checkVideoList;
}

void DataCenter::getCheckVideoListAsync(const QString &userId, int pageIndex)
{
    netClient.getCheckVideoList(userId,pageIndex);
}

//netClient.cpp
void NetClient::getCheckVideoList(const QString &userId, int pageIndex)
{
    /*
     * {
        "requestId": "string",
        "sessionId": "string",
        "userId": "string",
        "pageIndex": 0,
        "pageCount": 0
       }
     */
    //请求报文的body
    QJsonObject 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);
    //异步处理服务端的response
    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();
        dataCenter->setCheckVideoList(result);
        LOG() << "getCheckVideoList请求结束,获取审核视频列表请求成功!";
        emit dataCenter->getCheckVideoListDone();
    });
}

接下来审核视频界面进行对应的更新-同时根据获取的内容是否为第一页进行分页器的设置:

//checkTable.h
#ifndef CHECKTABLE_H
#define CHECKTABLE_H

#include <QWidget>
#include "paginator.h"

namespace Ui {
class CheckTable;
}

class CheckTable : public QWidget
{
    Q_OBJECT

public:
    explicit CheckTable(QWidget *parent = nullptr);
    void updateCheckTable();
    void resetPaginator(int pageCount);
    ~CheckTable();

private:
    void onResetBtnClicked();
    void onQueryBtnClicked();
    void getVideoList(int page);
private:
    Ui::CheckTable *ui;
    int page = 1;//当前获取的是第几页视频
    Paginator* paginator = nullptr;
    QString userId;
    int videoStatus = 0;//用户进行点击或查询时保存状态-如果我在点击分页器按钮时,修改了编辑栏上的userid或状态,就会出问题
};

#endif // CHECKTABLE_H

//checktable.cpp
#include "checktable.h"
#include "ui_checktable.h"
#include "checktableitem.h"
#include "./model/datacenter.h"
#include "toast.h"
#include <QRegularExpressionValidator>
#include <QRegularExpression>

CheckTable::CheckTable(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::CheckTable)
{
    ui->setupUi(this);
    ui->videoStatus->addItem("全部分类");
    ui->videoStatus->addItem("待审核");
    ui->videoStatus->addItem("审核通过");
    ui->videoStatus->addItem("审核驳回");
    ui->videoStatus->addItem("已下架");
    ui->videoStatus->setCurrentIndex(0);

    //设置正则表达式以限制用户Id的输入
    QRegularExpression regExp("^[0-9a-f]{4}-[0-9a-f]{8}-[0-9a-f]{4}$");
    QRegularExpressionValidator* validator = new QRegularExpressionValidator(regExp,this);
    //设置验证器
    ui->userIdEdit->setValidator(validator);
    //连接重置按钮与查询按钮的信号槽
    connect(ui->resetBtn,&QPushButton::clicked,this,&CheckTable::onResetBtnClicked);
    connect(ui->queryBtn,&QPushButton::clicked,this,&CheckTable::onQueryBtnClicked);
    //更新checkTable页面
    auto dataCenter = model::DataCenter::getInstance();
    connect(dataCenter,&model::DataCenter::getCheckVideoListDone,this,&CheckTable::updateCheckTable);
    connect(dataCenter,&model::DataCenter::getStatusVideoListDone,this,&CheckTable::updateCheckTable);
}

void CheckTable::updateCheckTable()
{
    //此时已经从服务端拉取到视频数据了,先清空界面中原来的条目信息
    // 清空界面上的旧视频内容
    QLayoutItem* item = nullptr;
    while(nullptr != (item = ui->layout->takeAt(0))){
        delete item->widget();
        delete item;
    }

    // 将DataCenter中保存到获取到的视频列表中的视频更新到界面
    auto dataCenter = model::DataCenter::getInstance();
    auto checkVideoList = dataCenter->getCheckVideoListPtr();
    if(nullptr == checkVideoList){
        return;
    }

    //向界面中更新视频条目
    QList<model::VideoInfo>& checkVideoInfos = checkVideoList->videoInfos;
    for(int i = 0;i < checkVideoInfos.size();i++)
    {
        CheckTableItem* checktableItem = new CheckTableItem(checkVideoInfos[i],this);
        ui->layout->addWidget(checktableItem);
    }
    //重置分页器
    int pageCount = model::VideoList::PAGE_COUNT;
    if(page == 1)
    {
        resetPaginator((checkVideoList->getVideoTotalCount() + pageCount - 1)/pageCount);
    }
}

void CheckTable::resetPaginator(int pageCount)
{
    // 因为总页数发生变化之后,分页器上显示的页数需要重新更新,因此可以先将旧的分页器销毁掉重新创建
    if(paginator){
        delete paginator;
    }

    paginator = new Paginator(ui->PaginatorArea,pageCount,7);
    paginator->move(0, 15);
    paginator->show();

    connect(paginator, &Paginator::pageChanged, this, [=](int page){
        getVideoList(page);
    });
}

CheckTable::~CheckTable()
{
    delete ui;
}

void CheckTable::onResetBtnClicked()
{
    ui->userIdEdit->setText("");
    ui->videoStatus->setCurrentIndex(0);
    userId = "";
    videoStatus = 0;
    auto datacenter = model::DataCenter::getInstance();
    auto myselfInfo = datacenter->getMyselfUserInfo();
    if(myselfInfo->isAdminDisable())
    {
        Toast::showToast("您的管理员权限目前处于禁止状态");
        return;
    }
    getVideoList(1);
}

void CheckTable::onQueryBtnClicked()
{
    auto datacenter = model::DataCenter::getInstance();
    auto myselfInfo = datacenter->getMyselfUserInfo();
    QString userId = ui->userIdEdit->text();
    videoStatus = ui->videoStatus->currentIndex();
    if(myselfInfo->isAdminDisable())
    {
        Toast::showToast("您的管理员权限目前处于禁止状态");
        return;
    }
    getVideoList(1);
}

void CheckTable::getVideoList(int page)
{
    this->page = page;
    auto dataCenter = model::DataCenter::getInstance();
    // 先清空DataCenter中保存的原有的旧视频,然后再保存新page中的视频
    // 因为在视频审核页面,界面中使用都加载一页的视频,无需缓存其他页面的视频
    auto checkVideoList = dataCenter->getCheckVideoListPtr();
    checkVideoList->videoInfos.clear();
    //优先通过userId进行视频列表的获取
    if(!userId.isEmpty())
    {
        dataCenter->getCheckVideoListAsync(userId,page);
    }
    else{
        //根据管理员选择的状态进行视频列表的获取
    }
}

当视频更新到界面上时需要对相应的checkTableItem进行样式设置:

//checktableitem.h
#ifndef CHECKTABLEITEM_H
#define CHECKTABLEITEM_H

#include <QWidget>
#include "./model/data.h"

namespace Ui {
class CheckTableItem;
}

class CheckTableItem : public QWidget
{
    Q_OBJECT

public:
    explicit CheckTableItem(model::VideoInfo& videoInfo,QWidget *parent = nullptr);

    ~CheckTableItem();

private:
    void initStyleHash();
    void setOperationAndStatusBtnFromStatus(model::VideoStatus status);
    void operationBtnClicked();
    void operationBtnClicked2();
private:
    Ui::CheckTableItem *ui;
    model::VideoInfo& videoInfo;
    QHash<QString,QString> styleSheet;
};

#endif // CHECKTABLEITEM_H


//checktableitem.cpp
#include "checktableitem.h"
#include "ui_checktableitem.h"
#include "./model/datacenter.h"
#include "util.h"
#include "playerpage.h"

CheckTableItem::CheckTableItem(model::VideoInfo& videoInfo,QWidget *parent)
    : QWidget(parent)
    , videoInfo(videoInfo)
    , ui(new Ui::CheckTableItem)
{
    ui->setupUi(this);
    //初始化界面信息
    ui->videoUserId->setText(videoInfo.userId);
    ui->nickNameLabel->setText(videoInfo.nickname);
    ui->videoTitleLabel->setText(videoInfo.videoTitle);
    ui->checkerLabel->setText(videoInfo.checkerName);//审核者
    //根据审核状态设置对应样式,同时设置对应按钮
    initStyleHash();
    setOperationAndStatusBtnFromStatus(static_cast<model::VideoStatus>(videoInfo.videoStatus));
    //获取视频封面
    auto dataCenter = model::DataCenter::getInstance();
    dataCenter->downloadPhotoAsync(videoInfo.photoFileId);
    connect(dataCenter,&model::DataCenter::downloadPhotoDone,this,[=](const QString& imageId,QByteArray imageData){
        if(imageId != videoInfo.photoFileId)
        {
            return;
        }
        //更新视频封面到videoBtn上
        ui->videoBtn->setIcon(makeIcon(imageData));
    });
    connect(ui->operationBtn,&QPushButton::clicked,this,&CheckTableItem::operationBtnClicked);
    connect(ui->operationBtn2,&QPushButton::clicked,this,&CheckTableItem::operationBtnClicked2);
}

void CheckTableItem::initStyleHash()
{
    styleSheet.insert("待审核", "#statusBtn{"
                                "border : none;"
                                "background-color : #FFF0E6;"
                                "font-size : 12px;"
                                "color : #FE964A;"
                                "border-radius : 10px;}");
    styleSheet.insert("通过", "#operationBtn{"
                              "border : none;"
                              "font-size : 14px;"
                              "color : #3686FF}");
    styleSheet.insert("驳回", "#operationBtn2{"
                              "border : none;"
                              "font-size : 14px;"
                              "color : #FD6A6A;}");

    styleSheet.insert("已审核", "#statusBtn{"
                                "border : none;"
                                "background-color : #EBF3FF;"
                                "font-size : 12px;"
                                "color : #3686FF;"
                                "border-radius : 10px;}");
    styleSheet.insert("下架", "#operationBtn{"
                              "border : none;"
                              "font-size : 14px;"
                              "color : #FD6A6A}");

    styleSheet.insert("已下架", "#statusBtn{"
                                "border : none;"
                                "background-color : #FFF0F0;"
                                "font-size : 12px;"
                                "color : #FD6A6A;"
                                "border-radius : 10px;}");
    styleSheet.insert("上架", "#operationBtn{"
                              "border : none;"
                              "font-size : 14px;"
                              "color : #3ECEFF}");

    styleSheet.insert("已驳回", "#statusBtn{"
                                "border : none;"
                                "background-color : #FFF0E6;"
                                "font-size : 12px;"
                                "color : #EF964A;"
                                "border-radius : 10px;}");
    styleSheet.insert("--", "#operationBtn{"
                            "border : none;"
                            "font-size : 14px;"
                            "color : #222222}");
}

void CheckTableItem::setOperationAndStatusBtnFromStatus(model::VideoStatus status)
{
    ui->operationBtn->move(1186, 51);
    ui->operationBtn2->hide();
    switch(status){
        case model::VideoStatus::putaway:{
            //已审核
            ui->statusBtn->setText("已审核");
            ui->operationBtn->setText("下架");
            break;
        }
        case model::VideoStatus::waitForChecking:{
            //待审核
            ui->statusBtn->setText("待审核");
            ui->operationBtn->setText("通过");
            ui->operationBtn->move(1156, 51);
            ui->operationBtn2->setText("驳回");
            ui->operationBtn2->show();
            break;
        }
        case model::VideoStatus::discard:{
            //已下架
            ui->statusBtn->setText("已下架");
            ui->operationBtn->setText("上架");
            break;
        }
        case model::VideoStatus::reject:{
            //已驳回
            ui->statusBtn->setText("已驳回");
            ui->operationBtn->setText("--");
            break;
        }
        default:{
            LOG() << "未知的视频状态";
        }
    }
    //根据设定好的文本进行样式设置
    ui->statusBtn->setStyleSheet(styleSheet[ui->statusBtn->text()]);
    ui->operationBtn->setStyleSheet(styleSheet[ui->operationBtn->text()]);
    ui->operationBtn2->setStyleSheet(styleSheet[ui->operationBtn2->text()]);
}

void CheckTableItem::operationBtnClicked()
{
    auto dataCenter = model::DataCenter::getInstance();
    QString btnText = ui->operationBtn->text();
    if(btnText == "通过" )
    {
        videoInfo.videoStatus = model::VideoStatus::putaway;
        //进行视频审核请求发送
    }
    else if(btnText == "上架")
    {
        videoInfo.videoStatus = model::VideoStatus::putaway;
        //进行视频上架请求发送
    }
    else if(btnText == "下架")
    {
        videoInfo.videoStatus = model::VideoStatus::discard;
        //进行视频下架请求发送
    }
    videoInfo.checkerAvatar = dataCenter->getMyselfUserInfo()->avatarFileId;
    videoInfo.checkerId = dataCenter->getMyselfUserInfo()->userId;
    videoInfo.checkerName = dataCenter->getMyselfUserInfo()->nickname;
    setOperationAndStatusBtnFromStatus(static_cast<model::VideoStatus>(videoInfo.videoStatus));
    ui->checkerLabel->setText(videoInfo.checkerName);
}

void CheckTableItem::operationBtnClicked2()
{
    auto dataCenter = model::DataCenter::getInstance();
    QString btnText = ui->operationBtn2->text();
    if(btnText == "驳回")
    {
        videoInfo.videoStatus = model::VideoStatus::reject;
        //进行视频审核请求发送
    }
    videoInfo.checkerAvatar = dataCenter->getMyselfUserInfo()->avatarFileId;
    videoInfo.checkerId = dataCenter->getMyselfUserInfo()->userId;
    videoInfo.checkerName = dataCenter->getMyselfUserInfo()->nickname;
    setOperationAndStatusBtnFromStatus(static_cast<model::VideoStatus>(videoInfo.videoStatus));
    ui->checkerLabel->setText(videoInfo.checkerName);
}

CheckTableItem::~CheckTableItem()
{
    delete ui;
}

4.2审核界面视频播放支持

我们当时再播放视频界面处理时,是当获取到弹幕数据时才去进行视频播放。那我们这里视频一共有四种状态:待审核,已审核,已驳回,已下架。这四种情况只有一种情况是没有弹幕数据的,那就是待审核状态,也就是某个用户刚上传至服务端的视频。所以视频播放时,如果视频为待审核状态,那么就需要禁止其发送弹幕,点赞。而此时也肯定没有弹幕,所以当视频状态为待审核状态时直接跳过弹幕获取,以及禁止播放数的更新:

//playerpage.cpp
void PlayerPage::onLikeImageBtnClicked()
{
    auto dataCenter = model::DataCenter::getInstance();
    auto myselfUserInfo = dataCenter->getMyselfUserInfo();
    if(myselfUserInfo->isTempUser())
    {
        Toast::showToast("登录之后才可以点赞哦~",login);
        return;
    }
    if(videoInfo.videoStatus == model::VideoStatus::waitForChecking)
    {
        Toast::showToast("视频状态禁止点赞!");
        return;
    }
    //因为用户在观看视频时可能频繁的点击点赞按钮,所以我们在关闭窗口时再去检验点赞情况发送如有需要修改请求即可
    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));
}

////////////////////////////////////////////////////////mpv播放器控制播放部分///////////////////////////////////////////////////////////////////
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);
    }
    //如果为待审核状态则直接进行视频播放
    if(videoInfo.videoStatus != model::VideoStatus::waitForChecking)
        loadBulletScreenData();//加载完毕弹幕数据后再进行播放
    else
        _startPlayer();
}

void PlayerPage::_startPlayer()
{
    auto datacenter = model::DataCenter::getInstance();
    QString videoPath = datacenter->getHttpUrl() + "/HttpService/downloadVideo?fileId=" + videoInfo.videoFileId;
    player->startPlay(videoPath);
}

///////////////////////////////////////////////////////////////弹幕相关函数///////////////////////////////////////////////////////////////////

void PlayerPage::downloadVideoBarrageSuccess(const QString& videoId,const QJsonObject& barrages)
{
    auto datacenter = model::DataCenter::getInstance();
    QJsonArray barrageLists = barrages["barrageList"].toArray();
    //不是我当前视频的弹幕直接拒绝
    if(videoId != videoInfo.videoId)
    {
        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);
    //弹幕数据加载完毕,开始视频数据的加载
    _startPlayer();
    //更新视频播放数
    videoInfo.playCount++;
    datacenter->setPlayNumberAsync(videoInfo.videoId);
    datacenter->updateVideoPlayNumber(videoInfo.videoId,videoInfo.playCount);//发射更新请求并更新本地存储的数据
    ui->playNum->setText(intToString(videoInfo.playCount));//更新播放器界面的播放数
    emit this->videoBoxUpdatePlayNum(videoInfo.playCount);//更新videoBox界面的播放数
    //点赞相关
    datacenter->judgeLikeAsync(videoId);
}

void PlayerPage::onBulletLaunch(const QString &bullet)
{
    //先判断当前用户是否为临时用户,再判断用户是否开启弹幕窗口
    auto dataCenter = model::DataCenter::getInstance();
    auto myselfUserInfo = dataCenter->getMyselfUserInfo();
    if(myselfUserInfo->isTempUser())
    {
        Toast::showToast("登录之后才可以发送弹幕哦~",login);
        return;
    }
    if(videoInfo.videoStatus == model::VideoStatus::waitForChecking)
    {
        Toast::showToast("视频状态禁止发射弹幕!");
        return;
    }
    if(isShowBullet)
    {
        //当开启弹幕时才允许发射弹幕
        BulletScreenItem* bs = new BulletScreenItem(bottom);
        bs->setText(bullet);
        bs->setImage(QPixmap(":/images/homePage/touxiang.png"));
        int duration = 10000 / (double)width() * (width() + bs->width());
        bs->initAnimation(width() + bs->width(),duration);
        bs->startAnimation();
        BulletScreenInfo barrageInfo("1",player->getCurrentTime(),bullet);
        bulletScreenLists[player->getCurrentTime()].append(barrageInfo);
        //向服务端发射弹幕
        auto dataCenter = model::DataCenter::getInstance();
        dataCenter->newBarrageAsync(videoInfo.videoId,barrageInfo);
    }
}

接下来让checktableitem的videoBtn点击之后可以进行视频的播放:

//checktableitem.h
    void onVideoBtnClicked();

//checktableitem.cpp
void CheckTableItem::onVideoBtnClicked()
{
    //初始化视频播放窗口-默认不显示-当点击视频界面时再去创建PlayerPage且不需要手动释放,窗口关闭时PlayerPage会自己释放
    PlayerPage* videoPlayer = new PlayerPage(videoInfo);
    auto dataCenter = model::DataCenter::getInstance();
    //待审核状态下直接进行视频播放
    videoPlayer->show();
    videoPlayer->startPlayer();
    //获取用户头像
    dataCenter->downloadPhotoAsync(videoInfo.userAvatarId);
    connect(dataCenter,&model::DataCenter::downloadPhotoDone,this,[=](const QString& imageId,QByteArray imageData){
        if(imageId != videoInfo.userAvatarId)
        {
            return;
        }
        videoPlayer->setUserAvatar(imageData);
    });
}

4.3状态视频列表的获取

请求url:POST /HttpService/statusVideoList
请求参数

字段名称字段类型字段说明
requestIdstring请求ID
sessionIdstring客户端会话ID
videoStatusinteger视频状态
pageIndexinteger页码
pageCountinteger每页条目数量

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

响应参数

字段名称字段类型字段说明
requestIdstring请求ID
errorCodeinteger错误码;0-成功
errorMsgstring错误信息
resultobject响应结果
videoListarray视频列表
videoIdstring视频ID
userIdstring所属用户ID
userAvatarIdstring用户头像ID
nicknamestring所属用户名称
checkerIdstring审核者用户ID
checkerAvatarstring审核者用户头像ID
checkerNamestring审核者用户名称
videoFileIdstring视频文件ID
photoFileIdstring封面文件ID
likeCountinteger点赞数量
playCountinteger播放数量
videoSizeinteger视频大小
videoDescstring视频简介
videoTitlestring视频标题
videoDurationinteger视频时长
videoUpTimestring视频上传时间

然后就是老一套流程:

//datacenter.h
    //获取状态视频列表
    void getStatusVideoListAsync(int videoStatus,int pageIndex);
signals:
    //获取状态视频列表成功
    void getStatusVideoListDone();
//datacenter.cpp
void DataCenter::getStatusVideoListAsync(int videoStatus, int pageIndex)
{
    netClient.getStatusVideoList(videoStatus,pageIndex);
}

//netclient.cpp
void NetClient::getStatusVideoList(int videoStatus, int pageIndex)
{
    /*
     * "requestId": "string",
     * "sessionId": "string",
     * "videoStatus": 0,
     * "pageIndex": 0,
     * "pageCount": 0
     */
    //请求报文的body
    QJsonObject reqbody;
    reqbody["requestId"] = makeRequestId();
    reqbody["sessionId"] = dataCenter->getSessionId();
    reqbody["videoStatus"] = videoStatus;
    reqbody["pageIndex"] = pageIndex;
    reqbody["pageCount"] = model::VideoList::PAGE_COUNT;
    QNetworkReply* reply = sendReqToHttpServer("/HttpService/statusVideoList",reqbody);
    //异步处理服务端的response
    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();
        dataCenter->setCheckVideoList(result);
        emit dataCenter->getStatusVideoListDone();
    });
}

//mockserver.cpp
QHttpServerResponse MockServer::getStatusVideoList(const QHttpServerRequest &request)
{
    //构建body信息
    QJsonObject reqData = QJsonDocument::fromJson(request.body()).object();
    int videoStatus = reqData["videoStatus"].toInt();
    LOG() << "[getStatusVideoList] 收到 getStatusVideoList 请求, requestId = " <<reqData["requestId"].toString() << "sessionId = " << reqData["sessionId"].toString() << "videoStatus = " << videoStatus;
    //获取页号与获取视频数目
    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;
    //总视频个数设置为200
    result["totalCount"] = 200;
    QJsonArray videoList;
    //构造假数据
    int videoId = 40000;
    int userId = 40000;
    int resourceId = 4000;
    int maxVideoNum = qMin(200,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"] = 231;
        videoJsonObj["videoUpTime"] = "2024-05-04 17:11:11";
        //假审核人员数据
        videoJsonObj["videoStatus"] = videoStatus == 0 ? rand() % 4 + 1 : videoStatus;
        videoJsonObj["checkerId"] = "114514";
        videoJsonObj["checkerName"] = "张三";
        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;
}

然后链接信号槽向界面中更新视频列表,在刚刚的getVideoList中的注释部分发起请求即可

4.4审核界面审核功能的支持

审核界面不支持审核功能,就像老婆饼里没有老婆一样,哎不对这个比喻好像不恰当。不管了反正就这个意思。我们接下来实现视频审核功能,那么一共需要有三套接口:视频审核请求,视频下架请求以及视频上架请求。三套请求接口设计如下:
审核视频:
请求URL:POST /HttpService/checkVideo
请求参数

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

返回响应:200 OK

响应参数

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

视频上架:
请求URL:POST /HttpService/saleVideo
请求参数

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

返回响应:200 OK

响应参数

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

视频下架:
请求URL:POST /HttpService/haltVideo
请求参数

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

返回响应:200 OK

响应参数

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

对三者进行老一套流程处理即可:

//datacenter.h
    //审核视频
    void checkVideoAsync(const QString& videoId,bool checkResult);
    //视频上架
    void saleVideoAsync(const QString& videoId);
    //视频下架
    void haltVideoAsync(const QString& videoId);
signals:
    //视频审核状态修改成功
    void checkVideoDone();
    //视频上架成功
    void saleVideoDone();
    //视频下架成功
    void haltVideoDone();
//datacenter.cpp
void DataCenter::checkVideoAsync(const QString &videoId, bool checkResult)
{
    netClient.checkVideo(videoId,checkResult);
}

void DataCenter::saleVideoAsync(const QString &videoId)
{
    netClient.saleVideo(videoId);
}

void DataCenter::haltVideoAsync(const QString &videoId)
{
    netClient.haltVideo(videoId);
}

//netclient.cpp
void NetClient::checkVideo(const QString &videoId, bool checkResult)
{
    //请求报文的body
    QJsonObject reqbody;
    reqbody["requestId"] = makeRequestId();
    reqbody["sessionId"] = dataCenter->getSessionId();
    reqbody["videoId"] = videoId;
    reqbody["checkResult"] = checkResult;
    QNetworkReply* reply = sendReqToHttpServer("/HttpService/checkVideo",reqbody);
    //异步处理服务端的response
    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();
        //解析成功,处理响应信息
        emit dataCenter->checkVideoDone();
    });
}

void NetClient::saleVideo(const QString &videoId)
{
    //请求报文的body
    QJsonObject reqbody;
    reqbody["requestId"] = makeRequestId();
    reqbody["sessionId"] = dataCenter->getSessionId();
    reqbody["videoId"] = videoId;
    QNetworkReply* reply = sendReqToHttpServer("/HttpService/saleVideo",reqbody);
    //异步处理服务端的response
    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();
        //解析成功,处理响应信息
        emit dataCenter->saleVideoDone();
    });
}

void NetClient::haltVideo(const QString &videoId)
{
    //请求报文的body
    QJsonObject reqbody;
    reqbody["requestId"] = makeRequestId();
    reqbody["sessionId"] = dataCenter->getSessionId();
    reqbody["videoId"] = videoId;
    QNetworkReply* reply = sendReqToHttpServer("/HttpService/haltVideo",reqbody);
    //异步处理服务端的response
    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();
        //解析成功,处理响应信息
        emit dataCenter->haltVideoDone();
    });
}

//mockserver.cpp
QHttpServerResponse MockServer::checkVideo(const QHttpServerRequest &request)
{
    QJsonObject reqData = QJsonDocument::fromJson(request.body()).object();
    LOG() << "[checkVideo] 收到 checkVideo 请求, requestId = " <<reqData["requestId"].toString() << "sessionId = " << reqData["sessionId"].toString();
    LOG() << "视频:" << reqData["videoId"].toString() << "审核情况:" << reqData["checkResult"].toBool();
    //构造响应信息
    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::saleVideo(const QHttpServerRequest &request)
{
    QJsonObject reqData = QJsonDocument::fromJson(request.body()).object();
    LOG() << "[saleVideo] 收到 saleVideo 请求, requestId = " <<reqData["requestId"].toString() << "sessionId = " << reqData["sessionId"].toString();
    LOG() << "视频上架:" << 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;
}

QHttpServerResponse MockServer::haltVideo(const QHttpServerRequest &request)
{
    QJsonObject reqData = QJsonDocument::fromJson(request.body()).object();
    LOG() << "[haltVideo] 收到 haltVideo 请求, requestId = " <<reqData["requestId"].toString() << "sessionId = " << reqData["sessionId"].toString();
    LOG() << "视频下架:" << 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;
}

接下来在checktableitem中进行相应的信号槽处理即可。就是在之前的checktableitem.cpp的注释部分加上请求即可。

五.管理员角色管理界面

5.0整体思路

先说下这部分的整体大致思路与限制要求,当我们点击查询时如果手机号不为空按照手机号进行获取,为空则按照状态进行获取。因为手机号是唯一的,所以这里第一种获取方式只会至多有一条数据。然后是编辑与新增操作当管理员点击新增操作之后因为是新增所以不需要任何改变,而后是编辑,则需要将对应条目的管理员的信息加载到编辑界面。但是这里有两个禁止:不允许编辑超管账号,不允许自己启用或禁用自己,同时还无法设置自己的身份为超级管理员。对于删除操作,普通管理员之间可以相互删除,但是不允许自己删除自己,也不允许删除超管账号。

5.1定义管理员信息与管理员列表

//data.h
/////////////////////////////////////////////////
/// 管理员信息结构
/////////////////////////////////////////////////
class AdminInfo
{
public:
    QString userId;
    QString nickname;
    RoleType roleType;
    QString phoneNumber;
    AdminStatus userStatu;
    QString userMemo;

    void loadAdminInfo(const QJsonObject& adminJson);
};

class AdminList
{
public:
    // 添加管理员
    void addAdminInfo(const AdminInfo& adminInfo);
    void setAdminStatus(const QString& userId, AdminStatus adminStatus);
    void clearAdminList();

    QList<AdminInfo> adminInfos;          // 保存管理员信息
    int64_t totalCount;                   // 系统中包含的管理员的总个数
    const static int PAGE_COUNT = 20;     // 默认一个页面显示20个管理员信息
};

//data.cpp
void AdminInfo::loadAdminInfo(const QJsonObject &adminJson)
{
    userId = adminJson["userId"].toString();
    nickname = adminJson["nickname"].toString();
    roleType = static_cast<RoleType>(adminJson["roleType"].toInt());
    phoneNumber = adminJson["phoneNumber"].toString();
    userStatu = static_cast<AdminStatus>(adminJson["userStatus"].toInt());
    userMemo = adminJson["userMemo"].toString();
}

void AdminList::addAdminInfo(const AdminInfo &adminInfo)
{
    adminInfos.append(adminInfo);
}

void AdminList::setAdminStatus(const QString &userId, AdminStatus adminStatus)
{
    for(auto& adminInfo : adminInfos){
        if(adminInfo.userId == userId){
            adminInfo.userStatu = adminStatus;
            return;
        }
    }
}

void AdminList::clearAdminList()
{
    adminInfos.clear();
    totalCount = 0;
}

在datacenter中增加AdminList*成员变量:

//datacenter.cpp
    AdminList* getAdminListPtr();
    void setAdminList(const QJsonObject& result,bool isgetForPhone);
private:
    explicit DataCenter(QObject *parent = nullptr);
private:
    //审核界面的管理员列表
    AdminList* adminList = nullptr;

//datacenter.cpp
AdminList *DataCenter::getAdminListPtr()
{
    if(adminList == nullptr)
    {
        adminList = new AdminList();
    }
    return adminList;
}

void DataCenter::setAdminList(const QJsonObject &result,bool isgetForPhone)
{
    getAdminListPtr();
    if(isgetForPhone)
    {
        adminList->totalCount = 1;
        AdminInfo adminInfo;
        adminInfo.loadAdminInfo(result["userInfo"].toObject());
        adminList->addAdminInfo(adminInfo);
    }
    else{
        //说明是根据管理员状态进行获取的
        adminList->totalCount = result["totalCount"].toInt();
        QJsonArray userList = result["userList"].toArray();
        for(int i = 0;i < userList.size();i++)
        {
            AdminInfo adminInfo;
            adminInfo.loadAdminInfo(userList[i].toObject());
            adminList->addAdminInfo(adminInfo);
        }
    }
}

5.2角色管理界面主体逻辑实现

因为这部分与视频审核界面大有重合,所以我直接给出设计好的相关接口与供读者参考,大家可以先参考接口尝试实现下。不会了再参考我给出的实现代码详情可见我上传的源码部分。(实则是最后一部分懒了不想写了QAQ~)。

5.2.1通过手机号获取管理员信息

请求URL: POST /HttpService/getAdminByPhone
请求参数

字段名称字段类型字段说明
requestIdstring请求ID
sessionIdstring客户端会话ID
phoneNumberstring手机号

返回响应:200 OK

响应参数

字段名称字段类型字段说明
requestIdstring请求ID
errorCodeinteger错误码;0-成功
errorMsgstring错误信息
resultobject响应结果
userInfoobject用户信息对象
userIdstring用户ID
nicknamestring用户昵称
roleTypeinteger角色类型
userStatusinteger用户状态
userMemostring用户备注信息
phoneNumberstring手机号

5.2.2 通过状态获取管理员信息

请求URL: POST /HttpService/getAdminListByStatus
请求参数

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

返回响应:200 OK

响应参数

字段名称字段类型字段说明
requestIdstring请求ID
errorCodeinteger错误码;0-成功
errorMsgstring错误信息
resultobject响应结果
totalCountinteger总数量
userListarray用户信息数组
userIdstring用户ID
nicknamestring用户昵称
roleTypeinteger角色类型
phoneNumberstring手机号
userStatusinteger用户状态
userMemostring用户备注信息

5.2.3新增管理员

请求URL: POST /HttpService/newAdministrator
请求参数

字段名称字段类型字段说明
requestIdstring请求ID
sessionIdstring客户端会话ID
userInfoobject管理员信息
nicknamestring用户昵称
roleTypeinteger角色类型
userStatusinteger用户状态
userMemostring用户备注信息
phoneNumberstring手机号

返回响应:200 OK

响应参数

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

5.2.4编辑管理员信息

请求URL: POST /HttpService/setAdministrator
请求参数

字段名称字段类型字段说明
requestIdstring请求ID
sessionIdstring客户端会话ID
userInfoobject管理员信息
userIdstring用户ID
nicknamestring用户昵称
userStatusinteger用户状态
userMemostring用户备注信息

返回响应:200 OK

响应参数

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

5.2.5启用或禁用管理员

请求URL:POST /HttpService/setStatus
请求参数

字段名称字段类型字段说明
requestIdstring请求ID
sessionIdstring客户端会话ID
userIdstring用户ID
userStatusinteger用户状态
0-未知;1-启用;2-禁用

返回响应:200 OK

响应参数

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

5.2.6删除管理员

请求URL:POST /HttpService/delAdministrator
请求参数

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

返回响应:200 OK

响应参数

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

六.补充

客户端的主题逻辑到这里就结束了,待服务端编写完毕我们再进行联调与漏洞的补充,所以大家编写下来如果出现问题可以参考后面的联调部分,慢慢来不要着急。

Logo

火山引擎视频云技术社区,是面向 AI 音视频开发者的技术交流平台。这里汇聚源自抖音、豆包等亿级 DAU 产品的 RTC、直播、点播、AI 媒体处理、音视频互动技术,提供接入指南、最佳实践、性能调优、场景案例、Demo 代码、开源项目、白皮书和 API 文档。社区汇聚官方工程师与一线开发者,为 AI 视频通话、数字人、AI 视频处理等应用的开发与落地提供技术支持。

更多推荐