Qt 实现软件自动更新

一、流程

详细流程可参考本博客:https://blog.csdn.net/weixin_38739598/article/details/106571074

二、具体实现

CHttpDownLoadFile头文件

#pragma once

#include <QWidget>

//网络相关头文件
#include <QtNetwork/QNetworkAccessManager>
#include <QtNetwork/QNetworkRequest>
#include <QtNetwork/QNetworkReply>
//文件相关头文件
#include <QFile>
#include <QFileInfo>
#include <QDir>
#include <QDesktopServices>

class CHttpDownLoadFile : public QWidget
{
	Q_OBJECT

public:
	CHttpDownLoadFile(const QString &url, const QString &fileName, const QString &dir,QWidget *parent);
	~CHttpDownLoadFile();

	void DownLoadFile();
	void DestroyData();

signals:
	//文件下载结束
	void DownloadFinishedSignal();	
	//文件下载进度
	void DownloadProcess(QString, qint64, qint64);

public slots:
	void ReplyNewDataArrived();//响应m_netReply有新的数据到达
	void ReplyFinished();//响应数据接收完成
	void ReplyDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);	//响应文件下载进度

public:
	QNetworkAccessManager *m_netAccessManager;//网络参数

	QNetworkReply *m_netReply;
	QUrl m_urlAdress;//网络地址
	QString m_strFileName;//需要下载的文件名
	QString m_strDir;//文件的存储位置

	QFile *m_file;//下载的文件
	qint64 m_nReceived;//下载文件时,已经接收的文件大小和总共大小
	qint64 m_nTotal;
	bool m_bIsFinished;
};

CHttpDownLoadFile源文件

#include "CHttpDownLoadFile.h"

CHttpDownLoadFile::CHttpDownLoadFile(const QString &url, const QString &fileName, const QString &dir, QWidget *parent)
	: QWidget(parent),
	m_urlAdress(url),
	m_strFileName(fileName),
	m_strDir(dir),
	m_bIsFinished(false)
{
	m_netAccessManager = new QNetworkAccessManager(this);
}

CHttpDownLoadFile::~CHttpDownLoadFile()
{
}

void CHttpDownLoadFile::DownLoadFile()
{
	m_bIsFinished = false;
	QNetworkRequest request(m_urlAdress);
	request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
	m_netReply = m_netAccessManager->get(request);

	connect(m_netReply, SIGNAL(readyRead()), this, SLOT(ReplyNewDataArrived()));//当有新数据到达时就会触发此信号
	connect(m_netReply, SIGNAL(finished()), this, SLOT(ReplyFinished()));//完成数据接收后发送此信号
	connect(m_netReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(ReplyError(QNetworkReply::NetworkError)));//出现错误时发送此信号;
	connect(m_netReply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(ReplyDownloadProgress(qint64, qint64)));//用来提示文件下载进度

	if (m_strFileName.isEmpty())//文件名
	{
		QFileInfo fileInfo(m_urlAdress.path());
		m_strFileName = fileInfo.fileName();

	}
	if (!m_strDir.isEmpty())//文件夹
	{
		QDir directory(m_strDir);
		if (!directory.exists())//没有此文件夹,则创建
		{
			directory.mkpath(m_strDir);
		}
		m_strFileName = m_strDir + m_strFileName;//添加/是为了防止用户名没有加/,因为对于文件夹来说两个/都会当成一个/
	}

	if (QFile::exists(m_strFileName))//如果文件已经存在,那么删除
	{
		QFile::remove(m_strFileName);
	}

	m_file = new QFile(m_strFileName);
	if (!m_file->open(QIODevice::WriteOnly | QIODevice::Text))
	{
		m_file->close();
		delete m_file;
		m_file = NULL;

		return;
	}
}

void CHttpDownLoadFile::DestroyData()
{
	m_netAccessManager->deleteLater();
	m_netReply->deleteLater();
	m_file->close();
	m_file->deleteLater();
}

void CHttpDownLoadFile::ReplyNewDataArrived()//响应m_netReply有新的数据到达
{
	if (m_file)
	{
		// 写文件-形式为追加
		QFile file(m_strFileName);
		if (file.open(QIODevice::Append))
			file.write(m_netReply->readAll());
		file.close();
	}
	else
	{
		qDebug() << m_netReply->readAll();
	}
}

void CHttpDownLoadFile::ReplyFinished()//响应数据接收完成
{
	m_bIsFinished = true;
	m_netAccessManager->deleteLater();
	m_netReply->deleteLater();
	m_file->close();
	m_file->deleteLater();

	emit DownloadFinishedSignal();
}

void CHttpDownLoadFile::ReplyDownloadProgress(qint64 bytesReceived, qint64 bytesTotal)
{
	QFileInfo fileInfo(m_urlAdress.path());
	QString strFileName = fileInfo.fileName();
	emit DownloadProcess(strFileName, bytesReceived, bytesTotal);
}

AutoUpdate 头文件

#pragma once

#include <QtWidgets/QDialog>
#include <QtWidgets/QMainWindow>
#include <QUrl>
#include <QDesktopServices>
//网络相关头文件
#include <QtNetwork/QNetworkAccessManager>
#include <QtNetwork/QNetworkRequest>
#include <QtNetwork/QNetworkReply>
//JSON相关头文件
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QMessageBox>
#include <QFile>
#include <QFileInfo>
#include <QDir>
#include <QTime>
#include <QProgressBar>
#include <QTextCodec>

#include <QProcess>
#include "zlib.h"
#include "zconf.h"
#include "unzip.h"
#include <direct.h>

#include "ui_AutoUpdate.h"
#include "CHttpDownLoadFile.h"
#include <QList>

class AutoUpdate : public QDialog
{
	Q_OBJECT

public:
	AutoUpdate(QWidget *parent = Q_NULLPTR);

private:
	int ParseJson(const QString &str);      //解析数据函数的声明
private:
	QNetworkAccessManager *	m_NetManager;     //定义网络请求对象
	QNetworkAccessManager *	m_NetManagerDown;     //定义网络请求对象
	QNetworkReply *			m_pReply;
	QString					m_CurVerison;    //定义当前软件的版本号
	QUrl					m_url;
	QString					m_strUrl;
	QString					m_strFileName;
	QTime					m_downloadTime;
	int						m_nTime;
	QString					m_lastVerison;	

private:
	CHttpDownLoadFile *m_httpXML;
	QList<QString> m_listFileDir;
	QList<QString> m_listFileName;
	QString m_strTip;
	QString m_strXmlName;

	void DownLoadXML();
	QString GetElementVersion(const QString &xml, const QString &name);
	bool CheckVersion(const QString &v1, const QString &v2);
	int CheckUpdateFiles(const QString &xml1, const QString &xml2);
	void DownLoadUpdateFiles();
	void ExitApp(const QString &name);

private slots:
	void slotUpdateNow();
	void slotClose();
	void replyFinished(QNetworkReply *reply);   //json 文件下载结束
	void onDownloadProgress(QString fileName, qint64 bytesReceived, qint64 bytesTotal);	//下载进度
	void ReplyHttpFinished();
private:
	Ui::AutoUpdate ui;
};

AutoUpdate源文件

# include <stdio.h>
# include <inttypes.h>
# include <Windows.h>
# include <WinInet.h>
#include "AutoUpdate.h"
#include "configer.h"
#include "Format.h"
#include <QDomDocument>

AutoUpdate::AutoUpdate(QWidget *parent)
	: QDialog(parent),
	m_NetManager(NULL),
	m_NetManagerDown(NULL),
	m_nTime(0)
{
	ui.setupUi(this);
	ui.progressBar->setValue(0);
	m_strFileName = QString(QString::fromLocal8Bit(Configer::GetInstance()->FilePath().c_str())) + "\\Test.zip";

	m_CurVerison = QString::fromStdString(Configer::GetInstance()->GetVersion());
	setWindowIcon(QIcon(QString::fromStdString(Configer::GetInstance()->GetWindowIcon())));
	setWindowFlags(Qt::FramelessWindowHint | Qt::WindowMinimizeButtonHint | Qt::WindowStaysOnTopHint);

	m_NetManager = new QNetworkAccessManager(this);          //新建QNetworkAccessManager对象
	connect(m_NetManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*)));//关联信号和槽

	QNetworkRequest quest;
	quest.setUrl(QUrl(Configer::GetInstance()->GetCfgQurl().c_str())); //包含最新版本软件的下载地址
	//quest.setHeader(QNetworkRequest::UserAgentHeader, "RT-Thread ART");
	QNetworkReply *reply = m_NetManager->get(quest);    //发送get网络请求
	connect(reply, SIGNAL(error), this, SLOT(slotError));
}
int AutoUpdate::ParseJson(const QString &str)
{
	QJsonParseError err_rpt;
	QJsonDocument  root_Doc = QJsonDocument::fromJson(str.toUtf8(), &err_rpt);//字符串格式化为JSON
	if (err_rpt.error != QJsonParseError::NoError)
	{
		QMessageBox::critical(this, QString::fromLocal8Bit("检查失败"), QString::fromLocal8Bit("服务器地址错误或JSON格式错误!"));
		return -1;
	}
	if (root_Doc.isObject())
	{
		QJsonObject  root_Obj = root_Doc.object();   //创建JSON对象,不是字符串

		m_lastVerison = root_Obj.value("LatestVerison").toString();  //V1.0
		m_strUrl = root_Obj.value("Url").toString();
		QString UpdateTime = root_Obj.value("UpdateTime").toString();
		QString ReleaseNote = root_Obj.value("ReleaseNote").toString();
		if (m_lastVerison > m_CurVerison)
		{
			this->show();
			QString warningStr = QString::fromLocal8Bit("检测到新版本!\n版本号:") + m_lastVerison + "\n" + QString::fromLocal8Bit("更新时间:") + UpdateTime + "\n" + QString::fromLocal8Bit("更新说明:") + ReleaseNote;
			ui.textBrowser->append(warningStr);
		}
		else
		{
			QString strCurrentDir = QString(QString::fromLocal8Bit(Configer::GetInstance()->FilePath().c_str()));//当前程序运行路径
			ExitApp(strCurrentDir + QString::fromStdString(Configer::GetInstance()->GetLocation()) + Configer::GetInstance()->GetRunExe().c_str());
		}
	}
	return 0;
}
void AutoUpdate::replyFinished(QNetworkReply *reply)
{
	QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
	if (statusCode != 200)
	{
		//QMessageBox::critical(this, QString::fromLocal8Bit("Json文件下载失败 状态码:%1").arg(statusCode.toString()), reply->errorString());
		return;
	}

	ParseJson(reply->readAll());
	reply->deleteLater();
}
void AutoUpdate::slotUpdateNow()
{
	//杀掉正在运行的进程
	QProcess taskkill;
	taskkill.execute("taskkill", QStringList() << "-im" << Configer::GetInstance()->GetRunExe().c_str() << "-f");

	ui.stackedWidget->setCurrentWidget(ui.pageInfo);
	ui.butNow->hide();
	DownLoadXML();
}
void AutoUpdate::slotClose()
{
	reject();
	qApp->exit(0);
	m_httpXML->DestroyData();
	/*
	this->close();*/
	
}
void AutoUpdate::onDownloadProgress(QString fileName,qint64 bytesReceived, qint64 bytesTotal)
{
	ui.lblFileInfo->setText(fileName);
	// 总时间
	int nTime = m_downloadTime.elapsed();

	// 本次下载所用时间
	nTime -= m_nTime;

	// 下载速度
	double dBytesSpeed = (bytesReceived * 1000.0) / nTime;
	double dSpeed = dBytesSpeed;

	//剩余时间
	qint64 leftBytes = (bytesTotal - bytesReceived);
	double dLeftTime = (leftBytes * 1.0) / dBytesSpeed;

	ui.lblSpeedInfo->setText(speed(dSpeed));
	ui.lblLeftTime->setText(timeFormat(dLeftTime));
	ui.lblFileInfoSize->setText(formatSize(bytesTotal));
	ui.lblDownLoadInfo->setText(formatSize(bytesReceived));
	ui.progressBar->setMaximum(bytesTotal);
	ui.progressBar->setValue(bytesReceived);

	// 获取上一次的时间
	m_nTime = nTime;
}

void AutoUpdate::DownLoadXML()
{
	/**从网页下载XML版本控制文件,里面记录了最新的文件版本**/
	string str = Configer::GetInstance()->FilePath();
	QString strDownLoad = QString::fromLocal8Bit(Configer::GetInstance()->FilePath().c_str()) + QString::fromStdString(Configer::GetInstance()->GetLocation());//存放下载文件的路径

	QDir directory(strDownLoad);//如果路径不存在,则创建
	if (!directory.exists())
	{
		directory.mkpath(strDownLoad);
	}
	QUrl url(QString::fromStdString(Configer::GetInstance()->GetXmlQurl()));
	QFileInfo fileInfo(url.path());
	m_strXmlName = fileInfo.fileName();

	m_httpXML = new CHttpDownLoadFile(QString::fromStdString(Configer::GetInstance()->GetXmlQurl()), "", strDownLoad, this);//调用下载文件的类
	connect(m_httpXML, SIGNAL(DownloadFinishedSignal()), this, SLOT(ReplyHttpFinished()));//发生错误时一样会发送此信号
	m_httpXML->DownLoadFile();
}

QString AutoUpdate::GetElementVersion(const QString &xml, const QString &name)
{
	QString result = "";
	if (xml.isEmpty() || name.isEmpty())
	{
		qDebug() << "名称或者xml文件路径为空";
		return result;
	}
	if (!QFile::exists(xml))
	{
		qDebug() << "xml文件不存在";
		return result;
	}

	QFile file(xml);
	if (file.open(QIODevice::ReadOnly | QFile::Text))//文件打开成功
	{
		QDomDocument doc;
		QString errorStr;
		int errorLine;
		int errorColumn;

		if (doc.setContent(&file, false, &errorStr, &errorLine, &errorColumn))
		{
			QDomElement root = doc.documentElement();
			if (root.tagName() == "filelist")
			{
				int i = 0;
				QDomNodeList nodeList = root.elementsByTagName("file");
				for (; i<nodeList.size(); i++)
				{
					QString tempName = nodeList.at(i).toElement().attribute("name");
					//QStringdir    =nodeList.at(i).toElement().attribute("dir");

					QString version = nodeList.at(i).toElement().attribute("version");
					if (name == tempName)
					{
						qDebug() << "find!" << name;
						result = version;
						break;
					}

				}
				if (i == nodeList.size())
				{
					qDebug() << "can'tfind!" << name;
				}
			}
			else
			{
				qDebug() << "root.tagname!=filelist..";
			}

		}
		else
		{
			qDebug() << "setcontenterror...";
		}

		file.close();
	}
	else
	{
		qDebug() << "openforreaderror...";
	}
	return result;
}

bool AutoUpdate::CheckVersion(const QString &v1, const QString &v2)
{
	return v1 == v2;
}

int AutoUpdate::CheckUpdateFiles(const QString &xml1, const QString &xml2)
{
	m_listFileDir.clear();
	m_listFileName.clear();

	if (xml1.isEmpty() || xml2.isEmpty())
		return 0;

	if (QFile::exists(xml2))
	{
		if (QFile::exists(xml1))
		{
			m_strTip = QString::fromLocal8Bit("检查需要更新的文件...");
			QFile file(xml1);
			if (file.open(QIODevice::ReadOnly | QFile::Text))//文件打开成功
			{

				QString errorStr;
				int errorLine;
				int errorColumn;

				QDomDocument doc;

				if (doc.setContent(&file, false, &errorStr, &errorLine, &errorColumn))
				{
					QDomElement root = doc.documentElement();
					if (root.tagName() == "filelist")
					{
						QDomNodeList nodeList = root.elementsByTagName("file");
						for (int i = 0; i<nodeList.size(); i++)
						{
							QString name = nodeList.at(i).toElement().attribute("name");
							QString dir = nodeList.at(i).toElement().attribute("dir");
							QString version = nodeList.at(i).toElement().attribute("version");
							QString versionDownload = GetElementVersion(xml2, name);//获取本地xml文件对应文件(name)的版本信息

							if (versionDownload.isEmpty())//本地XML没有此文件:下载,并放到相应的目录中
							{
								m_listFileDir.append(dir);
								m_listFileName.append(name);
							}
							else
							{
								/**检查版本,如果本地版本低于下载的版本,则下载**/
								if (!CheckVersion(version, versionDownload))
								{
									m_listFileDir.append(dir);
									m_listFileName.append(name);
								}
								else
								{
									qDebug() << name << QString::fromLocal8Bit("文件是最新版本,不需要更新");
								}
							}
						}
						return 1;//此时要退出,避免关闭程序
					}
					else
					{
						m_strTip = "XML内容错误!";
						return 0;

					}
				}
				else
				{
					qDebug() << "setcontenterror...";
					return 0;
				}
			 file.close();

			}
			else
			{
				m_strTip = "不能打开更新文件!";
				return 0;

			}

		}
		else
		{

			m_strTip = "下载更新文件错误!";
			return 0;
		}
	}

	else
	{
		m_strTip = "本地的更新文件不存在!";
		return 0;
	}
}

void AutoUpdate::DownLoadUpdateFiles()
{
	QString strServer = QString::fromStdString(Configer::GetInstance()->GetSerQurl());//需要下载的文件存储位置
	QString strCurrentDir = QString(QString::fromLocal8Bit(Configer::GetInstance()->FilePath().c_str()));//当前程序运行路径

	if (m_listFileDir.isEmpty() || m_listFileDir.isEmpty())
	{
		ExitApp(strCurrentDir + QString::fromLocal8Bit(Configer::GetInstance()->GetLocation().c_str()) + Configer::GetInstance()->GetRunExe().c_str());
		return;
	}
	m_strTip = QString::fromLocal8Bit("开始下载更新文件...");

	for (int i = 0; i<m_listFileName.size(); i++)
	{
		m_strTip = QString::fromLocal8Bit("正在下载文件") + m_listFileName.at(i);
		ui.progressBar->setValue(100 * i / m_listFileName.size());

		/**放置下载的文件的路径**/
		QString temp = m_listFileDir.at(i);
		QString tempdir = m_listFileDir.at(i);
		if (!m_listFileDir.at(i).isEmpty())
		{
			temp = temp + "/";
			tempdir = tempdir + "\\";
		}
		QString strPlaceDir = strCurrentDir + QString::fromStdString(Configer::GetInstance()->GetLocation()) + tempdir;
		QDir directory(strPlaceDir);//如果路径不存在,则创建
		if (!directory.exists())
			directory.mkpath(strPlaceDir);

		QString strFileDirServer = strServer + temp + m_listFileName.at(i);//文件在服务器中的存储位置

		CHttpDownLoadFile *http = new CHttpDownLoadFile(strFileDirServer, "", strPlaceDir, this);//调用下载文件的类
		http->DownLoadFile();
		connect(http, SIGNAL(DownloadProcess(QString, qint64, qint64)), this, SLOT(onDownloadProgress(QString, qint64, qint64)));
		m_downloadTime.start();
		while (!http->m_bIsFinished)
		{
			if (http->m_nTotal == -1)
			{
				ui.progressBar->setValue(1);
			}
			else
			{
				ui.progressBar->setValue(100 * http->m_nReceived / http->m_nTotal);
			}

			QCoreApplication::processEvents();
		}
		m_strTip = QString::fromLocal8Bit("文件") + m_listFileName.at(i) + QString::fromLocal8Bit("下载完成");

		///**将下载好的文件复制到主目录中,先删除原先的文件**/
		//QString strLocalFileName = strCurrentDir + "\\" + m_listFileDir.at(i) + "\\" + m_listFileName.at(i);

		//if (QFile::exists(strLocalFileName))QFile::remove(strLocalFileName);

		//QDir directory1(strCurrentDir + "\\" + m_listFileDir.at(i));//如果路径不存在,则创建

		//if (!directory1.exists())directory1.mkpath(strCurrentDir + "\\" + m_listFileDir.at(i));

		//QFile::copy(strPlaceDir + "\\" + m_listFileName.at(i), strLocalFileName);

	}

	m_strTip = QString::fromLocal8Bit("更新完成!");

	/**替换旧的xml文件**/
	QString strNewXML = strCurrentDir + QString::fromStdString(Configer::GetInstance()->GetLocation()) + m_strXmlName;//最新的XML文件
	QString strOldXML = strCurrentDir + "\\" + m_strXmlName;//旧的XML文件

	QFile::remove(strOldXML);
	QFile::copy(strNewXML, strOldXML);

	ExitApp(strCurrentDir + QString::fromStdString(Configer::GetInstance()->GetLocation()) + Configer::GetInstance()->GetRunExe().c_str());
}

void AutoUpdate::ExitApp(const QString &name)
{
	if (!name.isEmpty())
	{
		/**运行主程序,并且退出当前更新程序(说明:主程序在上上一级目录中)**/
		//更新配置文件版本号
		Configer::GetInstance()->SaveVersion(m_lastVerison.toStdString().c_str());
		if (!QProcess::startDetached(name, QStringList()))//启动主程序,主程序在其上一级目录
		{
			QMessageBox::warning(this, QString::fromLocal8Bit("警告信息"), QString::fromLocal8Bit("启动主程序错误!\n可能主程序不存在或者被破坏!\n解决办法:重新安装程序!"));
		}
	}
	this->close();
}

void AutoUpdate::ReplyHttpFinished()
{
	CheckUpdateFiles(QString::fromLocal8Bit(Configer::GetInstance()->FilePath().c_str()) + QString::fromStdString(Configer::GetInstance()->GetLocation()) + m_strXmlName, QString::fromLocal8Bit(Configer::GetInstance()->FilePath().c_str()) + "\\" + m_strXmlName);
	DownLoadUpdateFiles();
}

三、遇到的问题

  • 程序支持中文路径,不需要中间进行转换,设置编码。
  • 如果程序安装到C盘,需要以管理员的方式启动程序。
  • 如果要更新的程序已经在运行中,那么就需要杀掉正在运行的进程,再进行更新,不然会更新失败。
  • 相对上https://blog.csdn.net/weixin_38739598/article/details/106571074,避免了下载整个压缩包,没必要更新的文件也下载了。

四、实现效果

在这里插入图片描述
在这里插入图片描述
参考文章:https://blog.csdn.net/hulinhulin/article/details/46839107
由于博主只写了部分,所以自己实现了部分。

源码下载地址:Qt 实现软件自动升级
  • 6
    点赞
  • 78
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值