项目(10)(上传功能和秒传功能、上传功能改进——生产者消费者、通过代码去设置ui界面布局的原理)

上传功能和秒传功能

上传协议

文件上传

客户端

------WebKitFormBoundary88asdgewtgewx\r\n

Content-Disposition: form-data; user="mike"; filename="xxx.jpg"; md5="xxxx"; size=10240\r\n

Content-Type: application/octet-stream\r\n

\r\n

真正的文件内容\r\n

------WebKitFormBoundary88asdgewtgewx

 

服务器端回传数据

 

成功

{"code":"008"}

失败

{"code":"009"}

 

 

MD5 秒传

客户端

{

   user:xxxx,

   token:xxxx,

   md5:xxx,

   fileName: xxx

 }

 

服务器端

文件已存在:

{"code":"005"}

秒传成功: 

{"code":"006"}

秒传失败: 

{"code":"007"}

 

 

流程图

  1. 上传的方式有两个——上传按钮和右键菜单
  2. UploadTask将要上传的信息读取出来,添加到任务队列中
  3. 使用定时器实时监测任务队列中的任务,每个500毫秒检测一次,检测上传任务,每一次只能上传一个文件
  4. 上传时先尝试秒传
  5. 秒传时,服务器先验证客户端发来的http请求里面的token是否正确,如果正确,才能进行后续操作

  

            

整体

类的声明

namespace Ui {
class MyFileWg;
}

class MyFileWg : public QWidget
{
    Q_OBJECT

public:
    explicit MyFileWg(QWidget *parent = 0);
    ~MyFileWg();

    // 初始化listWidget文件列表
    void initListWidget();
    // 添加右键菜单
    void addActionMenu();

    //==========>上传文件处理<==============
    // 添加需要上传的文件到上传任务列表
    void addUploadFiles();
    // 设置md5信息的json包
    QByteArray setMd5Json(QString user, QString token, QString md5, QString fileName);
    // 上传文件处理,取出上传任务列表的队首任务,上传完后,再取下一个任务
    void uploadFilesAction();
    // 上传真正的文件内容,不能秒传的前提下
    void uploadFile(UploadFileInfo *info);

    //==========>文件item展示<==============
    // 清空文件列表
    void clearFileList();
    // 清空所有item项目
    void clearItems();
    // 添加上传文件项目item
    void addUploadItem(QString iconPath=":/images/upload.png", QString name="上传文件");
    // 文件item展示
    void refreshFileItems();

    //==========>显示用户的文件列表<==============
    // desc是descend 降序意思
    // asc 是ascend 升序意思
    // Normal:普通用户列表,PvAsc:按下载量升序, PvDesc:按下载量降序
    enum Display{Normal, PvAsc, PvDesc};
    // 得到服务器json文件
    QStringList getCountStatus(QByteArray json);
    // 显示用户的文件列表
    void refreshFiles(Display cmd=Normal);
    // 设置json包
    QByteArray setGetCountJson(QString user, QString token);
    // 设置json包
    QByteArray setFilesListJson(QString user, QString token, int start, int count);
    // 获取用户文件列表
    void getUserFilesList(Display cmd=Normal);
    // 解析文件列表json信息,存放在文件列表中
    void getFileJsonInfo(QByteArray data);

    //==========>分享、删除文件<==============
    // 处理选中的文件
    void dealSelectdFile(QString cmd="分享");
    QByteArray setDealFileJson(QString user, QString token, QString md5, QString filename);//设置json包

    //==========>分享文件<==============
    void shareFile(FileInfo *info); //分享某个文件

    //==========>删除文件<==============
    void delFile(FileInfo *info); //删除某个文件

    //==========>获取文件属性<==============
    void getFileProperty(FileInfo *info); //获取属性信息

    //==========>下载文件处理<==============
    // 添加需要下载的文件到下载任务列表
    void addDownloadFiles();
    //下载文件处理,取出下载任务列表的队首任务,下载完后,再取下一个任务
    void downloadFilesAction();

    //==========>下载文件标志处理<==============
    void dealFilePv(QString md5, QString filename); //下载文件pv字段处理

    //清除上传下载任务
    void clearAllTask();
    // 定时检查处理任务队列中的任务
    void checkTaskList();


signals:
    void loginAgainSignal();
    void gotoTransfer(TransferStatus status);

private:
    // 右键菜单信号的槽函数
    void rightMenu(const QPoint &pos);


private:
    Ui::MyFileWg *ui;

    Common m_cm;
    QNetworkAccessManager* m_manager;
    MyMenu   *m_menu;           // 菜单1
    QAction *m_downloadAction; // 下载
    QAction *m_shareAction;    // 分享
    QAction *m_delAction;      // 删除
    QAction *m_propertyAction; // 属性

    MyMenu   *m_menuEmpty;          // 菜单2
    QAction *m_pvAscendingAction;  // 按下载量升序
    QAction *m_pvDescendingAction; // 按下载量降序
    QAction *m_refreshAction;      // 刷新
    QAction *m_uploadAction;       // 上传


    long m_userFilesCount;        //用户文件数目
    int m_start;                  //文件位置起点
    int m_count;                  //每次请求文件个数

    //定时器
    QTimer m_uploadFileTimer;       //定时检查上传队列是否有任务需要上传
    QTimer m_downloadTimer;         //定时检查下载队列是否有任务需要下载


    QList<FileInfo *> m_fileList;    //文件列表


};

第一部分——上传按钮

点击上传按钮——>初始化文件列表——>检测触发事件是否为上传事件,如果是,则调用addUploadFiles()函数将需要上传的文件添加到上传任务列表。

// 初始化listWidget文件列表
void MyFileWg::initListWidget()
{
    ui->listWidget->setViewMode(QListView::IconMode);   //设置显示图标模式
    ui->listWidget->setIconSize(QSize(80, 80));         //设置图标大小
    ui->listWidget->setGridSize(QSize(100, 120));       //设置item大小

    // 设置QLisView大小改变时,图标的调整模式,默认是固定的,可以改成自动调整
    ui->listWidget->setResizeMode(QListView::Adjust);   //自动适应布局

    // 设置列表可以拖动,如果想固定不能拖动,使用QListView::Static
    ui->listWidget->setMovement(QListView::Static);
    // 设置图标之间的间距, 当setGridSize()时,此选项无效
    ui->listWidget->setSpacing(30);

    // listWidget 右键菜单
    // 发出 customContextMenuRequested 信号
    ui->listWidget->setContextMenuPolicy(Qt::CustomContextMenu);
    connect(ui->listWidget, &QListWidget::customContextMenuRequested, this, &MyFileWg::rightMenu);
    
    // 点中列表中的上传文件图标。点击事件被触发,会发送QListWidget::itemPressed信号
    connect(ui->listWidget, &QListWidget::itemPressed, this, [=](QListWidgetItem* item)
    {
        QString str = item->text();
        //如果事件是上传文件
        if(str == "上传文件")
        {
            //添加需要上传的文件到上传任务列表
            addUploadFiles();
        }
    });
}

第二部分——右键菜单

在文件处右键菜单,选择上传,即可调用addUploadFiles()函数则调用addUploadFiles()函数将需要上传的文件添加到上传任务列表。

注:右键菜单(在按钮处右键和在空白处右键的效果不同,因此是两个右键菜单)

void MyFileWg::addActionMenu()
{
    //===================菜单1==================
    m_menu = new MyMenu( this );

    // 初始化菜单项
    m_downloadAction = new QAction("下载", this);
    m_shareAction = new QAction("分享", this);
    m_delAction = new QAction("删除", this);
    m_propertyAction = new QAction(QString::fromLocal8Bit("属性"), this);

    // 动作1添加到菜单1
    m_menu->addAction(m_downloadAction);
    m_menu->addAction(m_shareAction);
    m_menu->addAction(m_delAction);
    m_menu->addAction(m_propertyAction);

    //===================菜单2===================
    m_menuEmpty = new MyMenu( this );
    // 动作2
    m_pvAscendingAction = new QAction("按下载量升序", this);
    m_pvDescendingAction = new QAction("按下载量降序", this);
    m_refreshAction = new QAction("刷新", this);
    m_uploadAction = new QAction("上传", this);

    // 动作2添加到菜单2
    m_menuEmpty->addAction(m_pvAscendingAction);
    m_menuEmpty->addAction(m_pvDescendingAction);
    m_menuEmpty->addAction(m_refreshAction);
    m_menuEmpty->addAction(m_uploadAction);

    //=====================信号与槽===================
    // 下载
    connect(m_downloadAction, &QAction::triggered, [=]()
    {
        cout << "下载动作";
        //添加需要下载的文件到下载任务列表
        addDownloadFiles();
    });

    // 分享
    connect(m_shareAction, &QAction::triggered, [=]()
    {
        cout << "分享动作";
        dealSelectdFile("分享"); //处理选中的文件
    });

    // 删除
    connect(m_delAction, &QAction::triggered, [=]()
    {
        cout << "删除动作";
        dealSelectdFile("删除"); //处理选中的文件
    });

    // 属性
    connect(m_propertyAction, &QAction::triggered, [=]()
    {
        cout << QString::fromLocal8Bit("属性动作");
        dealSelectdFile(QString(QString::fromLocal8Bit("属性"))); //处理选中的文件
    });

    // 按下载量升序
    connect(m_pvAscendingAction, &QAction::triggered, [=]()
    {
        cout << QString::fromLocal8Bit("按下载量升序");
        refreshFiles(PvAsc);
    });

    // 按下载量降序
    connect(m_pvDescendingAction, &QAction::triggered, [=]()
    {
        cout << QString::fromLocal8Bit("按下载量降序");
        refreshFiles(PvDesc);
    });

    //刷新
    connect(m_refreshAction, &QAction::triggered, [=]()
    {
        cout << "refresh";
        //显示用户的文件列表
        refreshFiles();
    });

    //上传
    connect(m_uploadAction, &QAction::triggered, [=]()
    {
        cout << "upload";
        //添加需要上传的文件到上传任务列表
        addUploadFiles();
    });
}

第三部分——向任务队列中添加任务

addUploadFiles()函数调用UploadTask类的appendUploadList函数,将需要上传的文件添加到上传任务列表。

// 添加需要上传的文件到上传任务列表
void MyFileWg::addUploadFiles()
{
    //用于切换界面的信号。点击上传之后进行界面切换,这是后续的用户友好优化。
    //切换到让用户看到上传界面的进度条
    emit gotoTransfer(TransferStatus::Uplaod);
    
    //获取上传列表实例,getInstance——单例模式
    UploadTask *uploadList = UploadTask::getInstance();
    if(uploadList == NULL)
    {
        cout << "UploadTask::getInstance() == NULL";
        return;
    }

    QStringList list = QFileDialog::getOpenFileNames();
    for(int i = 0; i < list.size(); ++i)
    {
        //cout << "所选文件为:"<<list.at(i);
        //  -1: 文件大于30m
        //  -2:上传的文件是否已经在上传队列中
        //  -3: 打开文件失败
        //  -4: 获取布局失败
        int res = uploadList->appendUploadList(list.at(i));
        if(res == -1)
        {
            QMessageBox::warning(this, QString::fromLocal8Bit("文件太大"), 
                        QString::fromLocal8Bit("文件大小不能超过30m"));
        }
        else if(res == -2)
        {
            QMessageBox::warning(this, QString::fromLocal8Bit("添加失败"), 
                        QString::fromLocal8Bit("上传文件是否已在上传队列中"));
        }
        else if(res == -3)
        {
            cout << "打开文件失败";
        }
        else if(res == -4)
        {
            cout << "获取布局失败";
        }
    }
}

UploadTask类的声明

//上传文件信息
struct UploadFileInfo
{
    QString md5;        //文件md5码
    QFile *file;        //文件指针
    QString fileName;   //文件名字
    qint64 size;        //文件大小
    QString path;       //文件路径
    bool isUpload;      //是否已经在上传
    DataProgress *dp;   //上传进度控件
};

//上传任务列表类,单例模式,一个程序只能有一个上传任务列表
//UploadTask将要上传的信息读取出来,添加到任务队列中
class UploadTask
{
public:
    static UploadTask *getInstance(); //保证唯一一个实例

    //追加上传文件到上传列表中
    //参数:path 上传文件路径
    //返回值:成功为0
    //失败:
    //  -1: 文件大于30m
    //  -2:上传的文件是否已经在上传队列中
    //  -3: 打开文件失败
    //  -4: 获取布局失败
    int appendUploadList(QString path);

    bool isEmpty(); //判断上传队列释放为空
    bool isUpload(); //是否有文件正在上传

    //取出第0个上传任务,如果任务队列没有任务在上传,设置第0个任务上传
    UploadFileInfo * takeTask();
    //删除上传完成的任务
    void dealUploadTask();

    void clearList(); //清空上传列表

private:
    UploadTask()    //构造函数为私有
    {}

    ~UploadTask()    //析构函数为私有
    { }

    //静态数据成员,类中声明,类外必须定义
    static UploadTask *instance;

    //它的唯一工作就是在析构函数中删除Singleton的实例
    class Garbo
    {
    public:
        ~Garbo()
        {
          if(NULL != UploadTask::instance)
          {
            UploadTask::instance->clearList();

            delete UploadTask::instance;
            UploadTask::instance = NULL;
            cout << "instance is detele";
          }
        }
    };

    //定义一个静态成员变量,程序结束时,系统会自动调用它的析构函数
    //static类的析构函数在main()退出后调用
    static Garbo temp; //静态数据成员,类中声明,类外定义

    QList <UploadFileInfo *> list; //上传任务列表
};

appendUploadList函数的实现

先检测文件大小,不能超过30M。之后查看该文件是否已在上传队列中,如果不在上传队列中,则将文件信息添加到上传队列(此时没有进行上传操作)

//追加上传文件到上传列表中,此时还没开始上传
//参数:path 上传文件路径
//失败:
//  -1: 文件大于30m
//  -2:上传的文件是否已经在上传队列中
//  -3: 打开文件失败
//  -4: 获取布局失败
int UploadTask::appendUploadList(QString path)
{
    qint64 size = QFileInfo( path ).size();
    if(size > 30*1024*1024) //最大文件只能是30M
    {
        cout << "file is to big\n";
        return -1;
    }


    //遍历查看一下,下载的文件是否已经在上传队列中
    for(int i = 0; i != list.size(); ++i)
    {
        if( list.at(i)->path == path) //list[i]->path
        {
            cout << QFileInfo( path ).fileName() << " 已经在上传队列中 ";
            return -2;
        }
    }

    QFile *file = new QFile(path); //文件指针分配空间

    if(!file->open(QIODevice::ReadOnly))
    {
        //如果打开文件失败,则删除 file,并使 file 指针为 0,然后返回
        cout << "file open error";

        delete file;
        file = NULL;
        return -3;
    }

    //获取文件属性信息
    QFileInfo info(path);

     //动态创建节点
    Common mc;
    UploadFileInfo *tmp = new UploadFileInfo;
    tmp->md5 = mc.getFileMd5(path); //获取文件的md5码, common.h
    tmp->file = file; //文件指针
    tmp->fileName = info.fileName();//文件名字
    tmp->size = size; //文件大小
    tmp->path = path; //文件路径
    tmp->isUpload = false;//当前文件没有被上传

    //进度条是为了上传列表准备的。进度条是被动态的插入到上传列表中的
    //有多少个带上传文件,就要做几个进度条
    DataProgress *p = new DataProgress; //创建进度条
    p->setFileName(tmp->fileName); //设置文件名字
    tmp->dp = p;

    //获取布局
    UploadLayout *pUpload = UploadLayout::getInstance();
    if(pUpload == NULL)
    {
        cout << "UploadTask::getInstance() == NULL";
        return -4;
    }
    QVBoxLayout *layout = (QVBoxLayout*)pUpload->getUploadLayout();
    // 添加到布局, 最后一个是弹簧, 插入到弹簧上边
    //一个布局里面可以插入窗口(进度条),窗口里面可以设置布局,布局里面可以再插入窗口
    //类似于做界面,先做一个窗口,然后设置他为水平布局或者垂直布局,然后再在布局中添加窗口
    //这里就是将进度条插入
    layout->insertWidget(layout->count()-1, p);

    cout << tmp->fileName.toUtf8().data() << "已经放在上传列表";

    //插入到任务队列,每个temp都是一个UploadFileInfo(一个结构体),即当前要上传的文件信息
    list.append(tmp);

    return 0;
}

第四部分——定时器检测任务队列中是否有任务需要上传

上传和下载都有相应的队列,因此有两个定时器分别检测这两个队列。

每500毫秒检测一次任务队列,当有任务需要上传,则调用uploadFilesAction();函数进行文件上传

// 定时检查处理任务队列中的任务
void MyFileWg::checkTaskList()
{
    //定时检查上传队列是否有任务需要上传
    connect(&m_uploadFileTimer, &QTimer::timeout, [=]()
    {
        //上传文件处理,取出上传任务列表的队首任务,上传完后,再取下一个任务
        uploadFilesAction();
    });

    // 启动定时器,500毫秒间隔
    // 每个500毫秒,检测上传任务,每一次只能上传一个文件
    m_uploadFileTimer.start(500);

    // 定时检查下载队列是否有任务需要下载
    connect(&m_downloadTimer, &QTimer::timeout, [=]()
    {
        // 下载文件处理,取出下载任务列表的队首任务,下载完后,再取下一个任务
        downloadFilesAction();
    });

    // 启动定时器,500毫秒间隔
    // 每个500毫秒,检测下载任务,每一次只能下载一个文件
    m_downloadTimer.start(500);
}

第五部分——md5尝试秒传

  1. 首先获取上传队列,如果获取成功,则检查队列是否为空。
  2. 如果队列非空且此时没有上传任务在进行上传操作,则首先尝试秒传。
  3. 如果服务器中存在该文件,则进行秒传,否则进行文件上传操作。
// 上传文件处理,取出上传任务列表的队首任务,上传完后,再取下一个任务
void MyFileWg::uploadFilesAction()
{
    // 获取上传列表实例
    UploadTask *uploadList = UploadTask::getInstance();
    //判断单例对象是否获取失败
    if(uploadList == NULL)
    {
        cout << "UploadTask::getInstance() == NULL";
        return;
    }

    // 如果队列为空,无上传任务,终止程序
    if( uploadList->isEmpty() )
    {
        return;
    }

    // 查看是否有上传任务,若此时有文件正在上传,则不能上传
    if( uploadList->isUpload() )
    {
        return;
    }

    //先尝试秒传
    // 获取登陆信息实例
    LoginInfoInstance *login = LoginInfoInstance::getInstance(); //获取单例

    // url
    QNetworkRequest request;
    QString url = QString("http://%1:%2/md5").arg( login->getIp() ).arg( login->getPort() );
    request.setUrl( QUrl( url )); //设置url
    request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");

    // 取出第0个上传任务,如果任务队列没有任务在上传,设置第0个任务上传
    UploadFileInfo *info = uploadList->takeTask();

    // post数据包
    QByteArray array = setMd5Json(login->getUser(), login->getToken(), info->md5, info->fileName);

    // 发送post请求
    QNetworkReply *reply = m_manager->post(request, array);
    if(reply == NULL)
    {
        cout << "reply is NULL";
        return;
    }

    // 信号和槽连接
    //捕捉QNetworkReply::finished信号。当服务器进行用户信息检测之后,会发送信号给客户端
    connect(reply, &QNetworkReply::finished, [=]()
    {
        //先判断是否出错
        if (reply->error() != QNetworkReply::NoError) //有错误
        {
            cout << reply->errorString();
            reply->deleteLater(); //释放资源
            return;
        }

        //读取服务器返回的数据
        QByteArray array = reply->readAll();
        //cout << array.data();
        reply->deleteLater(); //释放资源

        /*
        秒传文件:
            文件已存在:{"code":"005"}
            秒传成功:  {"code":"006"}
            秒传失败:  {"code":"007"}
        token验证失败:{"code":"111"}
        */
        if("006" == m_cm.getCode(array) ) //common.h
        {
            //秒传文件成功
            //cout << info->fileName.toUtf8().data() << "-->秒传成功";
            m_cm.writeRecord(login->getUser(), info->fileName, "006");

            //删除已经完成的上传任务
            uploadList->dealUploadTask();

        }
        else if("007" == m_cm.getCode(array) )
        {
            // 说明后台服务器没有此文件,因此秒传失败,需要真正的文件上传
            uploadFile(info);
        }
        else if("005" == m_cm.getCode(array) )// "005", 上传的文件已存在
        {
            //QMessageBox::information(this, "文件已存在", QString("%1 已存在").arg(info->fileName))
            //cout << QString("%1 已存在").arg(info->fileName).toUtf8().data();
            m_cm.writeRecord(login->getUser(), info->fileName, "005");

            //删除已经完成的上传任务
            uploadList->dealUploadTask();
        }
        else if("111" == m_cm.getCode(array)) //token验证失败
        {
            QMessageBox::warning(this, "账户异常", "请重新登陆!!!");
            emit loginAgainSignal(); //发送重新登陆信号
            return;
        }
    });
}

第六部分——对于无法秒传的文件进行文件上传操作

  1. 取出文件信息
  2. 获取用户信息实例
  3. 制作http请求,并将请求发送给服务器
  4. 进行文件上传,并通过服务器返回的信号实时更新上传进度条
  5. 文件上传完毕,服务器发送信号
  6. 分析服务器返回的信号,确定文件是否上传成功
  7. 删除上传队列中的任务
// 上传真正的文件内容,不能秒传的前提下
void MyFileWg::uploadFile(UploadFileInfo *info)
{
    //取出上传任务
    QFile *file = info->file;           //文件指针
    QString fileName = info->fileName;  //文件名字
    QString md5 = info->md5;            //文件md5码
    qint64 size = info->size;           //文件大小
    DataProgress *dp = info->dp;        //进度条控件
    QString boundary = m_cm.getBoundary();   //产生分隔线

    //获取登陆信息实例
    LoginInfoInstance *login = LoginInfoInstance::getInstance(); //获取单例

    QByteArray data;

    /*
    ------WebKitFormBoundary88asdgewtgewx\r\n
    Content-Disposition: form-data; user="mike"; filename="xxx.jpg"; md5="xxxx"; size=10240\r\n
    Content-Type: application/octet-stream\r\n
    \r\n
    真正的文件内容\r\n
    ------WebKitFormBoundary88asdgewtgewx
    */

    //第1行,分隔线
    data.append(boundary);
    data.append("\r\n");

    //上传文件信息
    data.append("Content-Disposition: form-data; ");
    data.append( QString("user=\"%1\" ").arg( login->getUser() ) ); //上传用户
    data.append( QString("filename=\"%1\" ").arg(fileName) ); //文件名字
    data.append( QString("md5=\"%1\" ").arg(md5) ); //文件md5码
    data.append( QString("size=%1").arg(size)  );   //文件大小
    data.append("\r\n");

    data.append("Content-Type: application/octet-stream");
    data.append("\r\n");
    data.append("\r\n");

    // 上传文件内容
    data.append( file->readAll() ); //文件内容
    data.append("\r\n");

    // 结束分隔线
    data.append(boundary);

    QNetworkRequest request; //请求对象
    //http://127.0.0.1:80/upload
    QString url = QString("http://%1:%2/upload").arg(login->getIp()).arg(login->getPort());
    request.setUrl(QUrl( url )); //设置url

    // qt默认的请求头
    //request.setRawHeader("Content-Type","text/html");
    request.setHeader(QNetworkRequest::ContentTypeHeader,"application/json");

    // 发送post请求
    QNetworkReply * reply = m_manager->post( request, data );
    if(reply == NULL)
    {
        cout << "reply == NULL";
        return;
    }

    // 有可用数据更新时。QNetworkReply::uploadProgress携带了已经上传了多少文件的信号
    // 进度条的增长就靠这个函数了。bytesRead已经上传的数据量,totalBytes文件总量
    connect(reply, &QNetworkReply::uploadProgress, [=](qint64 bytesRead, qint64 totalBytes)
    {
        if(totalBytes != 0) //这个条件很重要
        {
            //cout << bytesRead/1024 << ", " << totalBytes/1024;
            //因为值比较大,所以除以1024,这样进度条分的份数可以少一点。不除1024也可以
            dp->setProgress(bytesRead/1024, totalBytes/1024); //设置进度条长度
        }
    });

    // 获取请求的数据完成时,就会发送信号SIGNAL(finished())
    connect(reply, &QNetworkReply::finished, [=]()
    {
        if (reply->error() != QNetworkReply::NoError) //有错误
        {
            cout << reply->errorString();
            reply->deleteLater(); //释放资源
            return;
        }

        QByteArray array = reply->readAll();

        reply->deleteLater();

        /*
            上传文件:
                成功:{"code":"008"}
                失败:{"code":"009"}
            */
        if("008" == m_cm.getCode(array) ) //common.h
        {
            //cout << fileName.toUtf8().data() <<" ---> 上传完成";
            m_cm.writeRecord(login->getUser(), info->fileName, "008");
        }
        else if("009" == m_cm.getCode(array) )
        {
            //cout << fileName.toUtf8().data() << " ---> 上传失败";
            m_cm.writeRecord(login->getUser(), info->fileName, "009");
        }


        //获取上传列表实例。因为任务在单例里面,所以要调用单例去删除里面的任务
        UploadTask *uploadList = UploadTask::getInstance();
        if(uploadList == NULL)
        {
            cout << "UploadTask::getInstance() == NULL";
            return;
        }

        uploadList->dealUploadTask(); //删除已经完成的上传任务
    }
    );
}

 

上传功能改进——生产者消费者

  • 不使用定时器去检测任务,而是使用条件变量。
  • 往任务队列中放任务的就是生产者。
  • 如果任务队列为空,则消费者线程等待(wait)
  • 如果队列不为空,则使用条件变量唤醒消费者(signal——至少唤醒一个消费者、broadcast唤醒所有消费者
  • 线程池中任务管理使用的是条件变量。

QT中的条件变量——QWaitCondition Class

QT中的读写锁——QReadWriteLock Class

通过代码去设置ui界面布局的原理

  • 窗口中设置布局
  • 布局中可以添加窗口
  • 窗口 -> 从QWidget派生的类
  • 布局-> QLayout派生的类

上传布局分析

UploadLayout.h

#ifndef UPLOADLAYOUT_H
#define UPLOADLAYOUT_H
#include "common.h"
#include <QVBoxLayout>

//上传进度布局类,单例模式
class UploadLayout
{
public:
    static UploadLayout *getInstance(); //保证唯一一个实例
    void setUploadLayout(QWidget *p); //设置布局
    QLayout *getUploadLayout(); //获取布局

private:
    UploadLayout()
    { }

    ~UploadLayout()    //析构函数为私有
    { }

    //静态数据成员,类中声明,类外必须定义
    static UploadLayout *instance;
    QLayout *m_layout;
    QWidget *m_wg;

    //它的唯一工作就是在析构函数中删除Singleton的实例
    class Garbo
    {
    public:
        ~Garbo()
        {
          if(NULL != UploadLayout::instance)
          {
            delete UploadLayout::instance;
            UploadLayout::instance = NULL;
            cout << "instance is detele";
          }
        }
    };

    //定义一个静态成员变量,程序结束时,系统会自动调用它的析构函数
    //static类的析构函数在main()退出后调用
    static Garbo temp; //静态数据成员,类中声明,类外定义
};

#endif // UPLOADLAYOUT_H

UploadLayout.cpp

#include "uploadlayout.h"

//静态数据成员,类中声明,类外必须定义
UploadLayout * UploadLayout::instance = new UploadLayout;

//static类的析构函数在main()退出后调用
UploadLayout::Garbo UploadLayout::temp; //静态数据成员,类中声明,类外定义

UploadLayout * UploadLayout::getInstance()
{
    return instance;
}

//设置布局
//给当前布局添加窗口,因为布局要被设置到上传窗口中,因此p是指向上传列表窗口的指针
void UploadLayout::setUploadLayout(QWidget *p)
{
    m_wg = new QWidget(p);
    //获取当前窗口的布局
    QLayout* layout = p->layout();
    //向布局中添加窗口(就是进度条窗口)
    layout->addWidget(m_wg);
    //设置窗口的边界
    layout->setContentsMargins(0, 0, 0, 0);
    //创建了一个垂直布局
    QVBoxLayout* vlayout = new QVBoxLayout;
    // 将垂直布局设置给窗口
    m_wg->setLayout(vlayout);
    // 边界间隔,设置该垂直布局中的窗口没有缝隙
    vlayout->setContentsMargins(0, 0, 0, 0);
    m_layout = vlayout;

    // 添加弹簧
    vlayout->addStretch();
}

//获取布局
QLayout *UploadLayout::getUploadLayout()
{
    return m_layout;
}

猜你喜欢

转载自blog.csdn.net/qq_29996285/article/details/87902789