python使用pyinstaller打包成的exe程序,代码修改重新打包就需要重新发送一次程序,略微麻烦,通过服务器存储新版本打包后的程序,检测和下载通过代码实现。

本文通过FTP局域网服务器的形式完成,使用serv-u软件配置FTP服务器,配置方式可移步下方站内链接

Serv-U配置FTP服务器 使用designer画一个简单的ui,如下图

python 自动更新mysql 数据库脚本 python自动更新客户端_开发语言


转成py文件如下,命名为 update_test_ui.py代码如下

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'update_test.ui'
#
# Created by: PyQt5 UI code generator 5.15.7
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again.  Do not edit this file unless you know what you are doing.


from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_MainTestUp(object):
    def setupUi(self, MainTestUp):
        MainTestUp.setObjectName("MainTestUp")
        MainTestUp.resize(603, 378)
        self.centralwidget = QtWidgets.QWidget(MainTestUp)
        self.centralwidget.setObjectName("centralwidget")
        self.pushButton = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton.setGeometry(QtCore.QRect(220, 150, 161, 61))
        font = QtGui.QFont()
        font.setPointSize(20)
        font.setBold(True)
        font.setWeight(75)
        self.pushButton.setFont(font)
        self.pushButton.setStyleSheet("background-color: rgb(0, 255, 255);")
        self.pushButton.setObjectName("pushButton")
        MainTestUp.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainTestUp)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 603, 23))
        self.menubar.setObjectName("menubar")
        MainTestUp.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainTestUp)
        self.statusbar.setObjectName("statusbar")
        MainTestUp.setStatusBar(self.statusbar)

        self.retranslateUi(MainTestUp)
        self.pushButton.clicked.connect(MainTestUp.close) # type: ignore
        QtCore.QMetaObject.connectSlotsByName(MainTestUp)

    def retranslateUi(self, MainTestUp):
        _translate = QtCore.QCoreApplication.translate
        MainTestUp.setWindowTitle(_translate("MainTestUp", "软件自升级测试"))
        self.pushButton.setText(_translate("MainTestUp", "确定"))

定义一个main.py调用ui的py文件,并添加版本检测、FTP的服务器登录和文件下载功能,代码如下

# -*- coding:utf-8 -*-
from PyQt5.Qt import *
import sys
import os
import socket
from ftplib import FTP
import update_test_ui


class UpdateTest(update_test_ui.Ui_MainTestUp, QMainWindow):
    def __init__(self):
        super(update_test_ui.Ui_MainTestUp, self).__init__()
        super().__init__()
        self.setupUi(self)
        self.current_version = 'v2'
        # 承接服务器的文件名称
        self.remote_name = ''
        # 承接远程服务器文件路径
        self.remote_path = ''
        # 当前exe程序地址
        # self.local_folder = os.path.abspath(os.path.dirname(__file__))
        # 运行第一步检查是否要更新
        self.first_step()

    def first_step(self):
        try:
            # 登录FTP
            self.my_ftp = self.login_ftp("sq", "1")
            print(self.my_ftp.welcome)
            # 服务器文件目录
            self.remote_path = self.getfile("/suite")
            # 服务器文件名称
            self.remote_name = self.remote_path.split("/")[-1]
            # 服务器文件版本(设置服务器文件名称格式示例:软件名称_v1.exe)
            remote_version = self.remote_name.split("_")[-1].split(".")[0]
            # 判断服务器软件版本是否与本地版本一致
            if self.current_version != remote_version:
                # 本地路径,程序所在文件夹的上一级目录
                local_path = os.path.abspath(os.path.join(os.getcwd(), "../."))
                # 拼接一个本地路径的文件
                local_file = local_path + "/" + self.remote_name
                # 如果服务器文件还未下载
                if not os.path.exists(local_file):
                    # 本地文件目录全地址
                    local_full = local_path + "/" + self.remote_name
                    # 执行下载
                    self.download_file(local_full, self.remote_path)
                    QMessageBox.warning(self, "提示", "最新版软件已下载,请在上一级目录安装使用")
                # 服务器文件已下载
                else:
                    # 本地已存在,请在上一级目录安装使用最新版软件
                    QMessageBox.warning(self, "提示", "请在上一级目录安装使用最新版软件")
                # 设置下一步按钮不可操作,可强制使其使用新版本软件
                self.pushButton.setEnabled(False)
            else:
                pass
        except Exception:
            pass

    # 登录FTP服务器
    def login_ftp(self, username, password):
        try:
            timeout = 60
            socket.setdefaulttimeout(timeout)
            ftp = FTP()
            # 0主动模式 1 #被动模式
            ftp.set_pasv(False)
            host = "127.0.0.1"
            port = 21
            ftp.encoding = 'utf-8'  # 'gbk'
            ftp.connect(host, port)
            ftp.login(username, password)
            return ftp
        except Exception:
            pass

    # 获得FTP目录及子目录下的文件名称
    @staticmethod
    def get_file_name(f):
        names = f.split()
        if len(names) < 9:
            return ''
        else:
            file_names = names[8:]
            res = ''
            for name in file_names:
                res = res + name + ''
            return res.strip()

    # 获得FTP目录及子目录下的文件
    def getfile(self, path):
        self.my_ftp.cwd(path)
        file_list = []
        self.my_ftp.retrlines("LIST", file_list.append)
        for f in file_list:
            if f.startswith("d"):
                file_name = self.get_file_name(f)
                if file_name == '.' or file_name == '..':
                    continue
                path_a = self.my_ftp.pwd() + "/" + str(f).split(' ')[-1]
                self.getfile(path_a)
                self.my_ftp.cwd("..")
            else:
                if len(str(f).split(' ')) > 3:
                    # 得到文件名,并按照指定语句输出
                    a = self.my_ftp.pwd() + "/" + str(f).split(' ')[-1]
                    return a

    # 判断本地是否已经存在相同文件
    def is_same_size(self, local_file, remote_file):
        try:
            remote_file_size = self.my_ftp.size(remote_file)
        except Exception:
            remote_file_size = -1
        try:
            local_file_size = os.path.getsize(local_file)
        except Exception:
            local_file_size = -1
        if remote_file_size == local_file_size:
            return 1
        else:
            return 0

    def download_file(self, local_file, remote_file):
        if self.is_same_size(local_file, remote_file):
            return
        else:
            try:
                buf_size = 1024
                file_handler = open(local_file, 'wb')
                self.my_ftp.retrbinary('RETR %s' % remote_file, file_handler.write, buf_size)
                file_handler.close()
            except Exception:
                return


if __name__ == '__main__':
    app = QApplication(sys.argv)
    ui = UpdateTest()
    ui.show()
    sys.exit(app.exec())

代码还未完全实现自动化,站内有通过.bat进行自动化下载及安装的,对.bat文件的运行规则不懂,尝试未成功,文中的方法还需一步处理才能使用新版软件,且该方法让确定按钮不可点击,必须更新才可使用,若有大佬有更厉害的更加自动化的方法,请留下脚步。