电脑软件更新app(PC客户端软件在线更新方案)

wufei123 发布于 2024-01-28 阅读(147)

好久没有写文章了,这次介绍一种简单的PC客户端软件升级方案,从客户端到后台实现都是亲自手撸代码实现,并且在实际项目中应用(一般本人介绍的技术方法或者方案都是在实际项目中真实应用,并且介绍的案例都是基于真实项目),话不多说,进入正题。

1 技术背景PC客户端软件:这里的客户端软件是本人自己业余做的一款机器人控制软件,使用C++/Qt开发;软件更新后台:使用Python语言开发,web框架采用Flask;2 更新步骤

好吧,我承认我不会画图,我会认真学习的但是图糙理不糙,更新过程中前后台的交互就如同上图所示,只需要两步:第1步是客户端软件向发送请求获取最新的版本信息;第2步是客户端软件根据第一步的结果来下载最新版的客户端软件。

下面详细介绍这两个过程使用的技术和实现方法:2.1 客户端发送请求获取最新版本信息这是更新流程的第一步,我采取的方法是在客户端启动之后便发送一次http请求,根据预先定义好的接口(这里的接口下面介绍后台实现的时候介绍)请求向后台获取最新的版本信息(获取到的版本信息具体有哪些,在介绍后台接口实现的时候会详细介绍,这里最重要的信息主要包括两个:一个是版本号,一个是最新版软件的下载地址),然后将获取到的版本号跟当前软件的版本号进行比较,如果最新软件的版本号大于当前软件的版本号,则客户端弹框提示当前软件有新版,询问客户是否需要更新,如果更新,则客户端根据后台返回的下载地址进行下载。

2.1.1 如何发送请求获取最新版本信息这里我定义的软件最新版本的后台api是http://domainname/api/software/check_version,这里的url中的domainname可以是后台域名也可以是ip:port的形式,在客户端软件中,我使用的http库是Qt的QNetworkAccessManager库,关于QNetworkAccessManager的用法这里不做过多介绍,Qt的文档里面很详细,下面是客户端软件中发出请求的一段代码:

QNetworkRequestreq;req.setUrl(QUrl::fromUserInput(QString("%1/api/software/check_version").arg(HsrCore

::HSR_BASE_URL)));m_app_controller_->httpMgr()->networkAccessMgr()->get(req);上面代码里面networkAccessMgr()方法返回的是QNetworkAccessManager的实例,由于获取最新版本信息的接口是get请求,故只需要调用QNetworkAccessManager的get方法即可,QNetworkRequest是Qt用来封装请求地址,请求头,请求参数等,QNetworkReply是Qt用来封装请求的响应。

2.1.2 如何根据最新信息判断是否有新版本软件发送请求之后,后台接收请求会查询数据库中最新的版本信息,然后封装成json格式返回,客户端接收到之后处理返回来的数据,还是拿一部分实际代码来说明吧:QJsonObject

jsonObj=QJsonDocument::fromJson(reqEntity.network_reply->readAll()).object();intrespCode=jsonObj.value

("code").toInt();if(respCode!=hsr::API_RESP_OK){emitreqLatestVersionResponse(respCode,jsonObj.value("message"

).toString());return;}m_soft_update_date_item_->updateFromJson(jsonObj.value("data").toObject());emit

reqLatestVersionResponse(respCode,jsonObj.value("message").toString());上面是客户端代码中收到后台请求之后,根据返回的http响应来提取返回的结果,QNetworkReply的readAll()方法会读取http响应的body部分,由于返回的是json结构,所以使用QJsonDocument::fromJson的静态方法来从QByteArray中格式化json成QJsonObject方便使用,这里的m_soft_update_date_item_->updateFromJson(jsonObj.value(“data”).toObject());这一句主要就是把json结构数据转换成本地实体类结构,展示一下该方法的实现就知道返回的json里面主要包含的字段信息了:

voidHsrSoftwareUpdateDataItem::updateFromJson(constQJsonObject&jsonObj){m_app_filename_=jsonObj.value

("soft_name").toString();//安装包名称 m_filepath_=jsonObj.value("filepath").toString();//相对下载路径 m_major_version_

=jsonObj.value("major_version").toInt();//主版本号 m_minor_version_=jsonObj.value("minor_version").toInt();

//次版本号 m_fixed_version_=jsonObj.value("fixed_version").toInt();//修订版本号 m_update_log_=jsonObj.value("update_log"

).toString();//更新日志 m_update_story_=jsonObj.value("update_story").toString();//更新物语 }从上述代码的注释中可以看到包含的主要信息,这里我们就先关注major_version,minor_version,fixed_version以及filepath即可,现在有了后台返回的最新版本信息,则只要跟软件中当前的版本号比对即可,下面是对处理数据后发出的信号reqLatestVersionResponse做的处理,也是比对版本号的代码:

voidHsrMainWindow::onReqLatestSoftVersionReponse(intrespCode,QStringrespString){(void)respString;if(respCode

!=hsr::API_RESP_OK)return;// 比较服务器上的最新版本和当前的软件版本 HsrSoftwareUpdateDataItem*softUpdateDataItem=m_app_controller_

->softwareController()->softUpdateDataItem();if(HsrCore::HSR_APP_MAJOR_VERSION>softUpdateDataItem->latestMajorVersion

()||(HsrCore::HSR_APP_MAJOR_VERSION==softUpdateDataItem->latestMajorVersion()&&HsrCore::HSR_APP_MINOR_VERSION

>softUpdateDataItem->latestMinorVersion())||(HsrCore::HSR_APP_MAJOR_VERSION==softUpdateDataItem->latestMajorVersion

()&&HsrCore::HSR_APP_MINOR_VERSION==softUpdateDataItem->latestMinorVersion()&&HsrCore::HSR_APP_FIXED_VERSION

>=softUpdateDataItem->latestFixedVersion())){if(m_check_soft_version_action_->property("manualChecked"

).toBool()){m_check_soft_version_action_->setProperty("manualChecked",false);HsrMessageBox::information

(HSR_TEXT_INFORMATION,QObject::tr("当前已经是最新版本."),this);}return;}//弹框提示用户更新信息 HsrSoftwareUpdateDialogdlg

(this);dlg.exec();}以上代码主要是用服务器返回来的版本信息与软件当前的版本信息比较,看看当前版本是否低于服务端的最新版本,如果低于,则提示用户更新,上面代码中的三个常量值分别是客户端软件中标记当前的版本号,并且每次软件更新会修改这几个版本号再编译打包

constintHsrCore::HSR_APP_MAJOR_VERSION=0;constintHsrCore::HSR_APP_MINOR_VERSION=2;constintHsrCore::HSR_APP_FIXED_VERSION

=0;2.1.3 下载最新版本并安装如果有最新版本,客户端软件可以弹框提示用户是否需要更新,可以像下面这样提示

点击更新按钮,便可以开始从后台下载最新安装包,在2.1.2中说的后台返回来的最新版本信息中包含了相对下载路径,这里便可以用来生成后台下载路径了boolHsrSoftwareController::downloadLatestSoftware

(){if(m_download_reply_){m_download_reply_->deleteLater();m_download_reply_=nullptr;//关闭文件 QFileInfofileinfo

;if(m_download_file_){fileinfo.setFile(m_download_file_->fileName());m_download_file_->close();QFile::

remove(fileinfo.absoluteFilePath());deletem_download_file_;m_download_file_=nullptr;}}QNetworkRequest

request;request.setUrl(QUrl::fromUserInput(QString("%1/download/%2").arg(HsrCore::HSR_BASE_URL).arg(m_soft_update_date_item_

->downloadFilePath())));m_download_path_=QCoreApplication::applicationDirPath()+"/downloads";QDirdir;

if(!dir.exists(m_download_path_)){dir.mkpath(m_download_path_);}m_soft_installer_file_path_=m_download_path_

+"/"+m_soft_update_date_item_->latestAppInstallerName();//这里打开文件,将接收到的内容写入该文件 m_download_file_=newQFile

(m_soft_installer_file_path_);if(!m_download_file_->open(QFile::WriteOnly|QFile::Truncate)){deletem_download_file_

;m_download_file_=nullptr;emitdownloadingFinished();returnfalse;}m_download_reply_=m_app_controller_->

httpMgr()->networkAccessMgr()->get(request);if(m_download_reply_){emitdownloadingStarted();connect(m_download_reply_

,&QNetworkReply::readyRead,this,&HsrSoftwareController::onDownloadReplyReadyRead);//下载并写入安装包文件数据 connect

(m_download_reply_,&QNetworkReply::finished,this,&HsrSoftwareController::handleDownloadReplyFinished);

//下载结束 connect(m_download_reply_,&QNetworkReply::downloadProgress,this,&HsrSoftwareController::handleDownloadReplyProgress

);//下载进度 }returntrue;}上面是下载安装包文件的代码,由于我后台有个统一下载文件的地址前缀,http://domainname/download/相对下载路径,只要把url中的相对下载路径部分改成上面后台返回来的相对下载路径就是安装包文件的下载地址了,从上面可以看到,客户端下载文件也是使用QNetworkAccessManager,因为下载文件在网络层来说也是http请求,至于如何使用QNetworkAccessManager下载文件,Qt的文档里面有例子,这里不做过多介绍。

这里将下载下来的文件写入指定的目录即可,我是保存在程序目录下的downloads目录,下载完成之后先执行安装包程序,再退出当前程序即可,这样升级过程的客户端操作便完成// 进行安装操作 QProcess

::startDetached(m_software_controller_->downloadedLatestSoftInstallerFilePath(),QStringList());// 当前程序退出

qApp->quit();上面两句代码,第一句是使用QProcess::startDetached()分离式启动刚刚下载的安装包文件,第二句便是退出当前的客户端程序2.2 客户端软件升级的服务端实现服务端采用的是Python语言编写,使用的web框架是Falsk,数据库使用Mysql,部署在阿里云上,这里不介绍整个实现过程,后面我会单独写一遍文章来介绍基于Flask的web项目的部署,接下来介绍的内容可能需要一定的web开发的知识才能够看懂。

下面是软件版本信息存储在mysql中的表结构classSoftUpdate(JsonSerializer,BaseModel):""" 软件更新 """__tablename__=soft_update

id=db.Column(db.Integer,primary_key=True,autoincrement=True)# 软件名称soft_name=db.Column(db.String(255),

unique=True,nullable=False)# 文件路径filepath=db.Column(db.String(500),nullable=False)# 主版本号major_version

=db.Column(db.Integer,nullable=False)# 次版本号minor_version=db.Column(db.Integer,nullable=False)# 修复版本号fixed_version

=db.Column(db.Integer,nullable=False)# 更新物语update_story=db.Column(db.String(100),default=)# 更新日志update_log

=db.Column(db.Text)# 更新时间update_time=db.Column(db.DateTime(),default=datetime.now)# 软件类型唯一信息soft_key=

db.Column(db.Integer,default=SoftKey.YD_ROBOT_STUDIO_PC.value)然后是从数据表中查找最新版本信息的方法defcheck_version(self

):""" 检查获取最新版本信息, 目前只能获取YDRobotStudio的信息,后期该接口将会移除 """update_info=SoftUpdate.query.order_by(SoftUpdate

.major_version.desc(),SoftUpdate.minor_version.desc(),SoftUpdate.fixed_version.desc()).first()ifnotupdate_info

:returnServerErrorResult()returnSuccessResult(data=update_info)从上面可以看出,后台的ORM使用的是SQLAlchemy库,有了service层的方法,则下面便是获取最新版本信息的接口了

api=Blueprint(software_api,__name__,url_prefix=/api/software)@api.route(/check_version,endpoint=check_version

)defcheck_version():""" 检查软件版本 """returnmake_api_response(soft_update_srv.check_version())这里的make_api_response是自己封装的方法,主要是讲service层的返回结果以json格式的数据发送出去,这便是最新版本信息接口的实现了,没啥好说的,如果不理解,可以学习一下web开发的知识,可以学习Flask框架作为web开发的入门框架,上手快。

接下来便是文件下载功能的实现了,也是直接上代码fromflaskimportBlueprint,send_from_directory,send_file,current_appimportosfrom

cores.exceptions.api_exceptionsimportNotFoundErrorbp=Blueprint(download,__name__,url_prefix=/download

)@bp.route(/,endpoint=download)defdownload(name):""" 下载客户端软件 :param name: 软件名称,全路径名称,包括后缀

:return: 文件数据 """filename=current_app.config[ATTACHMENTS_ROOT_DIR]+/+nameifnotos.path.exists(

filename):returnNotFoundError(message=文件不存在)returnsend_file(filename,as_attachment=True)主要使用了flask的send_file方法。

3 小结至此,PC客户端的在线更新功能的实现方案就介绍完了,当然,这是我自己在实际项目中写的一种升级方案,因为之前看了一些现成的或者开源的工具实现,觉得太麻烦了,刚好自己也在学习后台开发方面的知识,便写了这么一种简单的升级方法。

其实上面介绍的还是比较笼统的,很多细节我也没有展开说,特别说后台实现那一块,因为后台实现那一块展开说的话就要把如何使用Flask进行web开发给介绍一遍了,没这个必要其实要是会一些web后台开发的知识的话,无论使用哪一种web框架实现上述的升级功能都很简单,因为很多东西框架已经为你做好了,这就是轮子的作用吧,反而是客户端那边的实现会有一些难度,更何况是用C++编写的客户端。

欢迎扫码关注我的个人微信公众号

main函数

亲爱的读者们,感谢您花时间阅读本文。如果您对本文有任何疑问或建议,请随时联系我。我非常乐意与您交流。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。