使用QNetworkAccessManager实现FTP下载器

photodownloader.h文件

#ifndef PHOTODOWNLOADER_H
#define PHOTODOWNLOADER_H
#include <QObject>
#include <QTimer>
#include <QThread>
#include <QList>
#include <QUrl>
#include <QDir>
#include <QMutex>
#include "communicate.h"
#include "mydatabase.h"
#include "ftpdownloader.h"
#include "withintablepushbutton.h"
violation表imgexist字段含义:0-未下载 1-已下载 2-下载失败 3-正在下载
class PhotoDownloader : public QObject
    Q_OBJECT
public:
    //单例模式
    static PhotoDownloader *Instance();
    //构造函数
    explicit PhotoDownloader(QObject *parent = Q_NULLPTR);
    void initTask();
private:
    //初始化imgexist字段(如果存在3则置为0)
    void initImgExistField();
    void prepareDownload(int downloaderId);
public:
    QMap<int,WithinTablePushButton *> viewImgBtnMap; //违章页面的查看按钮
private:
    static PhotoDownloader *self;
    bool isTaskActive;
    quint16 board_id;
    QList<FtpDownloader *>downloaderList;
    int maxDownloaderNum; //下载器最大数量
#endif // PHOTODOWNLOADER_H

photodownloader.cpp文件

#pragma execution_character_set("utf-8")
#include "photodownloader.h"
//单例模式
PhotoDownloader *PhotoDownloader::self = nullptr;
PhotoDownloader *PhotoDownloader::Instance()
    if (!self) {
        QMutex mutex;
        QMutexLocker locker(&mutex);
        if (!self) {
            self = new PhotoDownloader;
    return self;
//构造函数
PhotoDownloader::PhotoDownloader(QObject *parent):QObject(parent),isTaskActive(false)
  ,maxDownloaderNum(3)
    initImgExistField();
    //创建下载器
    for(int downloaderId=0;downloaderId<maxDownloaderNum;downloaderId++){
        downloaderList.append(new FtpDownloader(downloaderId));
        connect(downloaderList.at(downloaderId),&FtpDownloader::downloadFinish,this,&PhotoDownloader::prepareDownload);
        downloaderList[downloaderId]->setProperty("isBusy",false);
    //设备连接时自动调用initTask
    connect(Communicate::Instance(),&Communicate::hasConnected,[=]{
        this->board_id = Communicate::Instance()->getBoardId();
        initTask();
//初始化imgexist字段(如果存在3则置为0)
void PhotoDownloader::initImgExistField()
    MyDatabase::Instance()->restoreViolationImgexistField();
void PhotoDownloader::initTask()
    if(!isTaskActive){
        isTaskActive = true;
        //获取未下载的图片数
        int imgNum = MyDatabase::Instance()->getUndownloadImgNum(this->board_id);
        if(imgNum<=0){
            isTaskActive = false;
            return;
        //准备下载
        for(int downloaderId=0;downloaderId<qMin(imgNum,maxDownloaderNum);downloaderId++){
            prepareDownload(downloaderId);
            downloaderList[downloaderId]->setProperty("isBusy",true);
    else{
        //如果有未开启的下载器则开启
        for(int i=0;i<maxDownloaderNum;i++){
            if(!downloaderList[i]->property("isBusy").toBool()){
                prepareDownload(i);
                downloaderList[i]->setProperty("isBusy",true);
                qDebug()<<"新开启"<<i<<"号下载器";
                break;
void PhotoDownloader::prepareDownload(int downloaderId)
    int violationId;
    QString imgName;
    if( MyDatabase::Instance()->getEarliestImgName(this->board_id,violationId,imgName) ){
        QString url = "ftp://speedtest.tele2.net/" + imgName;
        QString dir = QDir::currentPath() + "/violationPhoto";
        MyDatabase::Instance()->updateImgexistById(violationId,3);//将imgexist改为3(正在下载)
        downloaderList[downloaderId]->startDownload(violationId,url,dir);
    else{
        qDebug()<<QString("下载器%1 发现 无未下载图片").arg(downloaderId);
        downloaderList[downloaderId]->setProperty("isBusy",false);
        for(int i=0;i<maxDownloaderNum;i++){
            if(downloaderList[i]->property("isBusy").toBool()) return;
        qDebug()<<"所有下载器均空闲";
        isTaskActive = false;

ftpdownloader.h文件

#ifndef FTPDOWNLOADER_H
#define FTPDOWNLOADER_H
#include <QObject>
#include <QDir>
#include <QUrl>
#include "mydatabase.h"
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QTimer>
#include <QDebug>
#include <QMetaEnum>
#include <QThread>
class FtpDownloader : public QObject
    Q_OBJECT
public:
    explicit FtpDownloader(int downloaderId,QObject *parent = Q_NULLPTR);
    void startDownload(int violationId, QString url, QString dir);
private:
    //检查Url正确性
    bool checkUrl();
    //检查保存目录正确性
    bool checkSaveDir();
    //创建下载文件
    bool createDownloadFile();
signals:
    void downloadFinish(int downloaderId);//下载结束信号
private slots:
    void timeOut();
    void slotReadyRead();
    void readReplyError(QNetworkReply::NetworkError error);
    void downloadFinishReply(QNetworkReply* reply);
    void downloadProgress(qint64 bytesReceived, qint64 bytesTotal);
private:
    int downloaderId;//当前下载器的编号
    int violationId;
    QUrl url;
    QDir dir;
    QFile *file;
    QTimer *timer;
    qint64 fileDownloadSize;
    qint64 lastDownloadSize;
    QNetworkReply *downloadReply;
    QNetworkAccessManager *downloadManager;
#endif // FTPDOWNLOADER_H

ftpdownloader.cpp文件

#pragma execution_character_set("utf-8")
#include "ftpdownloader.h"
#include "photodownloader.h"
FtpDownloader::FtpDownloader(int downloaderId,QObject *parent):QObject(parent)
    this->downloaderId = downloaderId;
    downloadManager = new QNetworkAccessManager(this);
    connect(downloadManager,&QNetworkAccessManager::finished,this,&FtpDownloader::downloadFinishReply);
    timer = new QTimer(this);
    connect(timer,&QTimer::timeout,this,&FtpDownloader::timeOut);
//检查Url正确性
bool FtpDownloader::checkUrl()
    if(!url.isValid()) return false;
    if(url.scheme() != "ftp") return false;
    if(url.path().isEmpty() || url.path()=="/") return false;
    return true;
//检查保存目录正确性
bool FtpDownloader::checkSaveDir()
    //如果目录不存在就创建目录
    if(!dir.exists())
        if(!dir.mkpath(dir.absolutePath())) return false;
    return true;
//创建下载文件
bool FtpDownloader::createDownloadFile()
    //从url中截取出文件名
    QString localFileName = QFileInfo(url.path()).fileName();
    localFileName = QString("%1-下载器%2-%3").arg(this->violationId).arg(this->downloaderId).arg(localFileName);//(临时这样命名文件)
    file = new QFile();
    file->setFileName(dir.absoluteFilePath(localFileName));
    if(!file->open(QIODevice::WriteOnly)) return false;
    return true;
void FtpDownloader::startDownload(int violationId, QString url, QString dir)
    this->violationId = violationId;
    this->url = QUrl(url);
    this->dir = QDir(dir);
    if( !checkUrl() || !checkSaveDir() || !createDownloadFile() ){
        MyDatabase::Instance()->updateImgexistById(violationId,2);//将imgexist改为2(下载失败)
        if( PhotoDownloader::Instance()->viewImgBtnMap.contains(violationId) ){   //将按钮文字改为“不存在”
            PhotoDownloader::Instance()->viewImgBtnMap.value(violationId)->setText("不存在");
        emit downloadFinish(this->downloaderId);
        return;
    fileDownloadSize = 0;
    lastDownloadSize = 0;
    downloadReply = downloadManager->get(QNetworkRequest(url));
    connect(downloadReply,&QNetworkReply::readyRead,this,&FtpDownloader::slotReadyRead);
    connect(downloadReply,&QNetworkReply::downloadProgress,this,&FtpDownloader::downloadProgress);
    connect(downloadReply,static_cast<void(QNetworkReply::*)(QNetworkReply::NetworkError)>(&QNetworkReply::error),this,&FtpDownloader::readReplyError);
    if(!timer->isActive()) timer->start(30 * 1000);//启动超时检查定时器,每30秒查询下载情况
void FtpDownloader::timeOut()
    if(lastDownloadSize != fileDownloadSize){
        lastDownloadSize = fileDownloadSize;//更新lastDownloadSize
    else{
        //如果30秒之前下载了的文件大小等于下载下载了的文件大小,就主动发出超时error信号
        emit downloadReply->error(QNetworkReply::TimeoutError);
void FtpDownloader::slotReadyRead()
    file->write(downloadReply->readAll());
    fileDownloadSize = file->size();//更新下载字节数
void FtpDownloader::readReplyError(QNetworkReply::NetworkError error)
    //打印错误信息
    QMetaEnum metaEnum = QMetaEnum::fromType<QNetworkReply::NetworkError>();
    //PS:字符串转换为枚举值
    //Qt::Alignment alignment = (Qt::Alignment)metaEnum.keyToValue("Qt::AlignLeft");
    //alignment = (Qt::Alignment)metaEnum.keysToValue("Qt::AlignLeft | Qt::AlignVCenter");
    //枚举值转换为字符串
    const char *errStr = metaEnum.valueToKey(error);
    qDebug()<<"文件下载error: " + QString(errStr);
    file->close();
    file->deleteLater();
    file = Q_NULLPTR;
    downloadReply->deleteLater();
    downloadReply = Q_NULLPTR;
    startDownload(violationId,url.toString(),dir.absolutePath()); //重新尝试下载文件
void FtpDownloader::downloadFinishReply(QNetworkReply *reply)
    if(timer->isActive()) timer->stop(); //停止超时计时器
    file->waitForBytesWritten(5 * 1000); //等待文件写入结束(5秒钟)
    if(file->size()==0){
//        MyDatabase::Instance()->updateImgexistById(violationId,2);
    else{
        MyDatabase::Instance()->updateImgexistById(violationId,1);
    file->close();
    file->deleteLater();
    file = Q_NULLPTR;
    reply->deleteLater();
    reply = Q_NULLPTR;
    emit downloadFinish(this->downloaderId);
void FtpDownloader::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
    if(bytesTotal!=0)
        qDebug()<<"下载器:"<<this->downloaderId<<" 总大小:"<<bytesTotal/1024<<"kb 已下载:"<<bytesReceived/1024<<"kb";
        if( PhotoDownloader::Instance()->viewImgBtnMap.contains(violationId) ){